Chapter Twenty-Three: Tips to Optimize Code in C++
When you write code in C++ or any other programming language, your main objective should be to write code that works correctly. Once you accomplish this, you need to change the code to improve the following:
-
The security of the code
-
The quantity of memory used while running the code
-
Performance of the code
This chapter gives you a brief idea of the areas to consider if you want to improve the performance of your code. Some points to keep in mind are:
●
You can use numerous techniques to improve the performance of your code. This method, however, can lead to the creation of a larger file.
●
If you choose to optimize multiple areas in your code at the same time, it may lead to some conflict between the areas of your code. For instance, you may not be able to optimize both the performance of the code and memory use. You need to strike a balance between the two.
●
You may always need to optimize your code, and this process is never-ending. The code you write is never fully optimized. There is always room to improve some parts of your code if you want the code to run better.
●
You can use different tricks to improve the performance of the code. While you do this, you should ensure that you do not forget about some coding standards. Therefore, do not use cheap tricks to make the code work better.
Using the Appropriate Algorithm to Optimize Code
Before you write any code, you need to sit down and understand the task. You then need to develop the right algorithm to use to optimize the code. We are going to understand how the algorithm affects your code using a simple example. In the program, we are going to use a two-dimensional segment to identify the maximum value and, for this, we will take two whole numbers. In the first code, we will not
look at the program’s performance. We will then look at a few methods to use to improve the performance of the code.
Consider the following parameters used in the code: both numbers should lie between the interval [-100, 100]. The maximum value is calculated using the function: (x * x + y * y) / (y * y + b).
There are two variables used in this function – x and y. We are also using a constant ‘b’ which is a user-defined value. The value of this constant should always be greater than zero but less than 1000. In the example below, we do not use the pow() function from the math.h library.
#include <iostream>
#define LEFT_MARGINE_FOR_X -100.0
#define RIGHT_MARGINE_FOR_X 100.0
#define LEFT_MARGINE_FOR_Y -100.0
#define RIGHT_MARGINE_FOR_Y 100.0
using namespace std;
int
main(void)
{
//Get the constant value
cout<<"Enter the constant value b>0"<<endl;
cout<<"b->"; double dB; cin>>dB;
if(dB<=0) return EXIT_FAILURE;
if(dB>1000) return EXIT_FAILURE;
//This is the potential maximum value of the function
//and all other values could be bigger or smaller
double dMaximumValue = (LEFT_MARGINE_FOR_X*LEFT_MARGINE_FOR_X+LEFT_MARGI
NE_FOR_Y*LEFT_MARGINE_FOR_Y)/ (LEFT_MARGINE_FOR_Y*LEFT_MARGINE_FOR_Y+dB);
double dMaximumX = LEFT_MARGINE_FOR_X;
double dMaximumY = LEFT_MARGINE_FOR_Y;
for(double dX=LEFT_MARGINE_FOR_X; dX<=RIGHT_MARGINE_FOR_X; dX+=1.0)
for(double dY=LEFT_MARGINE_FOR_Y; dY<=RIGHT_MARGINE_FOR_Y; dY+=1.0)
if( dMaximumValue<((dX*dX+dY*dY)/(dY*dY+dB)))
{
dMaximumValue=((dX*dX+dY*dY)/(dY*dY+dB));
dMaximumX=dX;
dMaximumY=dY;
}
cout<<"Maximum value of the function is="<< dMaximumValue<<endl;
cout<<endl<<endl;
cout<<"Value for x="<<dMaximumX<<endl
<<"Value for y="<<dMaximumY<<endl;
return EXIT_SUCCESS;
}
Look at the code carefully. You notice that the function and value dX * dX is run by the process too many times, and the value is stored multiple times in the memory. This is a waste of CPU time and memory. What do you think we could do to improve the speed of the code? An alternative to writing the operation multiple times in the code is to declare a variable and assign this function to it. Let us define a variable d, which stores the value of the function dX * dX. You can use the variable ‘d’ everywhere in the code where you need
to use the calculation. You can optimize other sections of the above code as well. Try to spot those areas.
The next area we need to look at is how general the lines of code are. You need to see whether the program runs as fast as you want it to. If you want to increase the speed of the algorithm, you need to tweak some functions based on the size of your input. What does this mean?
You can improve the speed of the code you have written using multiple algorithms instead of only one algorithm. When you use two algorithms, you can instruct the compiler to switch between the algorithms based on a condition.
Optimizing Code
When you write code, every element in your code uses some space in the memory. It is important to understand how each word in your code uses memory to reduce consumption or usage. Let us consider a simple example where we try to swap the values in two variables in the memory. You can do this using numerous sorting algorithms. To understand this better, let us take a real-world example – you have two people sitting in two different chairs. You introduce a third or temporary chair to hold one of the individuals when they want to swap chairs.
Consider the following code:
int nFirstOne =1, nSecondOne=2;
int nTemp = nFirstOne;
nFirstOne = nSecondOne;
nSecondOne = nTemp;
This code is easy to use, but when you create a temporary variable in your code, the compiler will assign some space in the memory for this object. You can avoid wasting memory space by avoiding the usage of a temporary variable in the code.
int nFirstOne = 3, nSecondOne = 7;
nFirstOne += nSecondOne;
nSecondOne = nFirstOne ? nSecondOne;
nFirstOne -= nSecondOne;
You may need to swap large values in the memory to a different section. How would you do this? The easiest way to do this is to use pointers. Instead of copying the same value across the memory, use a pointer to obtain the address of the value in the memory. You can then change their address instead of moving the value from one location to the next in the memory.
You may wonder how you can determine if your code is faster or how you can calculate this. When you finish writing your code, the system will translate it into a language it understands using the assembler. It will then translate this into machine code, which it quickly interprets. Every operation you write in the code takes place in the processor. It may also take place in the graphic card or mathematical coprocessor.
One operation can take place in one clock cycle, or it may take a few. For this reason, it is easier for the computer to multiply numbers compared to division. This could be the case because of the optimization the computer performs. You can also leave the task of optimization to the compiler in some cases.
If you want to learn more about how fast your code is, you should know the architecture of the computer you are using. The code can be faster because of one of the following reasons:
-
The program runs in the cache memory
-
The mathematical coprocessor processes sections of the code
-
A branch predictor was used correctly by the compiler
Let us now consider the following numbers: O(n), O(log(n) *n), n*n, n!. When you use this type of code, the program's speed depends on the number you key into the system. Let us assume you enter n = 10. The program may take ‘t’ amount of time to run and compile. What do you think will happen when you enter n = 100? The program may take 10 times longer to run. It is important to understand the limits a small number can have on your algorithm.
Some people also take time to see how fast the code runs. This is not
the right thing to do since not every program or algorithm you key in is completed first by the processor. Since an algorithm does not run in the computer’s kernel mode, the processor can get another task to perform. This means the algorithm is put on hold. Therefore, the time you write down is not an accurate representation of how fast the code can run. If you have more than one processor in the system, it is harder to identify which processor is running the algorithm. It is tricky to calculate the speed at which the processor completes running your code.
If you want to optimize or improve the speed at which the program runs, you need to prevent the processor from shifting the code to a different core during the run. You also need to find a way to prevent the counter from switching between tasks since that only increases the time the processor takes to run the code. You may also notice some differences in your code since the computer does not transfer all optimizations into machine code.
Using Input and Output Operators
When you write code, it is best to identify the functions you can use which do not occupy too much space in the memory. Most times, you can improve the speed of the program by using a different function to perform the same task. Printf and scanf are two functions used often in C programming, but you can use the same keywords in C++ if you can manipulate some files. This increases the speed of the program and can save you a lot of time and memory.
Let us understand this better through an example. You have two numbers in a file and need to read those numbers. It is best to use the keywords cin and cout on files in terms of security since you have instructions passed to the compiler from the header library in C++. If you use printf or scanf, you may need to use other functions of keywords to increase the speed of the program. If you want to print strings, you can use the keyword put or use an equivalent from file operations.
Optimizing the Use of Operators
You need to use operators to perform certain functions in C++. Basic operators, such as +=, *= or -= use a lot of space in the memory.
This is especially true when it comes to basic data types. Experts recommend you use a postfix decrement or increment along with the prefix operator if you want to improve the functioning of the code. You may also need to use the << or >> operators instead of division and multiplication, but you need to be careful when you use those operators. This may lead to a huge mistake in the code. It takes some time to identify these mistakes and, to overcome the mistake, you need to add more lines of code. This is only going to reduce the speed and performance of the program.
It is best to use bit operators in your code since these increase the speed of the program. If you are not careful about how you use these operators, you may end up with machine-dependent code, and this is something you need to avoid.
C++ is a hybrid language and allows you to use an assembler’s support to improve the functioning of your program. It also allows you to develop solutions to problems using object-oriented programming. If you are adept at coding, you can develop libraries to improve your code's functioning.
Optimization of Conditional Statements
You may need to include numerous conditional statements in your code, depending on the type of code you are writing. Most people choose to use the ‘if’ conditional statement, but it is advised that you do not do this. It is best to use the switch statement. When you use the former conditional statement, the compiler needs to test every element in the code, and this creates numerous temporary variables to store the code. This reduces the performance of the code.
It is important to note that the ‘if’ conditional statement has many optimizations built into the statement itself. If you only have a few conditions to test, and if these are connected to the or operator, you can use the ‘if’ conditional statement to calculate the value. Let us look at this using an example. We have two conditions, and each of these uses the and operator. If you have two variables and want to test if both values are equal to a certain number, you use the and operator. If the compiler notes that one value does not meet the condition, it returns false and does not look at the second value.
When you use conditional statements in your code, it is best to identify the statements which often occur before the other conditional statements. This is the best way to determine if an expression is true or false. If you have too many conditions, you need to sort them and split them into a nested conditional statement. There may be a possibility that the compiler does not look at every branch in the nest you have created. Some lines of code may be useless to the compiler, but they simply occupy memory.
You may also come across instances where you have long expressions with numerous conditions. Most programmers choose to use functions in this instance, but what they forget is that functions take up a lot of memory. They create calls and stacks in memory. It is best to use a macro to prevent the usage of memory. This increases the speed of the program. It is important to remember that negation is also an operation you can use in your code.
Dealing with Functions
If you are not careful when you use functions, you may end up with bad code. Consider the following example. If you have code written in the same format as the statements below, it will lead to a bad code:
for(int i=1; i<=10; ++i)
DoSomething(i);
Why do you think this is the case? When you write some code similar to the above, you need to call the function a few times. It is important to remember that the calls the compiler makes to the functions in the code use a lot of memory. If you want to improve the performance of the code, you can write the statement in the following format:
DoSomething(n);
The next thing you need to learn more about is inline functions. The compiler will use an inline function similar to a macro if it is small. This is one way to improve the performance of your code. You can also increase the reusability of the code in this manner. When you
pass large objects from one function to another, it is best to use references or pointers. It is better to use a reference since this allows you to write code that is easy to read. Having said that, if you are worried about changing the value of the actual variable being passed to the function, you should avoid using references. If you use a constant object, you should use the keyword const since it will save you some time.
It is important to note that the arguments and parameters passed in the function will change depending on the situation. When you create a temporary object for a function, it will only reduce the speed of the program. We have looked at how you can avoid using or creating temporary variables in the code.
Some programmers use recursive functions depending on the situation. Recursive functions can slow the code down. So you should avoid the use of recursive functions if you can since these reduce the performance of your code.
Optimizing Loops
Let us assume you have a set of numbers, and you are to check if the value is greater than 5 or less than 0. When you write the code, you need to choose the second option. It is easier for the compiler to check if a value is greater than zero than to check which number is greater than 10. In simple words, the statement written below makes the program slower when compared to the second statement in this section.
for( i =0; i<10; i++)
As mentioned, it is best to use this loop instead of the above. If you are not well-versed with C++ programming, this line of code may be difficult for you to read.
for(i=10; i--; )
Similarly, if you find yourself in a situation where you need to pick from <=n or !=0, you should choose the second option since that is faster. For instance, if you want to calculate a factorial, do not try to use a loop since you can use a linear function. If you ever find yourself in a situation where you need to choose between a few loops
or one loop with different tasks, you should choose the second option. This method may help you develop a better performing program.
Optimizing Data Structures
Do you think a data structure affects the performance of your code? It is not easy to answer this question. Since data structures are used everywhere in your code, the answer is difficult to formulate and vague. Let us look at the following example to understand this better. If you are tasked with creating permutations (using the pattern below), you may choose to use a linked list or array.
1, 2, 3, 4,
2, 3, 4, 1,
3, 4, 1, 2,
4, 1, 2, 3,
If you use an array, you can copy the first element in the array and move every other element in the array towards that element. You then need to move the first element in the array to the end of the list. To do this, you need to use multiple operations, and your program will be very slow. If you leave the data in a list, you can develop a program, which will improve the performance of the code. You can also store the data in the form of a tree. This data structure allows you to develop a faster program.
Bear in mind that the type of data structure you use affects the performance of your program. You can solve any problem you have in the code without using arrays or any other data structure.
Sequential or Binary Search?
When you look for a specific object or variable in the code, which method should you opt for – binary or sequential search?
No matter what you do in your code, you always look for some value in a data structure. You may need to look for data in tables, lists, etc. There are two ways to do this:
-
The first method is simple. You create an array and assign
some values to the array. If you want to look for a specific value in the array, you need to start looking at the start of the array until you find the value in the array. If you do not find the value at the start of the array, the compiler moves to the end of the array. This reduces the speed at which the program is compiled.
-
In the second strategy, you need to sort the array before you search for an element in the array. If you do not sort the array before you look for the element, you cannot obtain the results on time. When the array is sorted, the compiler will break it into two parts from the middle. It will then look for the value in either part of the array depending on the values in the sections. When you identify the part where the element may be, you need to divide it through the middle again. You continue to do this until you find the value you are looking for. If you do not, then you know the array does not have the value.
What is the difference between these strategies? When you sort the elements in the array, you may lose some time. Having said that, if you give the compiler time to do this, you will benefit faster from the search. When it comes to choosing between a sequential and binary search, you need to understand the problem before you implement the method you want to use.
Optimizing the Use of Arrays
We looked at arrays in the previous book, and this is one of the basic data structures used in C++. An array contains a list of objects of a similar data type. Every object in an array holds a separate location in the memory.
If you want to learn more about optimizing the work or use of an array, you need to understand the structure of this structure. What does this mean? An array is similar to a pointer, and it points to the elements in the array. You can access array methods using arithmetic pointers or any other type of pointer if needed. Consider the following example:
for(int i=0; i<n; i++) nArray[i]=nSomeValue;
The code below is better than the statement above. Why do you
think this is the case?
for(int* ptrInt = nArray; ptrInt< nArray+n; ptrInt++) *ptrInt=nSomeValue;
The second line of code is better than the first line of code since the operations rely only on pointers. In the example above, we are using pointers to access the values stored in the integer data type. The pointer takes the address of the variable in the memory. In the case of the example, the pointer points to the variable nArray. When we add the increment operator to the variable, the pointer will move from the first element in the array to the next until it reaches the end of the array. If you use the double data type, the compiler will know how far it should move the address.
It is difficult to interpret and read the code using this method, but this is the only way to increase the speed of the program. In simple words, if you do not use a good algorithm, you can increase compiling speed by writing code using the right syntax.
Consider the following example: You have a matrix with the required elements. A matrix is a type of array, and it will be stored in your memory based on the rows. So, how do you think you should access the elements in the array? You should access every element in the matrix row by row. It does not make sense for you to use any other method because you reduce the speed of the program.
It is best to avoid initializing large sections of the memory for only one element. If you know the size of the element, make sure to stick to that size. Do not allot more memory space. You can use the function memset or other commands to allot some space in the memory to the variables used in the code.
Let us assume you want to create an array of characters or strings. Instead of defining the variables or assigning the array to specific variables, it is best to use pointers. You can assign each element in the array to a string, but this would only reduce the speed of the program. The compiler will run the code faster, even if the file is big. If you use the new keyword to create or declare an array in the code, your program will not do well since it will use a lot of memory the
minute you try to run the code. It is for this reason you should use vectors. These objects add some space to your memory, allowing the program to do well.
If you want to move large volumes of data from one section in the memory to another, it is best to use an array of pointers. When you do this, you do not change the original values of the data but only replace the addresses of the objects stored in the memory.