Hallo,
Hier die Ergebnisse der Performance Tests für die strcpy Funktion,
in der Hoffnung, dass sie jemanden nützen besseren Code zu schreiben.
- Bei kurzen String < 30 Zeichen ist der Unterschied nicht allzu gross.
- Bei langen Strings ist die AgnerFog Asmlib und die Intel Lib weit
vorne.
Wenn Interesse besteht, das ganze auf dem eigenen Rechner auszuprobier,
dann kann ich den Source und die Exe hier reinstellen.
Gruss,
Udo
Ich hab jetzt ehrlich gesagt noch nie von einem Programm gehört, das zu
langsam gelaufen wäre, weil ein strcpy() zu viel Rechenzeit verbraucht
hätte. 🤔
Mark B. schrieb:> Ich hab jetzt ehrlich gesagt noch nie von einem Programm gehört,> das zu> langsam gelaufen wäre, weil ein strcpy() zu viel Rechenzeit verbraucht> hätte. 🤔
Weißt du denn von jedem trägen Programm den Grund, warum es träge ist?
Le X. schrieb:> Weißt du denn von jedem trägen Programm den Grund, warum es träge ist?
Wenn ich das richtig überblicke, dann hat der Themenersteller nie
gesagt, dass irgendein Teil einer Software zu träge wäre.
Weißt du, warum die eine Variante schneller ist? Der Maschinencode wäre
interessant.
Ich könnte mir vorstellen, dass irgendwo die Vektorverarbeitung in der
CPU verwendet wird.
Der Asm Code wird bei den Testfiles mit ausgegeben.
Hier ein Beispiel für die den Microsoft Compiler mit -O2,
für eine memcpy() Funktion.
(Parameterübergabe findet immer in den Registern rcx, rdx und r8 statt).
1
for(n=0;n<len;n++)
2
dest[n]=source[n];
MS CL.EXE 15.00 macht eine übersichtliche Schleife mit 6 Befehlen:
1
test_memcpyPROC;COMDAT
2
3
;19:size_tn;
4
;20:BYTE*p=dst;
5
;21:constBYTE*r=src;
6
;22:
7
;23:for(n=0;n<len;n++)
8
9
testr8,r8
10
jeSHORT$LN8@test_memcp
11
movr9,rcx
12
subrdx,rcx
13
npad5
14
$LL3@test_memcp:
15
16
;24:p[n]=r[n];
17
18
movzxeax,BYTEPTR[rdx+r9]
19
incr9
20
subr8,1
21
movBYTEPTR[r9-1],al
22
jneSHORT$LL3@test_memcp
23
$LN8@test_memcp:
24
25
;25:
26
;26:returndst;
27
28
movrax,rcx
29
30
;27:}
31
32
ret0
33
test_memcpyENDP
64 Bit Register r8 ist die Länge (len), und wird in jedem
Schleifendurchlauf bis auf 0 runtergezählt (sub r8,1).
64 Bit Register r9 ist der Index n, der in jedem Durchlauf
raufgezählt wird (inc r9).
Die Daten werden ins Register 32 Bit Register eax geladen,
und dann gleich als Byte (al = Register eax Low Byte) abgespeichert
Intel ICL.EXE macht bei -O1 -Oi- daraus 5 Befehle:
Eine 1:1 Umsetzung der for Schleife, mit 64 Bit Register r10
als Index n. Register r8 ist der Länge len.
Geladen werden die Bytes ins 64 Bit Register r9, und
als Byte (r9b) abgespeichert.
Wenn man aber -QxHost -O2 angibt, wird daraus ein Aufruf der
intel_memcpy(), zumindest für wenn der Block grösser als 96 Bytes ist:
"-O2" sollte man wegen dem Code-Bloat nur machen, wenn es notwendig ist.
Wobei im Zeitalter, wo ein "Hello World" schon mal 100 MByte hat,
ist das auch schon Wurscht.
Der gcc 9.2 mit -O2 macht daraus auch 5 Befehle:
1
test_memcpy:
2
.seh_endprologue
3
movrax,rcx
4
testr8,r8
5
je.L2
6
xorr9d,r9d
7
.p2align4,,10
8
.p2align3
9
.L3:
10
movzxr10d,BYTEPTR[rdx+r9]
11
movBYTEPTR[rax+r9],r10b
12
addr9,1
13
cmpr8,r9
14
jne.L3
15
.L2:
16
ret
mit "-O3 -march=native" werden daraus AVX Befehle, die 32 Bytes in
einem Rutsch kopieren (Label L4), zumindest wenn der Block > 64 Bytes
ist,
und aligned ist, sonst wird ganz konventionell über Register r9 kopiert
(Label L3):
[/c]
test_memcpy:
.seh_endprologue
mov rax, rcx
test r8, r8
je .L22
lea rcx, 31[rcx]
sub rcx, rdx
cmp rcx, 62
jbe .L8
lea rcx, -1[r8]
cmp rcx, 30
jbe .L8
mov rcx, r8
and rcx, -32
xor r9d, r9d
.p2align 4,,10
.p2align 3
.L4:
vmovdqu ymm0, YMMWORD PTR [rdx+r9]
vmovdqu YMMWORD PTR [rax+r9], ymm0
add r9, 32
cmp r9, rcx
jne .L4
mov r9, r8
and r9, -32
test r8b, 31
je .L21
.p2align 4,,10
.p2align 3
.L6:
movzx ecx, BYTE PTR [rdx+r9]
mov BYTE PTR [rax+r9], cl
inc r9
cmp r8, r9
ja .L6
vzeroupper
.L22:
ret
.p2align 4,,10
.p2align 3
.L8:
xor ecx, ecx
.p2align 4,,10
.p2align 3
.L3:
movzx r9d, BYTE PTR [rdx+rcx]
mov BYTE PTR [rax+rcx], r9b
inc rcx
cmp r8, rcx
jne .L3
ret
.L21:
vzeroupper
ret
[/c]
Ich fände einen Vergleich mit strlen interessanter. Weil da mehr
Spielraum für Optimierung des Algorithmus besteht. Bei strcpy muss jedes
Zeichen kopiert werden. Untere Schranke für die Komplexität ist also
O(n). D.h. die Optimierung beschränkt sich allein auf die Details der
Rechnerarchitektur.
Bei strlen aber nicht.
Cyblord -. schrieb:> Ich fände einen Vergleich mit strlen interessanter. Weil da mehr> Spielraum für Optimierung des Algorithmus besteht. Bei strcpy muss jedes> Zeichen kopiert werden. Untere Schranke für die Komplexität ist also> O(n). D.h. die Optimierung beschränkt sich allein auf die Details der> Rechnerarchitektur.> Bei strlen aber nicht.
Was willst du denn bei strlen anders machen? Bei strlen muss man jedes
Byte angucken, bis man ne 0 gefunden hat und darf nicht dahinter
weitergucken. Was soll man da anderes machen als ne lineare Suche Byte
für Byte?
Ist das so?
Der Compiler kann ja nicht irgendwo nach im Speicher nach '\0'
rumsuchen,
Die Komplexität muss also auch bei strlen() O(n) sein.
Aber wenn dir ein ganz bestimmter Test vorschwebt, kann ich den gerne
einbauen.
Die Benschmarks drehen sich aber um die Details der Rechnerarchitektur,
und inwieweit der Compiler da optimieren kann.
Hier wird ja immer wieder behauptet, dass Optimierungen auf dieser
Ebene keine Rolle spielen, aber wenn man z.B. grosse Daten kopiert,
dann machen solche "Mikrooptimierungen" schon mal einen sehr spürbaren
Faktor aus.
Hast du einen Ryzen? Mich würde wirlich interessieren, wie die
da abschneiden.
udok schrieb:> Ist das so?> Der Compiler kann ja nicht irgendwo nach im Speicher nach '\0'> rumsuchen,> Die Komplexität muss also auch bei strlen() O(n) sein.
Wäre in der Tat möglich. Mein erster Gedanke war da könnte Algorithmisch
was gehen, aber vielleicht auch nicht.
udok schrieb:> dann machen solche "Mikrooptimierungen" schon mal einen sehr spürbaren> Faktor aus
Nur sind die hochgradig vom konkreten System abhängig. Bei
Systemwechseln ungünstig.
Cyblord -. schrieb:> Ich fände einen Vergleich mit strlen interessanter. Weil da mehr> Spielraum für Optimierung des Algorithmus besteht.
Da ist was dran. Bei strlen() gäbe es für sehr lange Ketten jede Menge
Optimierungspotenzial über die Ausnutzung des Wissens über das Layout
des virtuellen Adressraums und des Heaps. Andererseits stellt sich
natürlich auch die Frage, wo so große Zeichenketten überhaupt ohne
Längeninformation überhaupt vorkommen, dass das eine Rolle spielt.
Walter T. schrieb:> Bei strlen() gäbe es für sehr lange Ketten jede Menge> Optimierungspotenzial über die Ausnutzung des Wissens über das Layout> des virtuellen Adressraums und des Heaps.
Nö.
Die Anforderung: "Darf kein Byte nach \0 lesen" macht dir so ziemlich
alle Optimierungsmöglichkeiten zunichte.
Der Prozessor wird zwar trotzdem viele Bytes nach dem "\0" lesen und
auswerten (Speculative execution), aber wieder "ROLLBACKen". Was uns die
schöne Klasse der Spectre-CPU-Bugs eingehandelt hat.