Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is the efficiency of C++ code operation?

2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article mainly explains "what is the efficiency of C++ code operation". The content in the article is simple and clear, and it is easy to learn and understand. Now please follow the editor's train of thought slowly and deeply. Let's study and learn what is the efficiency of C++ code operation.

This topic will analyze the efficiency of various code operations in C++, including the storage efficiency of different types of variables, the efficiency of using smart pointers, loops, function parameters, virtual functions, arrays, etc., and how to do targeted optimization. or choose a more effective alternative.

The detailed catalogue is shown in the following picture:

Variable storage area:

In C++, what kind of memory variables are stored in depends on how developers declare them. If the data is discontiguous and divided into countless segments and scattered in memory, the Cache hit rate of the data will be reduced. Therefore, it is important to understand how variables are stored.

Stack space

Stack space, usually used to store local variables, function parameters, function return address, registers that need to be recovered before the function returns, and so on. Each time a function is called, the system allocates a stack space to store these things. When the function returns, this stack space is reclaimed, and the program can reuse this stack space the next time the function is called.

Generally speaking, each thread of the program has a fixed size of stack space, how much to use and how much to recycle is just a matter of how much the offset is offset. Stack space is particularly efficient because the same memory space can be used repeatedly, memory can be easily loaded into Cache, and the Cache hit rate is higher.

We can make more use of stack space. All variables are best declared in functions that use them. In some cases, you can declare a variable within curly braces {} to minimize the scope of the variable.

Global or static SPAC

Global variables, which can be accessed by any function, are stored in static space in memory. Variables, floating-point constants, string constants, virtual function tables, and so on declared by the static keyword are all stored in static space.

The advantage of static space is that it can be initialized to the desired value before the program starts. The disadvantage is that even if the variable is used only once, or only in a small part of the program, its memory will be occupied during the whole running process of the program, which will reduce the efficiency of Cache.

Try not to declare a variable as a global variable. If a variable is used by multiple functions, consider using it as a parameter, but passing parameters has overhead. If we want to avoid this kind of overhead, should we declare it as a global variable? In fact, we can also store variables in the class object, and multiple functions access variable members in the class object.

In some cases, consider sharing static and const, such as declaring a static constant lookup table:

Float SomeFunction (int x) {static const float list [] = {1.1,2.2,3.4,4.4,5.5}; return list [x];}

The advantage of this approach is that there is no need to initialize the list each time the function is called. The static declaration means that after the initialization of the first call, initialization is no longer needed later, but this is less efficient because you need to check whether it is called for the first time or has already been called. By adding the const declaration, you can tell the compiler that there is no need to check whether this is the first call. So it's best to add static and const declarations to allow the compiler to better optimize.

String constants and floating-point constants are also often saved in static space, such as:

A = b * 3.5; c = d + 3.5

Here, the constant 3.5 will be stored in static space, and most compilers will recognize that the two constants are the same, so only one constant needs to be stored. All the same constants throughout the program will be connected together to optimize the space occupied by the constants in the program.

Register storage

A register is a small piece of memory in CPU that is used as temporary storage. The speed of accessing variables in registers is very fast, but the number of registers is limited and the number of variables stored is limited. When the compiler optimizes, it automatically selects the most commonly used variables in the function and saves them in the register. The local variables in the program are very suitable to be stored in registers.

The number of registers is limited. In 32-bit X86 systems, there are about 6 integer registers for general purposes and 14 in 64-bit systems. While floating-point variables use different registers, there are about 8 available floating-point registers in 32-bit systems and 16 in 64-bit systems, and there may be more floating-point registers available when more advanced instruction sets are enabled in 64-bit systems.

Volatile

You need to pay special attention to the volatile keyword here, which means that the variable it modifies can be changed by another thread to prevent the compiler from doing some over-optimization. For example:

