SSE3 vs. REG32 Was ODERt schneller?

09/11/2008 - 23:22 von Hubert Seidel | Report spam
Hallo NG,

in der Delphi-NG
gibt es mal wieder eine Frage a la "Was ist schneller".
Es geht dabei wie man am schnellsten zwei Arrays mit Oder verknüpft.

Hier meine beiden Varianten:
==procedure IntArrayOrReg16(var a,b; c:integer);
asm
push ebx
push esi
push edi

mov esi, a
mov edi, b
sub edi, esi

shr ecx, 3 // ecx := ecx / 8

jecxz @ende

@lp01:

db $0F,$18,$46,$40 { prefetchnta [esi+64] }
db $0F,$18,$44,$3E,$40 { prefetchnta [esi+edi+64] }

mov ebx, [esi]
mov edx, [esi+4]
or ebx, [esi+edi]
or edx, [esi+edi+4]
mov [esi], ebx
mov [esi+4], edx

mov ebx, [esi+8]
mov edx, [esi+12]
or ebx, [esi+edi+8]
or edx, [esi+edi+12]
mov [esi+8], ebx
mov [esi+12], edx

mov ebx, [esi+16]
mov edx, [esi+4+16]
or ebx, [esi+edi+16]
or edx, [esi+edi+4+16]
mov [esi+16], ebx
mov [esi+4+16], edx

mov ebx, [esi+8+16]
mov edx, [esi+12+16]
or ebx, [esi+edi+8+16]
or edx, [esi+edi+12+16]
mov [esi+8+16], ebx
mov [esi+12+16], edx

add esi, 32
dec ecx
jnz @lp01

@ende:
pop edi
pop esi
pop ebx
end;

==
procedure IntArrayOrSse16(var a,b; c:integer);
asm
push esi
push edi

mov esi, a
mov edi, b
sub edi, esi

shr ecx, 4 // ecx := ecx / 16

jecxz @ende

@lp01:

db $0F,$18,$46,$40 { prefetchnta [esi+64] }
db $0F,$18,$44,$3E,$40 { prefetchnta [esi+edi+64] }

db $0F,$28,$06 { movaps xmm0, [esi] }
db $0F,$28,$56,$10 { movaps xmm2, [esi+16] }
db $0F,$28,$66,$20 { movaps xmm4, [esi+32] }
db $0F,$28,$76,$30 { movaps xmm6, [esi+48] }

db $0F,$56,$04,$3E { orps xmm0, [esi+edi] }
db $0F,$56,$54,$3E,$10 { orps xmm2, [esi+edi+16] }
db $0F,$56,$64,$3E,$20 { orps xmm4, [esi+edi+32] }
db $0F,$56,$74,$3E,$30 { orps xmm6, [esi+edi+48] }

db $0F,$29,$06 { movaps [esi], xmm0 }
db $0F,$29,$56,$10 { movaps [esi+16], xmm2 }
db $0F,$29,$66,$20 { movaps [esi+32], xmm4 }
db $0F,$29,$76,$30 { movaps [esi+48], xmm6 }

add esi, 64
dec ecx
jnz @lp01

@ende:
pop edi
pop esi
end;

==Falls der komplette Code interessieren sollte:
news:gf520r$1hj$03$1@news.t-online.com (SSE-Variante)
news:gf5392$kcr$02$1@news.t-online.com (REG32-Variante)
Die beiden Arrays sind je 6000000 Integer a 32Bit groß.

Auf meinem PIII wurden im Original 24 Takte pro Integer
(in Delphi) gegenüber der SSE-Variante mit 15,6 Takte benötigt.
24,090 <== # ==> 15,598 > -35,252%
Auf der gleichen CPU war die REG32-Variante sogar
zu meinem großen erstaunen etwas schneller:
24,516 <== # ==> 13,907 > -43,275%

Ich kann das leider nur auf einem PIII testen.
Hat jemand zufàllig Zeit und Lust den Code
auf einer neueren CPU zu Testen?

mfg.
Herby

P.S:
Ok,... das prefetchnta ist voraussichtlich überflüssig.
Wird aber auch nicht schneller wenn's weg ist:)

http://www.hubert-seidel.de
 

Lesen sie die antworten

#1 Jan Seiffert
10/11/2008 - 14:51 | Warnen spam
Hubert Seidel wrote:
Hallo NG,




Hallo Hubert :)
Hmmm, welches ist denn jetzt das richtige Post? Ich antworte mal hier drauf...

in der Delphi-NG
gibt es mal wieder eine Frage a la "Was ist schneller".
Es geht dabei wie man am schnellsten zwei Arrays mit Oder verknüpft.




Hihihi.
Es gibt also auch noch andere die das Problem haben, bei mir war es nur XOR und AND.

[snip code]
==> Falls der komplette Code interessieren sollte:
news:gf520r$1hj$03$ (SSE-Variante)
news:gf5392$kcr$02$ (REG32-Variante)
Die beiden Arrays sind je 6000000 Integer a 32Bit groß.




also fast 23 Mb...
Nagut, bei mir sind es nur 128kb, aber dafuer immer und immer wieder.

Auf meinem PIII wurden im Original 24 Takte pro Integer
(in Delphi) gegenüber der SSE-Variante mit 15,6 Takte benötigt.
24,090 <== # ==> 15,598 > -35,252%
Auf der gleichen CPU war die REG32-Variante sogar
zu meinem großen erstaunen etwas schneller:
24,516 <== # ==> 13,907 > -43,275%




