Hallo. Wollte mal testen inwieweit intrinsische Funktionen für Geschwindigkeit-optimierte Programme einsetzbar sind. Zum Vergleich habe ich die intr. Funktion _bittest() eingesetzt und anschließend im normalen C-Code umgesetzt. Das Ergebnis war überraschend, da _bittest() um mehrere msec langsamer ist als die normale Bitprüfung in C. Wie kann das sein? Der Compiler sollte doch _bittest entsprechend optimieren können. Hier der Programmausschnitt: int nbit,i; long num = 2147483648; Code Intrinsic: --------------------------------------- ... for(i = 0; i < 80000; i++) for(nbit = 0; nbit < 32; nbit++) { test[nbit] = _bittest(&num,nbit); } ... --------------------------------------- normales c: --------------------------------------- ... for(a = 0; a < 80000; a++) for(nbit = 0; nbit < 32; nbit++) { if( num & (1<<nbit)) test[nbit]=1; else test[nbit]=0; } ... --------------------------------------- Umgebung: Win Vista,Dual Core,Visual Studio 2010 Danke. Gruß Christian
Habe den Assemblercode noch nicht angesehen. Wollte erstmal sehen ob mein Compiler _bittest() entsprechend optimieren kann und ob das effizienter ist als normales Bit prüfen. Christian
ist überhaupt die optimierung eingeschaltet? Dann müsste er doch sogar die äußere schleife komplett entfernen, weil sie keinen sinn macht.
Die Optimierung ist eingeschaltet(Geschwindigkeit maximieren /O2). Die äußere Schleife dient nur dazu das Bit- testen x-mal zu wiederholen. Ist die äußere Schleife durchlaufen, kann ich dann in milli- Sekunden messen(GetLocalTime() kann nicht besser auflösen als 1ms). Christian
Ok, Standard ist gcc, deshalb habe ich es mal angenommen, weil du keinen Comnpiler nennst. Was ist mit dem Assembler-Code?
Christian schrieb: > Die äußere Schleife dient nur dazu das Bit- testen x-mal zu wiederholen. das ist schon klar, aber der optimierer kann sie entfernen weil sie für ihn kein sinn macht und der egebniss am ende das gleiche ist.
liese mal ob du nicht noch andere schalter setzen musst http://msdn.microsoft.com/en-us/library/26td21ds%28v=vs.80%29.aspx
Hier mal den Assemblercode für Bitset(): test[nbit] = _bittest(&num,nbit); 01201072 lea ecx,[ebp-7Ch] 01201075 bt dword ptr [ecx],eax 01201078 inc eax 01201079 cmp eax,20h 0120107C jl wmain+72h (1201072h) Sieht nicht unbedingt optimiert aus. Christian
Der Schalter für /Oi (Generate Intrinsic Functions) ist auch gesetzt. Trotzdem produziert _bittest() langsameren Programmcode! Christian
Hast Du die Variable num als volatile deklariert? Wenn nicht, wird wie gesagt wahrscheinlich die äußere Schleife wegoptimiert.
>wegoptimiert
wäre auch mein erster verdacht gewesen, aber wie soll der compiler das
erkennen?
>Hast Du die Variable num als volatile deklariert?
Hab ich. Keine Veränderung.
Christian
Christian schrieb: > Hier mal den Assemblercode für Bitset(): > > test[nbit] = _bittest(&num,nbit); > 01201072 lea ecx,[ebp-7Ch] > 01201075 bt dword ptr [ecx],eax > 01201078 inc eax > 01201079 cmp eax,20h > 0120107C jl wmain+72h (1201072h) > > Sieht nicht unbedingt optimiert aus. > > Christian Und wie sieht der Assembler-Code zur C-Bittest-Funktion aus? Es wäre nicht schlecht, beides zu posten, wenn es um den Vergleich geht...
vermutlich lassen sich bei der Version mit shl und cmp die Sprünge besser vorhersehen und/oder einige Instruktionen parallel bearbeiten also (auch wenn es mehr sind) schneller abarbeiten.. ps --------------------------------------- ... for(a = 0; a < 80000; a++) for(nbit = 0; nbit < 32; nbit++) { if( _bittest(&num,nbit)) test[nbit]=1; else test[nbit]=0; hast das schon mal probiert? nur so interessehalber (nicht weil es sinnvoll wäre)
Hier mal das Assembly vom C Code: 00E91030 test ecx,80000000h 00E91036 setne dl 00E91039 mov byte ptr [ebp+eax-24h],dl 00E9103D inc eax 00E9103E rol ecx,1 00E91040 cmp eax,20h 00E91043 jl wmain+30h (0E91030h) Warum dieser asm Code schneller ist als der asm Code von _bittest() erschließt sich mir leider nicht. Christian
>erschließt sich mir leider nicht.
weil dort überhaupt kein "bittest" mehr drinnen ist
der ganze teil wurde wegoptimiert (weil test[] nach der schleife NIE
verwende wird...
Ich erkenne da schon noch einen Bittest, mit test ecx und rol ecx. Die lea-Instruktion im ersten asm-Auszug benötigt viel Zeit.
>der ganze teil wurde wegoptimiert
Habe mal das array test[] auf volatile und auch nach der Schleife mal
mit Dummy Werten beschrieben.Der Compiler sollte nichts mehr
wegoptimieren. Das Ergebnis ist dasselbe.
_bittest() benötigt im Schnitt 10ms, ein normaler Bittest(ohne
_bittets()) benötigt im Schnitt 5ms.
Also doppelt so schnell wenn man keine Intrinsic einsetzt.
Christian
Das ganze hängt stark von Prozessortyp, Cache und Speichermodell ab. Grundsätzlich steht intrinsic function für eine in eine Funktion eingebettete Operation. Danach sieht der Assembler-Auszug nicht aus. Christian, hast du beim intrinsic-Asm-Code evtl. zu viel weggeschnitten? Zwar enthält die Intrinsic-Version in der Schleife weniger Instruktionen, trotzdem benötigt lea viel Zeit, und du hast einen Datenkonflikt in eax, was zu einem Pipeline-Stall führt, was zusätzlich Zeit kostet. Christian schrieb: > Wollte mal testen inwieweit intrinsische Funktionen für > Geschwindigkeit-optimierte Programme einsetzbar sind. Fazit: Die Anwendung von intrinsischen Funktionen macht nur in Spezialfällen Sinn. Aus meiner Sicht ist das ganze Experiment stark sinnfrei.
>Christian, hast du beim intrinsic-Asm-Code evtl. zu viel weggeschnitten? Hab ich.Aber nur denn Teil mit den Schleifen. >und du hast einen Datenkonflikt in eax Woran erkennst du das? > Aus meiner Sicht ist das ganze Experiment stark sinnfrei. Wollte nur mal wissen welche Auswirkungen _bittest() auf mein Programm hat hinsichtlich Geschwindigkeit.Wenn der Compiler so leistungsfähige Optionen bietet,warum dann nicht verwenden.Daher der Test.Nur das Ergebnis ist ernüchternd. Christian
>Ich erkenne da schon noch einen Bittest, mit test ecx
das ist die Schleife (8000 in oct ??)
Per Definition hat Intrinsic nichts mit leistungsfähig zu tun, eher im Gegenteil. Was Datenkonflikte sind lernt man in Rechnerarchitektur, da findest du bestimmt auch zahlreiche Infos im Internet. Im C-Code erkenne ich zw. setne und mov einen RAW(Read after Write)-Konflikt mit dl. In der intrinsischen Variante finde ich zw. lea und bt, bzgl. ecx und zw. inc und cmp bzgl. eax einen Konflikt.
Christian schrieb: > 00E91030 test ecx,80000000h > 00E91036 setne dl > 00E91039 mov byte ptr [ebp+eax-24h],dl > 00E9103D inc eax > 00E9103E rol ecx,1 > 00E91040 cmp eax,20h > 00E91043 jl wmain+30h (0E91030h) für Robert: test ecx, 8... testet das höchste Bit in ecx Das Ergebnis landet in dl, und dann im Speicher (00E91036, 00E91039) eax ist die Schleifen-Variable und wird hochgezählt ecx enthält das zu prüfende Wort und wird um eine Stelle nach links geshiftet. eax wird mit 32 verglichen, danach kommt die Schleife... P.S. Kein Asm-Dump enthält oct...
(hab ich ja ziemlich Blödsinn gepostet..) unglaublich dass das so optimiert wird..
>Im C-Code erkenne ich zw. setne und mov einen RAW(Read after Write)-Konflikt mit
dl.
Sorry, aber auch wenn ich die Vorlesung Rechner- Architektur nicht
besucht
habe, bin ich mir sicher, das man aus den asm Ausschnitten oben, keine
Konflikte ableiten kann.
Weil 1.
Assembler Code aus rein sequenziellen Ausführungseinheiten besteht.
Der RAW-Konflikt macht sich aber erst in der massiven parallel
ausgeführten Pipeline bemerkbar. Ohne Kenntnisse der verwendeten
CPU/Pipeline Einheit und des eingesetzten Compilers solch eine Aussage
zutreffen halte ich für gewagt.
2.
Es sind keine Umordnungsbefehle oder Verzögerungselemente (_NOP) im asm
Code.
3.
Prozessoren heutzutage Datenkonflikte in Hardware lösen(Data Forwarding
Unit).
Gruß
Christian
>Per Definition hat Intrinsic nichts mit leistungsfähig zu tun
Meinte damit die Leistungsfähigkeit eines Compilers, bestimmte
Funktionen optimierend auszuführen.
Christian
Christian schrieb: >>Per Definition hat Intrinsic nichts mit leistungsfähig zu tun > > Meinte damit die Leistungsfähigkeit eines Compilers, bestimmte > Funktionen optimierend auszuführen. > > Christian Auch die optimierte Variante ist langsamer:
1 | int bitTestStandard(const long* array, long bitNumber, size_t len) { |
2 | int numberOfSetBits = 0; |
3 | for (size_t i = 0; i < len; i++) { |
4 | numberOfSetBits += (*array & (1 << bitNumber)) ? 1 : 0; |
5 | array++; |
6 | }
|
7 | return numberOfSetBits; |
8 | }
|
9 | |
10 | int bitTestIntrinsic(const long* array, long bitNumber, size_t len) { |
11 | int numberOfSetBits = 0; |
12 | for (size_t i = 0; i < len; i++) { |
13 | numberOfSetBits += _bittest(array, bitNumber) ? 1 : 0; |
14 | array++; |
15 | }
|
16 | return numberOfSetBits; |
17 | }
|
18 | |
19 | const int testArraySize = 123456789; |
20 | const long testArrayBitNumber = 13; |
21 | long *testArray; |
22 | |
23 | int _tmain(int argc, _TCHAR* argv[]) { |
24 | testArray = new long[testArraySize]; |
25 | |
26 | for (size_t i = 0; i < testArraySize; i++) { |
27 | testArray[i] = i; |
28 | }
|
29 | |
30 | auto tickCountIntrinsic = GetTickCount64(); |
31 | auto resultIntrinsic = bitTestIntrinsic(testArray, testArrayBitNumber, testArraySize); |
32 | tickCountIntrinsic = GetTickCount64() - tickCountIntrinsic; |
33 | |
34 | auto tickCountStandard = GetTickCount64(); |
35 | auto resultStandard = bitTestStandard(testArray, testArrayBitNumber, testArraySize); |
36 | tickCountStandard = GetTickCount64() - tickCountStandard; |
37 | |
38 | printf("Intrinsic: %lld, %d, Standard: %lld, %d\n", tickCountIntrinsic, resultIntrinsic, tickCountStandard, resultStandard); |
39 | |
40 | return 0; |
41 | }
|
VC2012 /Ox (full optimization) /Ob2 (inline any suitable) /Oi (enable intrinsics) /Ot (favor fast code) /Oy (omit frame pointers) /GL (whole program optimization) /arch:AVX
1 | testArray = new long[testArraySize]; |
2 | 00C8100D push 1D6F3454h |
3 | 00C81012 call dword ptr ds:[0C82094h] |
4 | 00C81018 movdqa xmm1,xmmword ptr ds:[0C82130h] |
5 | 00C81020 add esp,4 |
6 | 00C81023 mov dword ptr ds:[00C83368h],eax |
7 | |
8 | for (size_t i = 0; i < testArraySize; i++) { |
9 | 00C81028 xor ecx,ecx |
10 | 00C8102A mov edx,eax |
11 | 00C8102C lea esp,[esp] |
12 | 00C81030 movd xmm0,ecx |
13 | 00C81034 pshufd xmm0,xmm0,0 |
14 | 00C81039 paddd xmm0,xmm1 |
15 | 00C8103D add ecx,4 |
16 | testArray[i] = i; |
17 | 00C81040 movdqu xmmword ptr [edx],xmm0 |
18 | 00C81044 lea edx,[edx+10h] |
19 | 00C81047 cmp ecx,75BCD14h |
20 | 00C8104D jb wmain+30h (0C81030h) |
21 | |
22 | for (size_t i = 0; i < testArraySize; i++) { |
23 | 00C8104F cmp ecx,75BCD15h |
24 | 00C81055 jae wmain+6Ch (0C8106Ch) |
25 | 00C81057 jmp wmain+60h (0C81060h) |
26 | 00C81059 lea esp,[esp] |
27 | testArray[i] = i; |
28 | 00C81060 mov dword ptr [eax+ecx*4],ecx |
29 | 00C81063 inc ecx |
30 | 00C81064 cmp ecx,75BCD15h |
31 | 00C8106A jb wmain+60h (0C81060h) |
32 | } |
33 | |
34 | auto tickCountIntrinsic = GetTickCount64(); |
35 | 00C8106C mov ebx,dword ptr ds:[0C82000h] |
36 | 00C81072 call ebx |
37 | auto resultIntrinsic = bitTestIntrinsic(testArray, testArrayBitNumber, testArraySize); |
38 | 00C81074 mov ecx,dword ptr ds:[0C83368h] |
39 | auto resultIntrinsic = bitTestIntrinsic(testArray, testArrayBitNumber, testArraySize); |
40 | 00C8107A mov ebp,eax |
41 | 00C8107C mov dword ptr [esp+10h],edx |
42 | 00C81080 xor edi,edi |
43 | } |
44 | |
45 | auto tickCountIntrinsic = GetTickCount64(); |
46 | 00C81082 mov esi,75BCD15h |
47 | 00C81087 jmp wmain+90h (0C81090h) |
48 | 00C81089 lea esp,[esp] |
49 | auto resultIntrinsic = bitTestIntrinsic(testArray, testArrayBitNumber, testArraySize); |
50 | 00C81090 bt dword ptr [ecx],0Dh |
51 | 00C81094 mov eax,0 |
52 | 00C81099 setb al |
53 | 00C8109C lea ecx,[ecx+4] |
54 | 00C8109F add edi,eax |
55 | 00C810A1 dec esi |
56 | 00C810A2 jne wmain+90h (0C81090h) |
57 | tickCountIntrinsic = GetTickCount64() - tickCountIntrinsic; |
58 | 00C810A4 call ebx |
59 | 00C810A6 sub eax,ebp |
60 | 00C810A8 mov ecx,edx |
61 | 00C810AA sbb ecx,dword ptr [esp+10h] |
62 | 00C810AE mov dword ptr [esp+1Ch],eax |
63 | 00C810B2 mov dword ptr [esp+18h],ecx |
64 | |
65 | auto tickCountStandard = GetTickCount64(); |
66 | 00C810B6 call ebx |
67 | 00C810B8 mov ecx,dword ptr ds:[0C83368h] |
68 | 00C810BE mov dword ptr [esp+14h],edx |
69 | 00C810C2 xor edx,edx |
70 | 00C810C4 xor ebx,ebx |
71 | 00C810C6 mov dword ptr [esp+10h],eax |
72 | 00C810CA add ecx,8 |
73 | 00C810CD mov ebp,273EF07h |
74 | auto resultStandard = bitTestStandard(testArray, testArrayBitNumber, testArraySize); |
75 | 00C810D2 mov eax,dword ptr [ecx-8] |
76 | 00C810D5 shr eax,0Dh |
77 | auto resultStandard = bitTestStandard(testArray, testArrayBitNumber, testArraySize); |
78 | 00C810D8 and eax,1 |
79 | 00C810DB add esi,eax |
80 | 00C810DD mov eax,dword ptr [ecx-4] |
81 | 00C810E0 shr eax,0Dh |
82 | 00C810E3 and eax,1 |
83 | 00C810E6 add ebx,eax |
84 | 00C810E8 mov eax,dword ptr [ecx] |
85 | 00C810EA shr eax,0Dh |
86 | 00C810ED and eax,1 |
87 | 00C810F0 add edx,eax |
88 | 00C810F2 lea ecx,[ecx+0Ch] |
89 | 00C810F5 dec ebp |
90 | 00C810F6 jne wmain+0D2h (0C810D2h) |
91 | tickCountStandard = GetTickCount64() - tickCountStandard; |
92 | 00C810F8 lea eax,[edx+ebx] |
93 | 00C810FB add esi,eax |
94 | 00C810FD call dword ptr ds:[0C82000h] |
95 | 00C81103 sub eax,dword ptr [esp+10h] |
_bittest steht nicht für die schnellstmögliche Operation, Bits in Worten zu testen, sondern direkt für den Befehl BT. Und der ist bei variabler Bitnummer auf Speicher ausgesprochen komplex, weil nicht auf 0..31 oder 0..63 begrenzt. Die Bitnummer wird vom Prozessor per unoptimiertem Microcode erst in eine Nummer innerhalb des Wortes und einen Offset auf die Adresse umgerechnet. Folglich ist diese Variante von BT ausgesprochen langsam, bei leidlich aktuellen Intels beispielsweise ~10 Takte. In realem Code wird man nicht selten feststellen, dass ein Compiler solchen Code besser optimiert, als die Hardware ihn per Microcode ausführt. Solche Effekte führten mit zur Entwicklung von RISC Architekturen.
Das meinen die GCC-Entwickler dazu:
1 | ;; %%% bts, btr, btc, bt. |
2 | ;; In general these instructions are *slow* when applied to memory, |
3 | ;; since they enforce atomic operation. When applied to registers, |
4 | ;; it depends on the cpu implementation. They're never faster than |
5 | ;; the corresponding and/ior/xor operations, so with 32-bit there's |
6 | ;; no point. But in 64-bit, we can't hold the relevant immediates |
7 | ;; within the instruction itself, so operating on bits in the high |
8 | ;; 32-bits of a register becomes easier. |
9 | ;; |
10 | ;; These are slow on Nocona, but fast on Athlon64. We do require the use |
11 | ;; of btrq and btcq for corner cases of post-reload expansion of absdf and |
12 | ;; negdf respectively, so they can never be disabled entirely. |
(aus ggg-4.7.2/gcc/config/i386/i386.md)
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.