Volatile int seconds; void DelayFiveSeconds () {seconds = 0; while (seconds

< 5) { // do nothing while seconds count to 5 } } 在本例中,DelayFiveSeconds()将一直等待,直到另一个线程将seconds增加到5。 如果seconds没被声明为volatile,那么编译器可能会进行过度优化,将假定在while循环中seconds保持为0,循环内的任何内容都不能更改该值。循环将是while(0 < 5){},这将是个死循环。 关键字volatile的作用是,确保变量永远存储在内存中,而不是在寄存器中,并阻止对变量的所有优化。 注意volatile不保证原子性,它不会阻止两个线程同时尝试写操作。其他线程增加seconds的同时,试图将seconds设置为0,这样可能会失败。更安全的做法是,一个线程只读取seconds,并等待该值更改。 thread-local存储 C++11中可以使用thread_local关键字来声明线程本地变量,C++11前也有别的方式声明,被修饰的变量对于每个线程都有一份拷贝,保证了线程安全。thread-local存储效率较低,因为它是通过全局指针访问。我们应该尽量避免线程本地存储,可以更多将变量存储在线程自己的栈中,即在线程自己的函数中声明变量。 堆内存 堆内存主要通过操作符new和delete动态分配,或者使用函数malloc和free。如果以随机的顺序分配和释放不同大小的内存,很容易产生内存碎片,分散在堆内存的不同地方,而且频繁分配内存,开销也较大。尽量避免动态分配内存吧,或者用JeMalloc替换一波?或内存池? 整数变量和运算 整型大小 整数中,不同类型可能会有不同的大小,下图总结了不同整型的大小和最大最小值:

The method of declaring integers of a specific size is different on different platforms. We can use the standard header file stdint.h to declare integers of a specific size. This method is also cross-platform and portable.

In most cases, integers are very fast, but if integers are larger than the available register size, they are less efficient. For example, in a 32-bit system, using 64-bit integers is less efficient, especially for multiplication or division.

If the int type is declared but no specific size is specified, the compiler will always choose the most efficient integer size. Smaller integers, such as char, short int, and so on, may be slightly less efficient, and in many cases, the compiler converts these types to integers of the default size and then uses only the lower 8 or 16 bits in the result. In a 64-bit system, there is not much difference between the efficiency of using 32-bit integers and 64-bit integers as long as we do not do division.

When calculating integers, we need to consider the results of the intermediate calculation to see if it will cause an overflow. For example, the expression a=b+c+d, even though b, c, d are all below the integer maximum, may cause integer overflow, which we need to pay attention to all the time.

Signed and unsigned integers

In most cases, there is no difference in speed between signed and unsigned integers, but there are some special cases:

Constant division, when divided by a constant, unsigned integers are more efficient than signed integers, and modular operations are similar.

For most instruction sets, using signed integers to floating point conversions is faster than using unsigned integers.

The overflow behavior of signed and unsigned integers is different: the overflow of unsigned integers produces a low positive result, and the overflow of signed integers is officially undefined. Signed integers, the normal behavior is to convert positive overflows to negative values, but the compiler may do some optimization, which assumes that no overflow will occur.

Conversion between signed and unsigned integers without any overhead. This is just a different interpretation of the same symbol bit. A negative integer is interpreted as a very large positive number when converted to unsigned.

Int a, b; double c; b = (unsigned int) a / 10; / / convert to unsigned integers to do division faster c = a * 2.5; / / signed integers are implicitly converted to double

In the above example, converting a to unsigned makes division faster. Of course, this can only be done when an is definitely not negative. The last line, before multiplying the constant 2.5, implicitly converts a to double, because the latter is double, where an is converted as a signed integer, which is more efficient.

Note that when comparing operations, for example, 3) {.}

We cannot swap the order here, because when I > ARRAYSIZE, the list [I] operation is illegal. Another example:

If (handle! = INVALID_HANDLE_VALUE & & WriteFile (handle,...)) {...}

Similarly, it is impossible for us to change the order here.

Boolean value

Boolean variables are stored as 8-bit integers, with a value of 0 for false and 1 for true. In this sense, Boolean variables are determined by a variety of factors, that is, all operators with Boolean variables as inputs may not only be 0 and 1, but operators with Boolean variables as output can only produce 0 or 1 values. This may be inefficient for Boolean variables as inputs.

For example, for

Bool a, b, c, d; c = a & & b; d = a | b

The compiler may be implemented like this:

Bool a, b, c, d; if (a! = 0) {if (b! = 0) {c = 1;} else {goto cfalse;}} else {cfalse: C = 0;} if (a = = 0) {if (b = = 0) {d = 0;} else {goto dtrue;}} else {dtrue: d = 1;}

Of course, this is not the best way. In the case of misprediction, branching may take a long time. Boolean operations are much more efficient if you can determine that the operands have no values other than 0 and 1. The reason the compiler does not make this assumption is that if variables are not initialized or the source is unknown, they may have other values. If an and b have been initialized to valid values, or if they come from values that produce Boolean output, you can optimize the above code. The optimized code is as follows:

Char a = 0, b = 0, c, d; c = a & b; d = a | b

Here, we can use char (or int) instead of bool so that we can use the bit operators (& and |) instead of Boolean operators (& & and | |). A bitwise operator is a single instruction that takes up only one clock cycle. It works even if the values of an and b are not 0 or the 1 or operator (|). However, if the values of the operands are not 0 and 1, the AND operator (&) and the XOR operator (^) may produce inconsistent results.

Note that there is a pit here, we can't use ~ instead of!, on the contrary, if you make sure that the input is 0 or 1, you can get it by XOR with 1! The value of.

For example:

Bool a, b; b =! a

Can be optimized to:

Char a = 0, b; b = a ^ 1

Pointers and references

Look at the code:

Void FuncA (int * p) {* p = * p + 2;} void FuncB (int & r) {r = r + 2;}

These two paragraphs use pointers and references respectively, and they actually do the same thing. We can look at the compiled code. In fact, their assembly code is exactly the same, and the difference is only a matter of programming style.

The reasons why pointers are superior to references are:

Looking directly at the function body above, it is clear that p is a pointer, but it is not clear whether r is a reference or a simple variable, using pointers to give the reader a better idea of what is going on.

The pointer can be changed for more flexible use, and you can also use the pointer to do arithmetic.

The reasons why references are superior to pointers are:

References are more secure than pointers, because in most cases, references must point to a valid address, and the reference's point is immutable. For pointers, if the pointer is not initialized, the pointer arithmetic calculation is outside the range of valid addresses, or the pointer type is converted to the wrong type, then the pointer may be invalid and lead to a fatal error.

References are useful for copying constructors and overloaded operators.

Function parameters declared as constant references can accept expressions as arguments, while pointers and non-constant references require variables.

Accessing variables using pointers or references may be as fast as direct access. All non-static variables declared in the function are stored on the stack and are actually addressed through the stack pointer. Similarly, all non-static variables declared in the class are accessed through implicit this pointers, so most variables are actually accessed through pointers.

There are also drawbacks to using pointers or references, which require an additional register to hold the value of the pointer or reference, and registers are a scarce resource, especially in 32-bit mode. If the number of registers is insufficient, it must be loaded from memory every time the pointer is used, and the speed will slow down.

Notice that there is a pit in the pointer: that is, the arithmetic of the pointer:

Struct A {int a; int b;}; A * a; a = + + a; a = + + (char*) a

The calculation results of + + an and + + (void*) an are different, the value added in + + an is actually 8, because the size of An is 8, the value added in (void*) an is 1, because the size of (char) is 1.

Function pointer

If the target address is predictable, it takes several more clock cycles to call the function through the function pointer than to call the function directly. If the value of the function pointer is the same as the last time the statement was executed, the target address will be predicted successfully, and if the value of the function pointer changes, the target address is likely to be mispredicted, and the prediction failure will result in a delay of multiple clock cycles.

Intelligent pointer

A smart pointer is an object that behaves like a pointer. There are basically two kinds of intelligent pointers after Category 11. The characteristics of unique_ptr and shared_ptr,unique_ptr are that there is only one pointer that owns the allocated object, and only one object pointer owns the ownership of the object, while the characteristic of shared_ptr is that there can be multiple pointers pointing to the same object together. Obviously, shared_ptr costs more than unique_ptr. You can choose more unique_ptr when choosing smart pointers, and the compiler can optimize it to strip off most or all of the overhead of unique_ptr in a simple case, which is basically the same as using new and delete directly. In general, you can consider using smart pointers in scenarios where an intra-function new requires another intra-function delete. But if you new and delete within the same function, and the body of the function does not have too many branches, you may not need to use smart pointers.

Thank you for your reading, the above is the content of "what is the efficiency of C++ code operation?" after the study of this article, I believe you have a deeper understanding of the efficiency of C++ code operation. Specific use also needs to be verified by practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report