Address: http://blog.csdn.net/hengyunabc/article/details/7170865
A simple accumulate and sum program:
TYPE S=0; for(int i = 0;i < SIZE; i++) { S += a[i]; }
Many people think that this program is not well written, and the compiler cannot generate good assembly code. As a result, there are several "optimizations ":
# Include <iostream> using namespace STD; void main (INT argc, char ** argv) {# define type int # define size struct type * A = new type [size]; for (INT I = 0; I <size; ++ I) {A [I] = I;} // sum. The general version is type S = 0; for (INT I = 0; I <size; I ++) {S + = A [I];} cout <S <Endl; Type S2 = 0; // version 1: The variable I generated in the middle is considered redundant. Instead, the moving pointer type * end = a + size; For (;! = End;) {S2 + = * (a ++) ;}cout <S2 <Endl; // in version 1, A is moved to the end of the array, now move back to the original position a = end-size; // version 2: think there are too many cycles, you can change to reduce the number of cycles type S3 = 0; For (INT I = 0; I <size;) {// only when the size is an even number, S3 + = A [I ++]; S3 + = A [I ++];} cout <S3 <Endl; // Version 3: In version 2, the CPU cannot be executed in disorder and the efficiency is reduced. It should be changed to assembly, place the intermediate results in an independent register. // thanks to menzi11, I realized that the data in the program will prevent the CPU from executing in disorder. // Here, use pseudo Assembly instead of Type S4 = 0; Register Type R1 = 0; Register Type r2 = 0; For (INT I = 0; I <size ;) {// only when the size is an even number, R1 + = A [I ++]; r2 + = A [I ++];} cout <R1 + R2 <Endl ;}
The above versions are reasonable, but these optimizations are based on the assumption that the compiler cannot generate efficient assembly code.
Next let's take a look at the result generated by the compiler (vs2010, release ):
For (INT I = 0; I <size; I ++) {S + = A [I]; 013b1040 mov EBX, dword ptr [eax + 4] // set a [0], a [4], a [8]... add to EBX 013b1043 add ECx, dword ptr [eax-8] // set a [1], a [5], a [9]... add edX, dword ptr to ECx 013b1046 [eax-4] // set a [2], a [6], a [10]... add ESI, dword ptr [eax] // Add a [3], a [7], a [11]... add 013b104b add dword ptr [ebp-4], EBX 013b104e add eax, 10 h 013b1051 dec dword ptr [ebp-8] 013b1054 JNE main + 40 h (13b1040h )} cout <S <Endl; 013b1056 mov eax, dword ptr [ebp-4] 013b1059 add eax, ESI 013b105b add eax, EDX 013b105d mov edX, dword ptr [_ imp_std :: endl (13b204ch)] 013b1063 add ECx, eax // the preceding three add commands add EBX, ECx, EDX, and EDI to ECx, that is, ECx is the cumulative result.
It can be seen that the code generated by the compiler is the best code. It eliminates the intermediate variable I, reduces the number of loops, and eliminates the factors that may cause the CPU to not execute in disorder.
BTW:
Some people may wonder: If the size is not an even number, can the compiler generate similar efficient assembly code?
When size = 9999:
// When size = 9999, the compiler puts the intermediate result in three registers, perfectfor (INT I = 0; I <size; I ++) {S + = A [I]; 01341030 add ECx, DWORD PTR [eax-8] 01341033 add edX, DWORD PTR [eax-4] 01341036 add ESI, DWORD PTR [eax] 01341038 add eax, 0ch 0132133b dec EBX 01320.3c JNE main + 30 h (1341030 h )}
When size = 9997:
// When the size is 9997, it is a little complicated. First, add a [0] to a [9995] To ECx and EDX and then add a [9996] to EDI, add ECx and edi to the edx pressure stack. Call operator <function for (INT I = 0; I <size; I ++) {00d31024 XOR eax, eax S + = A [I]; 00d31026 add ECx, DWORD PTR [ESI + eax * 4] 00d31029 add edX, dword ptr [ESI + eax * 4 + 4] 00d3102d add eax, 2 00d31030 CMP eax, 270ch 00d31035 JL main + 26 h (0d31026h) for (INT I = 0; I <size; I ++) {00d31037 CMP eax, 270dh 00d3103c jge main + 41 h (0d300001h) S + = A [I]; 00d3103e mov EDI, dword ptr [ESI + eax * 4]} cout <S <Endl; 00d31041 mov eax, dword ptr [_ imp_std: Endl (0d3204ch)] 00d31046 add edX, ECX 00d31048 mov ECx, dword ptr [_ imp_std: cout (0d32050h)] 00d3104e push eax 00d3104f add edX, EDI 00d31051 push edX 00d31052 call dword ptr [_ imp_std :: basic_ostream <char, STD: char_traits <char >>:: operator <(0d32048 h)]
The above analysis is the size, that is, if the size of the array is known and the size of the array is unknown, what will happen to the compiler?
TYPE mySum(TYPE* a, int size){TYPE s = 0;for(int i = 0; i < size; ++i){s += a[i];}return s;}
The generated assembly code:
// First accumulate a [0] to a [size-2] type S = 0; 00ed100c xor esi, ESI for (INT I = 0; I <size; ++ I) {00ed100e XOR eax, eax 00ed1010 cmp ebx, 2 00ed1013 JL mysum + 27 h (0ed1027h) 00ed1015 dec ebx s + = A [I]; 00ed1016 add ECx, dword ptr [EDI + eax * 4] // A [0], a [2], a [4]... add 00ed1019 to ECx add edX, dword ptr [EDI + eax * 4 + 4] // A [1], a [3], a [5]... add 00ed101d add eax, 2 00ed1020 CMP eax, EBX 00ed1022 JL mysum + 16 h (0ed1016h) 00ed1024 mov EBX, dword ptr [size] For (INT I = 0; I <size; ++ I) {00ed1027 CMP eax, EBX // determines whether the last element has been added with 00ed1029 jge mysum + 2EH (0ed102eh) S + = A [I]; 00ed102b mov ESI, dword ptr [EDI + eax * 4] // when the size is an odd number, it will be executed. If the size is an even number, 00ed102e add edX, ECx} will not be executed}
Summary: The compilation code generated by the C ++ compiler is equivalent to the best compilation code written by people in most cases.
The key point is that the compiler will constantly upgrade to adapt to new CPU commands, systems, etc. handwritten assembly code is often a tragedy.
Knowing the degree to which the compiler can be optimized and how the compiler can be optimized is a very important quality for programmers.