Nicht ganz so erstaunlich.
Bei meinen Experimenten und Tests auf 4 verschiedenen CPUs hat sich fuer mich
ergeben (und viel lesen ueber das "Wehklagen" anderer, wenn auch mit komplexeren
Befelsfolgen):
Man kann das mit statischem Code nicht abdecken. Es gibt da kein "universelles
schneller".
Man kann auf den Code die ueblichen Tricks anwenden, unrolling, sheduling etc.
OK, das macht den SSE Code schneller, aber genauso den normalen Register Code.

Welcher Befehlsatz dann fuer diese "Brutforceanwendung" (man benutzt die SSE/MMX
Befehle ja nicht, weil sie in dem Fall irgendwas tolles koennen, sondern weil
sie einfach "breiter" sind) schneller ist, haengt absolut von der Generation der
CPUs ab. Athlons bis vor dem K10 (Phenom) kranken z.B. daran, das ihr SSE intern
mit 64 Bit arbeitet und deshalb alle Befehle in 2 Schritten abarbeitet. Dein
PIII ist was SSE angeht eben auch ein typischer Intel-Test-Ballon (viel
Marketing, wenig Substanz). Anders sieht das z.B. schon wieder auf dem P4 aus.

Man muss verschiedene Funktionen schreiben, die alle ein wenig einer gewissen
CPU entgegen kommen (mal 2fach ausgerollt, mal 4fach, MMX, SSE), beim Start
misst man kurz die Laufzeit der Routinen gegen ein kleines Testarray (nicht zu
klein! Wir wollen Cachebust effekte mitsehen), und switcht dann einen globalen
Funktionszeiger auf die schnellste Routine. (Calloverhaed bei 23Mb ist pillepalle)
Der Linux Softraid Code macht das auch so. Beispiel aus dem Kernel dmesg:
raid6: int32x1 222 MB/s
raid6: int32x2 332 MB/s
raid6: int32x4 296 MB/s
raid6: int32x8 250 MB/s
raid6: mmxx1 742 MB/s
raid6: mmxx2 1128 MB/s
raid6: sse1x1 496 MB/s
raid6: sse1x2 902 MB/s
raid6: sse2x1 933 MB/s
raid6: sse2x2 1265 MB/s
raid6: using algorithm sse2x2 (1265 MB/s)

Hier sieht man z.B. das bei einfachem Register Code 2fach ausgerollt schneller
ist als 4 fach auf meinem Athlon X2. SSE2 ist (das sich nur durch ein paar
prefetch von SSE1 unterscheided) insgesamt am schnellsten. SSE ist aber nur
minimal schneller als MMX (das angesprochene 64Bit abarbeitungs Problem).

Mal als Gegenbeispiel ein Core2:
raid6: int32x1 667 MB/s
raid6: int32x2 734 MB/s
raid6: int32x4 546 MB/s
raid6: int32x8 515 MB/s
raid6: mmxx1 2332 MB/s
raid6: mmxx2 2308 MB/s
raid6: sse1x1 1585 MB/s
raid6: sse1x2 1792 MB/s
raid6: sse2x1 2968 MB/s
raid6: sse2x2 3046 MB/s
raid6: using algorithm sse2x2 (3046 MB/s)

Ja, SSE ist "breiter", lahmt aber auch ohne die richtigen prefetch.

Nungut, HTH.

Hier dann noch wie ich es fuer mich geloest habe:
Da ich keine Laufzeitmessung machen wollte (tsc saugt, normale Messung dann zu
lang), hab ich es dann so geloest:
Es gibt die XOR/AND-Funktion mehrfach nach verschiedenen Technologiestufen. Je
nach Technologiestufe werden dann neuere Befehle fuer die Operation oder fuer
Prefetch oder was immer benutzt. Da ich nicht weiss, wie die Eingangsdaten
ausgerichtet sind (es soll ne transparente lib-Funktion sein) muss ich erst
alignen (MMX/SSE mit den mov unaligned Befehlen ist Witzlos, das wird immer von
normalem Register Code geschlagen) und da es zwei arrays sind (Quelle und Ziel)
kann man beide nicht immer auf das gleich grosse alignment bringen, darum gibt
es auch immer die "kleinere" Tech-Variante als Fallback. Die Routinen selbst
sind nach "bestem Gewissen" gecoded. Beim start wird einfach die CPU
abgeklappert nach "was kannst du" und dann ein globaler Funktionpointer umgebogen.

Die Routinen sehen dann immer so aus, mal mit mehr oder weniger technologien:

if(aligment moeglich technologie 1) {
aligment technologie 1
goto technologie 1
}
if(aligment moeglich technologie 2) {
aligment technologie 2
goto technologie 2
}
#ifdef X86
/* schreibzugriffe auf x86 reagieren auf alignment, lesezugriffe kaum
* nur eines der beiden arrays kann man immer ausgerichtten
*/
align Ziel auf native register groesse
goto native register groesse
#else
if(aligment moeglich native register groesse) {
aligment native register groesse
goto native register groesse
}
#endif
goto fallback

technologie 1:
if(laenge > technologie 1 groesse)
{

}
/* falltrough */
technologie 2:
if(laenge > technologie 2 groesse)
{

}
/* falltrough */
native register groesse:
if(laenge > native groesse)
{

}
/* falltrough */
fallback:
byteweise den rest

Ich kann das leider nur auf einem PIII testen.
Hat jemand zufàllig Zeit und Lust den Code
auf einer neueren CPU zu Testen?


mfg.
Herby




Gruss
Jan

P.S:
Ok,... das prefetchnta ist voraussichtlich überflüssig.
Wird aber auch nicht schneller wenn's weg ist:)



Sag das nicht...

Die Schweiz ist nur deswegen neutral, weil sie noch nicht weiß
auf welcher Seite Chuck Norris steht.

Ähnliche fragen