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
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
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.
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
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...
>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
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...
>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:
_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.