Related
So I'm debugging my DPLL implementation and it's not quite working right so I step through the code line by line in the debugger, it gets to a return statement but the thing is it doesn't return, it just keeps on executing the same function. WTF I thought, am I really seeing this? So I looked at the dissasembly and sure enough one of the return statements jumps to the wrong place. Never have I seen VS generate incorrect code so I'm wondering if I screwed up somewhere but I can't find anything. The jump is incorrect even when compiling with all optimizations off.
This illustrates whats going on.
bool dpll(std::vector<clause> f)
{
unitPropagate(f);
if(checkFalseClause(f))
{
return false; //je dpll+5Fh (0C01D1Fh) <-- Totally wrong jump adress
}
else if(checkAllClausesTrue(f))
{
return true; //jmp dpll+206h (0C01EC6h) <-- this is fine
}
else
{
atom l = chooseLiteral(f); //this is where the jump ends up (0C01D1Fh)
std::vector<clause> a = makeDuplicate(f);
replaceInstancesOf(a, l, true);
std::vector<clause> b = makeDuplicate(f);
replaceInstancesOf(b, l, false);
return dpll(a) | dpll(b);
}
//this is where the jump is supposed to go (0C01EC6h)
}
So my question is, is Visual Studio actually broken or have I misunderstood something? Has anyone run into something like this before?
The version is Visual Studio Enterprise 2015 if that makes a difference, the code is generated for x86_32.
Here's the full dissasembly if anyone's interested:
00C01CC0 push ebp
00C01CC1 mov ebp,esp
00C01CC3 push 0FFFFFFFFh
00C01CC5 push 0C08FF0h
00C01CCA mov eax,dword ptr fs:[00000000h]
00C01CD0 push eax
00C01CD1 sub esp,40h
00C01CD4 mov eax,dword ptr [__security_cookie (0C0D008h)]
00C01CD9 xor eax,ebp
00C01CDB mov dword ptr [ebp-10h],eax
00C01CDE push ebx
00C01CDF push esi
00C01CE0 push eax
00C01CE1 lea eax,[ebp-0Ch]
00C01CE4 mov dword ptr fs:[00000000h],eax
bool dpll(std::vector<clause> f)
00C01CEA lea ecx,[f]
00C01CED mov dword ptr [ebp-4],0
00C01CF4 call unitPropagate (0C01950h)
{
unitPropagate(f);
00C01CF9 lea ecx,[f]
00C01CFC call checkFalseClause (0C01660h)
00C01D01 test al,al
00C01D03 je dpll+4Ch (0C01D0Ch)
00C01D05 xor bh,bh
00C01D07 jmp dpll+206h (0C01EC6h)
if(checkFalseClause(f))
{
return false;
00C01D0C lea ecx,[f]
00C01D0F call checkAllClausesTrue (0C014F0h)
00C01D14 test al,al
00C01D16 je dpll+5Fh (0C01D1Fh)
}
else if(checkAllClausesTrue(f))
00C01D18 mov bh,1
00C01D1A jmp dpll+206h (0C01EC6h)
{
return true;
}
else
00C01D1F lea edx,[f]
00C01D22 lea ecx,[l]
00C01D25 call chooseLiteral (0C013D0h)
00C01D2A mov byte ptr [ebp-4],1
{
atom l = chooseLiteral(f);
00C01D2E lea edx,[f]
00C01D31 xorps xmm0,xmm0
00C01D34 mov dword ptr [ebp-20h],0
00C01D3B lea ecx,[a]
00C01D3E movq mmword ptr [a],xmm0
00C01D43 call makeDuplicate (0C01A30h)
00C01D48 mov byte ptr [ebp-4],2
00C01D4C sub esp,20h
00C01D4F mov esi,esp
00C01D51 mov bl,1
00C01D53 mov dword ptr [ebp-4Ch],esi
00C01D56 lea ecx,[esi+4]
00C01D59 mov al,byte ptr [l]
00C01D5C mov byte ptr [esi],al
00C01D5E mov dword ptr [ecx+14h],0Fh
00C01D65 mov dword ptr [ecx+10h],0
00C01D6C cmp dword ptr [ecx+14h],10h
00C01D70 jb dpll+0B6h (0C01D76h)
00C01D72 mov eax,dword ptr [ecx]
00C01D74 jmp dpll+0B8h (0C01D78h)
00C01D76 mov eax,ecx
00C01D78 push 0FFFFFFFFh
00C01D7A mov byte ptr [eax],0
00C01D7D lea eax,[ebp-44h]
00C01D80 push 0
00C01D82 push eax
00C01D83 call std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign (0C02A80h)
00C01D88 mov al,byte ptr [ebp-2Ch]
00C01D8B lea ecx,[a]
00C01D8E mov byte ptr [esi+1Ch],al
00C01D91 mov dl,bl
00C01D93 mov al,byte ptr [ebp-2Bh]
00C01D96 mov byte ptr [esi+1Dh],al
00C01D99 call replaceInstancesOf (0C017D0h)
00C01D9E xorps xmm0,xmm0
00C01DA1 mov dword ptr [ebp-14h],0
std::vector<clause> a = makeDuplicate(f);
replaceInstancesOf(a, l, true);
00C01DA8 lea edx,[f]
std::vector<clause> a = makeDuplicate(f);
replaceInstancesOf(a, l, true);
00C01DAB movq mmword ptr [b],xmm0
00C01DB0 lea ecx,[b]
00C01DB3 call makeDuplicate (0C01A30h)
00C01DB8 mov esi,esp
00C01DBA mov byte ptr [ebp-4],3
00C01DBE mov dword ptr [ebp-4Ch],esi
00C01DC1 lea ecx,[esi+4]
00C01DC4 mov al,byte ptr [l]
00C01DC7 xor bl,bl
00C01DC9 push 0FFFFFFFFh
00C01DCB mov byte ptr [esi],al
00C01DCD lea eax,[ebp-44h]
00C01DD0 push 0
00C01DD2 mov dword ptr [ecx+14h],0Fh
00C01DD9 mov dword ptr [ecx+10h],0
00C01DE0 push eax
00C01DE1 mov byte ptr [ecx],bl
00C01DE3 call std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign (0C02A80h)
00C01DE8 mov al,byte ptr [ebp-2Ch]
00C01DEB lea ecx,[b]
00C01DEE mov byte ptr [esi+1Ch],al
00C01DF1 mov dl,bl
00C01DF3 mov al,byte ptr [ebp-2Bh]
00C01DF6 mov byte ptr [esi+1Dh],al
00C01DF9 call replaceInstancesOf (0C017D0h)
std::vector<clause> b = makeDuplicate(f);
replaceInstancesOf(b, l, false);
00C01DFE add esp,14h
00C01E01 lea eax,[a]
00C01E04 mov ecx,esp
00C01E06 push eax
00C01E07 call std::vector<std::vector<atom,std::allocator<atom> >,std::allocator<std::vector<atom,std::allocator<atom> > > >::vector<std::vector<atom,std::allocator<atom> >,std::allocator<std::vector<atom,std::allocator<atom> > > > (0C02420h)
00C01E0C call dpll (0C01CC0h)
00C01E11 mov bl,al
00C01E13 mov ecx,esp
00C01E15 lea eax,[b]
00C01E18 push eax
00C01E19 call std::vector<std::vector<atom,std::allocator<atom> >,std::allocator<std::vector<atom,std::allocator<atom> > > >::vector<std::vector<atom,std::allocator<atom> >,std::allocator<std::vector<atom,std::allocator<atom> > > > (0C02420h)
00C01E1E call dpll (0C01CC0h)
00C01E23 mov ecx,dword ptr [b]
00C01E26 mov bh,al
00C01E28 add esp,0Ch
00C01E2B or bh,bl
00C01E2D test ecx,ecx
00C01E2F je dpll+1B4h (0C01E74h)
00C01E31 push dword ptr [ebp-4Ch]
00C01E34 mov edx,dword ptr [ebp-18h]
00C01E37 push ecx
00C01E38 call std::_Destroy_range1<std::allocator<std::vector<atom,std::allocator<atom> > >,std::vector<atom,std::allocator<atom> > *> (0C035E0h)
00C01E3D mov ecx,dword ptr [ebp-14h]
00C01E40 mov eax,2AAAAAABh
00C01E45 mov esi,dword ptr [b]
00C01E48 add esp,8
00C01E4B sub ecx,esi
00C01E4D imul ecx
00C01E4F sar edx,1
00C01E51 mov eax,edx
00C01E53 shr eax,1Fh
00C01E56 add eax,edx
00C01E58 push eax
00C01E59 push esi
00C01E5A call std::_Wrap_alloc<std::allocator<std::vector<atom,std::allocator<atom> > > >::deallocate (0C02D20h)
00C01E5F mov dword ptr [b],0
00C01E66 mov dword ptr [ebp-18h],0
00C01E6D mov dword ptr [ebp-14h],0
00C01E74 mov ecx,dword ptr [a]
00C01E77 test ecx,ecx
00C01E79 je dpll+1FEh (0C01EBEh)
00C01E7B push dword ptr [ebp-4Ch]
00C01E7E mov edx,dword ptr [ebp-24h]
00C01E81 push ecx
00C01E82 call std::_Destroy_range1<std::allocator<std::vector<atom,std::allocator<atom> > >,std::vector<atom,std::allocator<atom> > *> (0C035E0h)
00C01E87 mov ecx,dword ptr [ebp-20h]
00C01E8A mov eax,2AAAAAABh
00C01E8F mov esi,dword ptr [a]
00C01E92 add esp,8
00C01E95 sub ecx,esi
00C01E97 imul ecx
00C01E99 sar edx,1
00C01E9B mov eax,edx
00C01E9D shr eax,1Fh
00C01EA0 add eax,edx
00C01EA2 push eax
00C01EA3 push esi
00C01EA4 call std::_Wrap_alloc<std::allocator<std::vector<atom,std::allocator<atom> > > >::deallocate (0C02D20h)
00C01EA9 mov dword ptr [a],0
00C01EB0 mov dword ptr [ebp-24h],0
00C01EB7 mov dword ptr [ebp-20h],0
00C01EBE lea ecx,[ebp-44h]
00C01EC1 call std::basic_string<char,std::char_traits<char>,std::allocator<char> >::~basic_string<char,std::char_traits<char>,std::allocator<char> > (0C027A0h)
00C01EC6 mov ecx,dword ptr [f]
00C01EC9 test ecx,ecx
00C01ECB je dpll+23Bh (0C01EFBh)
00C01ECD push dword ptr [ebp-4Ch]
00C01ED0 mov edx,dword ptr [ebp+0Ch]
00C01ED3 push ecx
00C01ED4 call std::_Destroy_range1<std::allocator<std::vector<atom,std::allocator<atom> > >,std::vector<atom,std::allocator<atom> > *> (0C035E0h)
00C01ED9 mov ecx,dword ptr [ebp+10h]
00C01EDC mov eax,2AAAAAABh
00C01EE1 mov esi,dword ptr [f]
00C01EE4 add esp,8
00C01EE7 sub ecx,esi
00C01EE9 imul ecx
00C01EEB sar edx,1
00C01EED mov ecx,edx
00C01EEF shr ecx,1Fh
00C01EF2 add ecx,edx
00C01EF4 push ecx
00C01EF5 push esi
00C01EF6 call std::_Wrap_alloc<std::allocator<std::vector<atom,std::allocator<atom> > > >::deallocate (0C02D20h)
00C01EFB mov al,bh
00C01EFD mov ecx,dword ptr [ebp-0Ch]
00C01F00 mov dword ptr fs:[0],ecx
00C01F07 pop ecx
00C01F08 pop esi
00C01F09 pop ebx
00C01F0A mov ecx,dword ptr [ebp-10h]
00C01F0D xor ecx,ebp
00C01F0F call __security_check_cookie (0C080CCh)
00C01F14 mov esp,ebp
00C01F16 pop ebp
00C01F17 ret
The source interleaving is wrong. This is the correct place you want to look at:
00C01CFC call checkFalseClause (0C01660h)
00C01D01 test al,al
00C01D03 je dpll+4Ch (0C01D0Ch)
00C01D05 xor bh,bh
00C01D07 jmp dpll+206h (0C01EC6h)
As you can see, it goes to the expected address if the return value was nonzero.
The part you looked at is actually for the else if(checkAllClausesTrue(f)) and the jump is the one going to the else clause because the compiler negated the condition.
Problem
I currently write a program with large arrays and I am very confused about the processing time of the different arrays. On the one hand I have 7 "smaller" arrays (<=65536 elements) and on the other hand I have 7 large arrays (65536 < elements <= 67108864). Both are integer arrays.
I only want to increment the various elements in a loop. Every loop I increase the elements at the same index of each array.
This means every loop I want to increment all 14 array at the same index. This takes 21 seconds. If I only increment the 7 smaller arrays it takes only 2 seconds and if I only increment the 7 larger arrays it requires 18 seconds!
For a better illustration I have written an example code:
#include <iostream>
#include <time.h>
using namespace std;
int main(int argc,char* argv[])
{
int i;
int* ExampleArray1 = new int [16384]; // 3 "small" arrays (2^14)
int* ExampleArray2 = new int [32768]; // (2^15)
int* ExampleArray3 = new int [65536]; // (2^16)
for(i=0 ; i<16384 ; i++) {ExampleArray1[i] = 0;}
for(i=0 ; i<32768 ; i++) {ExampleArray2[i] = 0;}
for(i=0 ; i<65536 ; i++) {ExampleArray3[i] = 0;}
int* ExampleArray4 = new int [16777216]; // 3 large arrays (2^24)
int* ExampleArray5 = new int [33554432]; // (2^25)
int* ExampleArray6 = new int [67108864]; // (2^26)
for(i=0 ; i<16777216 ; i++) {ExampleArray4[i] = 0;}
for(i=0 ; i<33554432 ; i++) {ExampleArray5[i] = 0;}
for(i=0 ; i<67108864 ; i++) {ExampleArray6[i] = 0;}
int until;
clock_t start,stop;
cout << "Until: "; cin >> until;
start = clock();
for(i=0 ; i<until; i++)
{
ExampleArray1[1]++;
ExampleArray2[1]++;
ExampleArray3[1]++;
}
stop = clock();
cout << "Time: " << static_cast<float>(stop-start)/CLOCKS_PER_SEC << " sec.\n";
start = clock();
for(i=0 ; i<until; i++)
{
ExampleArray4[1]++;
ExampleArray5[1]++;
ExampleArray6[1]++;
}
stop = clock();
cout << "Time: " << static_cast<float>(stop-start)/CLOCKS_PER_SEC << " sec.\n";
delete[] ExampleArray1; ExampleArray1 = NULL;
delete[] ExampleArray2; ExampleArray2 = NULL;
delete[] ExampleArray3; ExampleArray3 = NULL;
delete[] ExampleArray4; ExampleArray4 = NULL;
delete[] ExampleArray5; ExampleArray5 = NULL;
delete[] ExampleArray6; ExampleArray6 = NULL;
getchar();
return 0;
}
Another confusing point is the time difference between the compiling modes.
As "until" I entered 1 billion.
Output -> Debug-Mode (standard):
Time: 6 sec.
Time: 26 sec.
Output -> Release-Mode (Fully Optimization enabled[/Ox] (I believe this is standard)):
Time: 34 sec.
Time: 47 sec.
Output -> Release-Mode (Optimization in property sheet disabled[/Od]):
Time: 25 sec.
Time: 25 sec.
But if I change the code a little bit and replace the increment by an assignment the output is a bit different:
[...]
int until;
int value;
clock_t start,stop;
cout << "Until: "; cin >> until;
cout << "Value: "; cin >> value;
start = clock();
for(i=0 ; i<until; i++)
{
ExampleArray1[1]=value;
ExampleArray2[1]=value;
ExampleArray3[1]=value;
}
stop = clock();
cout << "Time: " << static_cast<float>(stop-start)/CLOCKS_PER_SEC << " sec.\n";
start = clock();
for(i=0 ; i<until; i++)
{
ExampleArray4[1]=value;
ExampleArray5[1]=value;
ExampleArray6[1]=value;
}
stop = clock();
cout << "Time: " << static_cast<float>(stop-start)/CLOCKS_PER_SEC << " sec.\n";
[...]
Output -> Debug-Mode (standard):
Time: 4 sec.
Time: 28 sec.
Output -> Release-Mode (Fully Opitmization enabled[/Ox]):
Time: 38 sec.
Time: 38 sec.
Output -> Release-Mode (Optimization disabled[/Od]):
Time: 24 sec.
Time: 28 sec.
I thought that the access time of an array is always the same.
I'm using Visual Studio 2012 32-Bit Express and Windows 7 64-Bit.
I have everything tested several times. The times were approximately the same (but up to +/- 6 seconds) and I have taken a rounded average.
My questions are:
Questions
Why the times differs so much with the various array sizes ? -> Is this avoidable without splitting the large arrays ?
Why the debug mode requires less time than the release mode ?
What can be the reasons that the release mode without optimization is faster than with optimization ?
Thanks in advance.
Edit:
System data:
Prozessor: AMD Phenom II X6 1045T (6 x 2,7GHz)
L1 Cache: 768 KB (2x6x64 KB) 2-way | L2 Cache: 3072 KB (6x512 KB) 16-way | L3 Cache: 6 MB 48-way associative
RAM: 2x 4GB DDR3
SSD hard drive
Disassembly (of the first code with incrementation)
Disassembly - VS 2012 (Express)
The disassembly in debug mode is in each case (large and small arrays) the same.
Array1 for example and the header of the first loop:
for (i = 0; i<until; i++)
01275FEA mov dword ptr [i],0
01275FF1 jmp main+21Ch (01275FFCh)
01275FF3 mov eax,dword ptr [i]
01275FF6 add eax,1
01275FF9 mov dword ptr [i],eax
01275FFC mov eax,dword ptr [i]
01275FFF cmp eax,dword ptr [until]
01276002 jge main+283h (01276063h)
{
ExampleArray1[1]++;
01276004 mov eax,4
01276009 shl eax,0
0127600C mov ecx,dword ptr [ExampleArray1]
0127600F mov edx,dword ptr [ecx+eax]
01276012 add edx,1
01276015 mov eax,4
0127601A shl eax,0
0127601D mov ecx,dword ptr [ExampleArray1]
01276020 mov dword ptr [ecx+eax],edx
In release mode (with optimization) the code of the first loop is shorter than the code of the second and the second header is twice in the disassembly (could this be the culprit ?). First loop:
for(i=0 ; i<until; i++)
00AD10D9 xor ecx,ecx
00AD10DB mov dword ptr [esp+24h],eax
00AD10DF cmp dword ptr [esp+10h],ecx
00AD10E3 jle main+0F5h (0AD10F5h)
{
ExampleArray1[1]++;
00AD10E5 inc dword ptr [esi+4]
ExampleArray2[1]++;
00AD10E8 inc dword ptr [ebx+4]
ExampleArray3[1]++;
00AD10EB inc dword ptr [edi+4]
00AD10EE inc ecx
00AD10EF cmp ecx,dword ptr [esp+10h]
00AD10F3 jl main+0E5h (0AD10E5h)
Second loop:
for(i=0 ; i<until; i++)
003B13B4 xor ecx,ecx
003B13B6 mov dword ptr [esp+24h],eax
003B13BA cmp dword ptr [esp+10h],ecx
003B13BE jle main+174h (03B13E4h)
for(i=0 ; i<until; i++)
003B13C0 mov eax,dword ptr [esp+1Ch]
003B13C4 mov edx,dword ptr [esp+18h]
003B13C8 mov edi,dword ptr [esp+20h]
003B13CC lea esp,[esp]
{
ExampleArray4[1]++;
003B13D0 inc dword ptr [eax+4]
ExampleArray5[1]++;
003B13D3 inc dword ptr [edx+4]
ExampleArray6[1]++;
003B13D6 inc dword ptr [edi+4]
003B13D9 inc ecx
003B13DA cmp ecx,dword ptr [esp+10h]
003B13DE jl main+160h (03B13D0h)
003B13E0 mov edi,dword ptr [esp+14h]
}
Thats the disassembly of release without opitmization -> First loop:
for(i=0 ; i<until; i++)
001A17A2 mov dword ptr [i],0
001A17A9 jmp main+1C4h (01A17B4h)
001A17AB mov edx,dword ptr [i]
001A17AE add edx,1
001A17B1 mov dword ptr [i],edx
001A17B4 mov eax,dword ptr [i]
001A17B7 cmp eax,dword ptr [until]
001A17BA jge main+22Bh (01A181Bh)
{
ExampleArray1[1]++;
001A17BC mov ecx,4
001A17C1 shl ecx,0
001A17C4 mov edx,dword ptr [ExampleArray1]
001A17C7 mov eax,dword ptr [edx+ecx]
001A17CA add eax,1
001A17CD mov ecx,4
001A17D2 shl ecx,0
001A17D5 mov edx,dword ptr [ExampleArray1]
001A17D8 mov dword ptr [edx+ecx],eax
ExampleArray2[1]++;
001A17DB mov eax,4
001A17E0 shl eax,0
001A17E3 mov ecx,dword ptr [ExampleArray2]
001A17E6 mov edx,dword ptr [ecx+eax]
001A17E9 add edx,1
001A17EC mov eax,4
001A17F1 shl eax,0
001A17F4 mov ecx,dword ptr [ExampleArray2]
001A17F7 mov dword ptr [ecx+eax],edx
ExampleArray3[1]++;
001A17FA mov edx,4
001A17FF shl edx,0
001A1802 mov eax,dword ptr [ExampleArray3]
001A1805 mov ecx,dword ptr [eax+edx]
001A1808 add ecx,1
001A180B mov edx,4
001A1810 shl edx,0
001A1813 mov eax,dword ptr [ExampleArray3]
001A1816 mov dword ptr [eax+edx],ecx
Second loop:
for(i=0 ; i<until; i++)
001A186F mov dword ptr [i],0
001A1876 jmp main+291h (01A1881h)
001A1878 mov eax,dword ptr [i]
001A187B add eax,1
001A187E mov dword ptr [i],eax
001A1881 mov ecx,dword ptr [i]
001A1884 cmp ecx,dword ptr [until]
001A1887 jge main+2F8h (01A18E8h)
{
ExampleArray4[1]++;
001A1889 mov edx,4
001A188E shl edx,0
001A1891 mov eax,dword ptr [ExampleArray4]
001A1894 mov ecx,dword ptr [eax+edx]
001A1897 add ecx,1
001A189A mov edx,4
001A189F shl edx,0
001A18A2 mov eax,dword ptr [ExampleArray4]
{
ExampleArray4[1]++;
001A18A5 mov dword ptr [eax+edx],ecx
ExampleArray5[1]++;
001A18A8 mov ecx,4
001A18AD shl ecx,0
001A18B0 mov edx,dword ptr [ExampleArray5]
001A18B3 mov eax,dword ptr [edx+ecx]
001A18B6 add eax,1
001A18B9 mov ecx,4
001A18BE shl ecx,0
001A18C1 mov edx,dword ptr [ExampleArray5]
001A18C4 mov dword ptr [edx+ecx],eax
ExampleArray6[1]++;
001A18C7 mov eax,4
001A18CC shl eax,0
001A18CF mov ecx,dword ptr [ExampleArray6]
001A18D2 mov edx,dword ptr [ecx+eax]
001A18D5 add edx,1
001A18D8 mov eax,4
001A18DD shl eax,0
001A18E0 mov ecx,dword ptr [ExampleArray6]
001A18E3 mov dword ptr [ecx+eax],edx
}
Disassembly - VS 2013 (Express)
Debug mode: -same as in VS 2012-
In release mode (with optimization) it looks a bit different ->
First loop:
for (i = 0; i<until; i++)
01391384 xor ecx,ecx
01391386 mov dword ptr [esp+10h],eax
0139138A cmp dword ptr [esp+20h],ecx
0139138E jle main+100h (013913A0h)
{
ExampleArray1[1]++;
01391390 inc dword ptr [esi+4]
01391393 inc ecx
ExampleArray2[1]++;
01391394 inc dword ptr [ebx+4]
ExampleArray3[1]++;
01391397 inc dword ptr [edi+4]
0139139A cmp ecx,dword ptr [esp+20h]
0139139E jl main+0F0h (01391390h)
}
Second loop (loop header is larger ? ):
for (i = 0; i<until; i++)
013913EF xor ecx,ecx
013913F1 mov dword ptr [esp+10h],eax
013913F5 cmp dword ptr [esp+20h],ecx
013913F9 jle main+17Bh (0139141Bh)
013913FB mov eax,dword ptr [esp+18h]
013913FF mov edx,dword ptr [esp+0Ch]
01391403 mov edi,dword ptr [esp+1Ch]
{
ExampleArray4[1]++;
01391407 inc dword ptr [eax+4]
0139140A inc ecx
ExampleArray5[1]++;
0139140B inc dword ptr [edx+4]
ExampleArray6[1]++;
0139140E inc dword ptr [edi+4]
01391411 cmp ecx,dword ptr [esp+20h]
01391415 jl main+167h (01391407h)
01391417 mov edi,dword ptr [esp+14h]
}
Release mode (without optimization) -> First loop:
for (i = 0; i<until; i++)
00DC182C mov dword ptr [i],0
00DC1833 jmp main+1CEh (0DC183Eh)
00DC1835 mov edx,dword ptr [i]
00DC1838 add edx,1
00DC183B mov dword ptr [i],edx
00DC183E mov eax,dword ptr [i]
00DC1841 cmp eax,dword ptr [until]
00DC1844 jge main+235h (0DC18A5h)
{
ExampleArray1[1]++;
00DC1846 mov ecx,4
00DC184B shl ecx,0
00DC184E mov edx,dword ptr [ExampleArray1]
00DC1851 mov eax,dword ptr [edx+ecx]
00DC1854 add eax,1
00DC1857 mov ecx,4
00DC185C shl ecx,0
00DC185F mov edx,dword ptr [ExampleArray1]
00DC1862 mov dword ptr [edx+ecx],eax
ExampleArray2[1]++;
00DC1865 mov eax,4
00DC186A shl eax,0
00DC186D mov ecx,dword ptr [ExampleArray2]
00DC1870 mov edx,dword ptr [ecx+eax]
00DC1873 add edx,1
00DC1876 mov eax,4
00DC187B shl eax,0
00DC187E mov ecx,dword ptr [ExampleArray2]
00DC1881 mov dword ptr [ecx+eax],edx
ExampleArray3[1]++;
00DC1884 mov edx,4
00DC1889 shl edx,0
00DC188C mov eax,dword ptr [ExampleArray3]
00DC188F mov ecx,dword ptr [eax+edx]
00DC1892 add ecx,1
00DC1895 mov edx,4
00DC189A shl edx,0
00DC189D mov eax,dword ptr [ExampleArray3]
00DC18A0 mov dword ptr [eax+edx],ecx
}
Second loop:
for (i = 0; i<until; i++)
00DC18F9 mov dword ptr [i],0
00DC1900 jmp main+29Bh (0DC190Bh)
00DC1902 mov eax,dword ptr [i]
00DC1905 add eax,1
00DC1908 mov dword ptr [i],eax
00DC190B mov ecx,dword ptr [i]
00DC190E cmp ecx,dword ptr [until]
00DC1911 jge main+302h (0DC1972h)
{
ExampleArray4[1]++;
00DC1913 mov edx,4
{
ExampleArray4[1]++;
00DC1918 shl edx,0
00DC191B mov eax,dword ptr [ExampleArray4]
00DC191E mov ecx,dword ptr [eax+edx]
00DC1921 add ecx,1
00DC1924 mov edx,4
00DC1929 shl edx,0
00DC192C mov eax,dword ptr [ExampleArray4]
00DC192F mov dword ptr [eax+edx],ecx
ExampleArray5[1]++;
00DC1932 mov ecx,4
00DC1937 shl ecx,0
00DC193A mov edx,dword ptr [ExampleArray5]
00DC193D mov eax,dword ptr [edx+ecx]
00DC1940 add eax,1
00DC1943 mov ecx,4
00DC1948 shl ecx,0
00DC194B mov edx,dword ptr [ExampleArray5]
00DC194E mov dword ptr [edx+ecx],eax
ExampleArray6[1]++;
00DC1951 mov eax,4
00DC1956 shl eax,0
00DC1959 mov ecx,dword ptr [ExampleArray6]
00DC195C mov edx,dword ptr [ecx+eax]
00DC195F add edx,1
00DC1962 mov eax,4
00DC1967 shl eax,0
00DC196A mov ecx,dword ptr [ExampleArray6]
00DC196D mov dword ptr [ecx+eax],edx
}
Edit: Well, I've done what I can. I'm more confused than before. To see the disassembly I have set a breakpoint in my code and pressed right mousbutton->Go to disassembly. Then I have seen the disassembly. In all this codes I have set the breakpoint at "ExampleArray2[1]++" (line 34).
Now, I have noticed that the disassembly changes if I set the breakpoint at another array.
Why ... is Visual Studio broken or something like this or is this normal?
This Question is already really big. I have uploaded the disassembly of the clocks and the hard coded until at pastebin: http://pastebin.com/pxgegzZ6
IMPORTANT: I have made a hopefully helpful discovery in release mode. If I press the right mouse button in the second loop and press "run until cursor is reached" or set the cursor in the second loop and press Ctrl+F10 than the first loop only require 2.6 seconds ! If I set the cursor at the end of the code the first loop need 2.5-2.7 seconds but the second is as slow as before. In debug mode the times don't change.
This looks to me like a cache issue.
If, instead of
for(i=0 ; i<until; i++)
{
ExampleArray4[1]++;
ExampleArray5[1]++;
ExampleArray6[1]++;
}
you did
for(i=0 ; i<until; i++) ExampleArray4[1]++;
for(i=0 ; i<until; i++) ExampleArray5[1]++;
for(i=0 ; i<until; i++) ExampleArray6[1]++;
I predict it would be much faster.
The reason the cache is an issue is because every time you switch from working on one array to the next, a different location must be drawn from cache. By switching array so often you have a lot of cache misses. A similar issue occurs if you walk across rows of a 2d array rather than columns. Presumably, this doesn't affect your smaller arrays because they can all fit in cache.
EDIT
I switched from memcmp to a home brewed 13 byte compare function and the homebrew doesnt have the extra instructions. So all I can guess is that the extra assembly is just a flaw in the optimizer.
if (!EQ13(&ti, &m_ti)) { // in 2014, memcmp was not being optimzied here
000007FEF91B2CFE mov rdx,qword ptr [rsp]
000007FEF91B2D02 movzx eax,byte ptr [rsp+0Ch]
000007FEF91B2D07 mov ecx,dword ptr [rsp+8]
000007FEF91B2D0B cmp rdx,qword ptr [r10+28h]
000007FEF91B2D0F jne TSccIter::SetTi+9Dh (7FEF91B2D1Dh)
000007FEF91B2D11 cmp ecx,dword ptr [r10+30h]
000007FEF91B2D15 jne TSccIter::SetTi+9Dh (7FEF91B2D1Dh)
000007FEF91B2D17 cmp al,byte ptr [r10+34h]
000007FEF91B2D1B je TSccIter::SetTi+0B1h (7FEF91B2D31h)
My homebrew isn't perfect in this case since it does 3 movs at the start even though it is unlikely to ever check past the first mov. I need to work on that part.
ORIGINAL QUESTION
Here is asm code from msvc 2010 showing how it can optimze a small, fixed-sized memcmp (in this case, 13 bytes). I've seen this type of optimization a lot in our code, but never with the last 6 lines. Can anyone tell me why the last 6 lines of assembly are there? TransferItem is 13 bytes so that explains the QWORD, DWORD, then BYTE cmps.
struct TransferItem {
char m_szCxrMkt1[3];
char m_szCxrOp1[3];
char m_chDelimiter;
char m_szCxrMkt2[3];
char m_szCxrOp2[3];
};
...
if (memcmp(&ti, &m_ti, sizeof(TransferItem))) {
2B8E lea rax,[rsp]
2B92 mov rdx,qword ptr [rax]
2B95 cmp rdx,qword ptr [r10+28h]
2B99 jne TSccIter::SetTi+0A2h (7FEF9302BB2h)
2B9B mov edx,dword ptr [rax+8]
2B9E cmp edx,dword ptr [r10+30h]
2BA2 jne TSccIter::SetTi+0A2h (7FEF9302BB2h)
2BA4 movzx edx,byte ptr [rax+0Ch]
2BA8 cmp dl,byte ptr [r10+34h]
2BAC jne TSccIter::SetTi+0A2h (7FEF9302BB2h)
2BAE xor eax,eax
2BB0 jmp TSccIter::SetTi+0A7h (7FEF9302BB7h)
2BB2 sbb eax,eax
2BB4 sbb eax,0FFFFFFFFh
2BB7 test eax,eax
2BB9 je TSccIter::SetTi+0CCh (7FEF9302BDCh)
Also what is the point of xor eax,eax which we know will be zero and then testing that for that known to be zero on line 2bb7?
Here is the whole function
// fWildCard means match certain fields to '**' in the db
// szCxrMkt1,2 are required and cannot be null, ' ', or '\0\0'.
// szCxrOp1,2 can be null, ' ', or '\0\0'.
TSccIter& SetTi(bool fWildCard, LPCSTR szCxrMkt1, LPCSTR szCxrOp1, LPCSTR szCxrMkt2, LPCSTR szCxrOp2) {
if (m_fSkipSet)
return *this;
m_iSid = -1; // resets the iterator to search from the start
// Pad the struct to 16 bytes so we can clear it with 2 QWORDS
// We use a temp, ti, to detect if the new transferitem has changed
class TransferItemPadded : public TransferItem {
char padding[16 - sizeof(TransferItem)]; // get us to 16 bytes
} ti;
U8(&ti) = U8(BUMP(&ti, 8)) = 0x2020202020202020; // 8 spaces
// copy in the params
CPY2(ti.m_szCxrMkt1, szCxrMkt1);
if (szCxrOp1 && *szCxrOp1)
CPY2(ti.m_szCxrOp1, szCxrOp1);
ti.m_chDelimiter = (fWildCard) ? '*' : ':'; // this controls wild card matching
CPY2(ti.m_szCxrMkt2, szCxrMkt2);
if (szCxrOp2 && *szCxrOp2)
CPY2(ti.m_szCxrOp2, szCxrOp2);
// see if different
if (memcmp(&ti, &m_ti, sizeof(TransferItem))) {
memcpy(&m_ti, &ti, sizeof(TransferItem));
m_fQryChanged = true;
}
return *this;
}
typedef unsigned __int64 U8;
#define CPY2(a,b) ((*(WORD*)a) = (*(WORD*)b))
And here's the whole asm
TSccIter& SetTi(bool fWildCard, LPCSTR szCxrMkt1, LPCSTR szCxrOp1, LPCSTR szCxrMkt2, LPCSTR szCxrOp2) {
2B10 sub rsp,18h
if (m_fSkipSet)
2B14 cmp byte ptr [rcx+0EAh],0
2B1B mov r10,rcx
return *this;
2B1E jne TSccIter::SetTi+0CCh (7FEF9302BDCh)
m_iSid = -1;
class TransferItemPadded : public TransferItem {
char padding[16 - sizeof(TransferItem)];
} ti;
U8(&ti) = U8(BUMP(&ti, 8)) = 0x2020202020202020;
2B24 mov rax,2020202020202020h
2B2E mov byte ptr [rcx+36h],0FFh
2B32 mov qword ptr [rsp],rax
2B36 mov qword ptr [rsp+8],rax
CPY2(ti.m_szCxrMkt1, szCxrMkt1);
2B3B movzx eax,word ptr [r8]
2B3F mov word ptr [rsp],ax
if (szCxrOp1 && *szCxrOp1)
2B43 test r9,r9
2B46 je TSccIter::SetTi+47h (7FEF9302B57h)
2B48 cmp byte ptr [r9],0
2B4C je TSccIter::SetTi+47h (7FEF9302B57h)
CPY2(ti.m_szCxrOp1, szCxrOp1);
2B4E movzx eax,word ptr [r9]
2B52 mov word ptr [rsp+3],ax
ti.m_chDelimiter = (fWildCard) ? '*' : ':';
2B57 mov eax,3Ah
2B5C mov ecx,2Ah
2B61 test dl,dl
2B63 cmovne eax,ecx
2B66 mov byte ptr [rsp+6],al
CPY2(ti.m_szCxrMkt2, szCxrMkt2);
2B6A mov rax,qword ptr [szCxrMkt2]
2B6F movzx ecx,word ptr [rax]
if (szCxrOp2 && *szCxrOp2)
2B72 mov rax,qword ptr [szCxrOp2]
2B77 mov word ptr [rsp+7],cx
2B7C test rax,rax
2B7F je TSccIter::SetTi+7Eh (7FEF9302B8Eh)
2B81 cmp byte ptr [rax],0
2B84 je TSccIter::SetTi+7Eh (7FEF9302B8Eh)
CPY2(ti.m_szCxrOp2, szCxrOp2);
2B86 movzx eax,word ptr [rax]
2B89 mov word ptr [rsp+0Ah],ax
if (memcmp(&ti, &m_ti, sizeof(TransferItem))) {
2B8E lea rax,[rsp]
2B92 mov rdx,qword ptr [rax]
2B95 cmp rdx,qword ptr [r10+28h]
2B99 jne TSccIter::SetTi+0A2h (7FEF9302BB2h)
2B9B mov edx,dword ptr [rax+8]
2B9E cmp edx,dword ptr [r10+30h]
2BA2 jne TSccIter::SetTi+0A2h (7FEF9302BB2h)
2BA4 movzx edx,byte ptr [rax+0Ch]
2BA8 cmp dl,byte ptr [r10+34h]
2BAC jne TSccIter::SetTi+0A2h (7FEF9302BB2h)
2BAE xor eax,eax
2BB0 jmp TSccIter::SetTi+0A7h (7FEF9302BB7h)
2BB2 sbb eax,eax
2BB4 sbb eax,0FFFFFFFFh
2BB7 test eax,eax
2BB9 je TSccIter::SetTi+0CCh (7FEF9302BDCh)
memcpy(&m_ti, &ti, sizeof(TransferItem));
2BBB mov rax,qword ptr [rsp]
m_fQryChanged = true;
2BBF mov byte ptr [r10+0E9h],1
2BC7 mov qword ptr [r10+28h],rax
2BCB mov eax,dword ptr [rsp+8]
2BCF mov dword ptr [r10+30h],eax
2BD3 movzx eax,byte ptr [rsp+0Ch]
2BD8 mov byte ptr [r10+34h],al
}
return *this;
2BDC mov rax,r10
}
2bb7 can be reached by different code paths: via taken jumps at 2b99, 2ba2 and 2bac, as well as directly when none of the conditional jumps is taken. The xor eax,eax is only executed at the last path, and it ensures that eax is 0 - which is apparently not the case otherwise.
The last 6 lines return the value in eax == 0 for a match, and also set the SF and ZF condition codes.
test eax, eax will test whether eax AND eax == 0. The following je will jump if zero.
And xor eax, eax is an efficient way to encode "eax = 0". It is more efficient than mov eax, 0
EDIT: Initially misread the question. It looks like something will happen at "TSccIter::SetTi+0A7h" which should change the value?
Also, the SBB trick to replicate the carry(2BB2-2BB4) is explained here:
http://compgroups.net/comp.lang.asm.x86/trick-with-sbb-instruction/20164
I'm using Visual Studio 2010. I wrote a program that does a simple Binary Search algorithm. I'm trying to convert it into assemble code. I used the Disassembler to get the assembly code. I'm trying to paste it into _asm. I've tried so many ways and it's just not working.
I tried
_asm(" . . . .");
_asm( );
_asm{ } <--- -Currently going with this way for c++. seems to work well.
seen somewhere online people saying put '\' at the end of each line. That hasn't worked for me.
Here's the code. I'll comment where the errors are. well, I have 13 as of now. Some I won't list because they're the same as other errors. Once I fix one or 2 I should be able to fix them all. The orignal c++ code for the function is also in there.It's commented out.
bool binarySearch(int searchNum,int myArray[],int size){
_asm{
push ebp
mov ebp,esp
sub esp,0F0h
push ebx
push esi
push edi
lea edi,[ebp-0F0h]
mov ecx,3Ch
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
// 217: int first=0,last=size-1,middle;
mov dword ptr [first],0
mov eax,dword ptr [size] // ERROR! Error 2 error C2400: inline assembler syntax error in 'second operand'; found ']'
sub eax,1
mov dword ptr [last],eax
// 218: bool found = false;
mov byte ptr [found],0
// 220: while (first <= last)
mov eax,dword ptr [first]
cmp eax,dword ptr [last]
jg binarySearch+80h (0B51970h) //ERROR! Error 4 error C2400: inline assembler syntax error in 'second operand'; found '('
// 222: middle = (first + last)/2;
mov eax,dword ptr [first] ; // Error 5 error C2400: inline assembler syntax error in 'opcode'; found '('
add eax,dword ptr [last] ;
cdq
sub eax,edx
sar eax,1
mov dword ptr [middle],eax
// 224: if(searchNum > myArray[middle])
mov eax,dword ptr [middle]
mov ecx,dword ptr [myArray]
mov edx,dword ptr [searchNum]
cmp edx,dword ptr [ecx+eax*4]
jle binarySearch+61h (0B51951h) // Error 8 error C2400: inline assembler syntax error in 'opcode'; found '('
// 226: first = middle +1;
mov eax,dword ptr [middle]
add eax,1
mov dword ptr [first],eax
jmp binarySearch+7Eh (0B5196Eh)
// 228: else if (searchNum < myArray[middle])
mov eax,dword ptr [middle]
mov ecx,dword ptr [myArray]
mov edx,dword ptr [searchNum]
cmp edx,dword ptr [ecx+eax*4]
jge binarySearch+7Ah (0B5196Ah)
// 230: last = middle -1;
mov eax,dword ptr [middle]
sub eax,1
mov dword ptr [last],eax
// 232: else
jmp binarySearch+7Eh (0B5196Eh) // Error 18 error C2400: inline assembler syntax error in 'second operand'; found '('
// 233: return true;
mov al,1 // Error 19 error C2400: inline assembler syntax error in 'opcode'; found '('
jmp binarySearch+82h (0B51972h)
jmp binarySearch+32h (0B51922h) // Error 22 error C2400: inline assembler syntax error in 'opcode'; found '('
// 236: return false;
xor al,al
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
};
/*
int first=0,last=size-1,middle;
bool found = false;
while (first <= last)
{
middle = (first + last)/2;
if(searchNum > myArray[middle])
{
first = middle +1;
}
else if (searchNum < myArray[middle])
{
last = middle -1;
}
else
return true;
}
return false;
*/
}
Here is the (almost 1:1) working code you posted for a standalone assembly.
binsearch.cpp
extern "C"
{
bool BinSearch(int searchNum, int myArray[], int arraySize);
};
// This is the inlined version.
bool BinSearchInline(int searchNum, int myArray[], int arraySize)
{
int middle;
int first;
int last;
char found;
_asm
{
push ebx
push esi
push edi
mov first,0
mov eax, arraySize
sub eax,1
mov last ,eax
mov found,0
LocalLoop:
mov eax, first
cmp eax, last
jg NotFound
mov eax, first
add eax, last
cdq
sub eax,edx
sar eax,1
mov middle,eax
mov eax,middle
mov ecx,myArray
mov edx,searchNum
cmp edx, dword ptr [ecx+eax*4]
jle MaybeLower
mov eax, middle
add eax,1
mov first, eax
jmp WhileLoop
MaybeLower:
mov eax, middle
mov ecx, myArray
mov edx, searchNum
cmp edx,dword ptr [ecx+eax*4]
jge Found
mov eax, middle
sub eax,1
mov last, eax
jmp WhileLoop
Found:
mov al,1
jmp Done
WhileLoop:
jmp LocalLoop
NotFound:
xor al,al
Done:
pop edi
pop esi
pop ebx
};
}
int main(int argc, char*arg[])
{
int testvalues[7];
for(int i = 0; i < 7; i++)
testvalues[i] = i;
bool b = BinSearch(8, testvalues, 7); // false, value not in array
b = BinSearch(3, testvalues, 7); // true, value is in array.
b = BinSearchInline(8, testvalues, 7); // false
b = BinSearchInline(3, testvalues, 7); // true
return 0;
}
binsearch.asm
.486
.model flat, C
option casemap :none
.code
BinSearch PROC, searchNum:DWORD, myArray:PTR DWORD, arraySize:DWORD
LOCAL first:DWORD
LOCAL middle:DWORD
LOCAL last:DWORD
LOCAL found:BYTE
push ebx
push esi
push edi
; This block is only for debugging stack errors and should be removed.
; lea edi,[ebp-0F0h]
; mov ecx,3Ch
; mov eax,0CCCCCCCCh
; rep stos dword ptr es:[edi]
mov dword ptr [first],0
mov eax,dword ptr [arraySize]
sub eax,1
mov dword ptr [last],eax
mov byte ptr [found],0 ; not even used.
##Loop:
mov eax,dword ptr [first]
cmp eax,dword ptr [last]
jg ##NotFound
mov eax,dword ptr [first]
add eax,dword ptr [last]
cdq
sub eax,edx
sar eax,1
mov dword ptr [middle],eax
mov eax,dword ptr [middle]
mov ecx,dword ptr [myArray]
mov edx,dword ptr [searchNum]
cmp edx,dword ptr [ecx+eax*4]
jle ##MaybeLower
mov eax,dword ptr [middle]
add eax,1
mov dword ptr [first],eax
jmp ##WhileLoop
##MaybeLower:
mov eax,dword ptr [middle]
mov ecx,dword ptr [myArray]
mov edx,dword ptr [searchNum]
cmp edx,dword ptr [ecx+eax*4]
jge ##Found
mov eax,dword ptr [middle]
sub eax,1
mov dword ptr [last],eax
jmp ##WhileLoop
##Found:
mov al,1
jmp ##Done
##WhileLoop:
jmp ##Loop
##NotFound:
xor al,al
##Done:
pop edi
pop esi
pop ebx
ret
BinSearch ENDP
END
When I tried to create my own alternative to classic array, I saw, that into disassembly code added one instruction: mov edx,dword ptr [myarray]. Why this additional instruction was added?
I want to use my functionality of my alternative, but do not want to lose performance! How to resolve this question? Every processor cycle is important for this application.
For example:
for (unsigned i = 0; i < 10; ++i)
{
array1[i] = i;
array2[i] = 10 - i;
}
Assembly (classic int arrays):
mov edx, dword ptr [ebp-480h]
mov eax, dword ptr [ebp-480h]
mov dword ptr array1[edx*4], eax
mov ecx, 10
sub ecx, dword ptr [ebp-480h]
mov edx, dword ptr [ebp-480h]
mov dword ptr array2[edx*4], ecx
Assembly (my class):
mov edx,dword ptr [array1]
mov eax,dword ptr [ebp-43Ch]
mov ecx,dword ptr [ebp-43Ch]
mov dword ptr [edx+eax*4], ecx
mov edx, 10
sub edx, dword ptr [ebp-43Ch]
mov eax, dword ptr [array2]
mov ecx, dword ptr [ebp-43Ch]
mov dword ptr [eax+ecx*4], edx
One instruction is not a loss of performance with today's processors. I would not worry about it and instead suggest you read Coding Horror's article on micro optimization.
However, that instruction is just moving the first index (myarray+0) to edx so it can be used.