D
Bin gerade erst dazu gekommen, den Code mal nach C für Linux zu portieren.
Dein Code prüft ein paar Fälle, die wir in der Realität nicht supporten:
- die Kopierfunktion soll nur das Kopieren von Pages unterstützen. Das heißt: dest und src sind immer korrekt aligned (selbst AVX benötigt nur 32 Byte), ebenso wie die Länge. Sprich, wir haben nie kleinere Reste, die noch mitkopiert werden müssen.
- bei meinen AVX-Tests habe ich festgestellt, dass das non-temporal Schreiben (nicht Lesen) doppelt so lange dauert wie die Daten aus dem Cache zu senden. Daher habe ich in meiner AVX-Implementierung einfache Reads und Writes verwendet:
while(plength--)
{
r1 = psrc[0];
r2 = psrc[1];
r3 = psrc[2];
r4 = psrc[3];
pdest[0] = r1;
pdest[1] = r2;
pdest[2] = r3;
pdest[3] = r4;
pdest += iterations;
psrc += iterations;
}
Um Optimierungen durch den Compiler vorzubeugen, habe ich die eigentlichen Kopierfunktionen in eine eigene Library gepackt, ohne LTO. Meines Wissens sollte das genug sein - schaue ich in das Kompilat, sehe ich dort auch den Funktionsaufruf von measure und die Funktionspointer in %esi geschoben.
Die Rtl-Funktionen habe ich auf Linux nicht, ein repmovsb war aber schnell gehackt:
void repmovsb
(
type_dest dest,
type_src src,
size_t length
)
{
__asm__
(
"rep movsd\n\t"
:
:"S"(src),"D"(dest),"c"(length / 4)
:"memory"
);
}
Iterations: 1000|Buffer size: 65536
memcpy : 2407023 ns (27.226994 GiB/s)
memmove : 2407597 ns (27.220502 GiB/s)
char_copy : 2419531 ns (27.086241 GiB/s) <---Wieso ...?
int_copy : 2420056 ns (27.080365 GiB/s)
avx_copy : 2449989 ns (26.749508 GiB/s)
repmovsb : 2407616 ns (27.220288 GiB/s)
Iterations: 3|Buffer size: 1073741824
memcpy : 803536257 ns (4.008812 GiB/s)
memmove : 797947852 ns (4.036887 GiB/s)
char_copy : 1139965098 ns (2.825723 GiB/s)
int_copy : 1142812474 ns (2.818682 GiB/s)
avx_copy : 1146694691 ns (2.809140 GiB/s)
repmovsb : 909587241 ns (3.541415 GiB/s)
Weitere Tests stehen noch aus - und so ganz vertraue ich den Berechnungen noch nicht, das werde ich mir noch mal anschauen, wenn mir nicht die Augen fast zufallen. Wobei ich eine Sache unbesehen glaube - dass memcpy bzw. memmove auf Linux so hochgezüchtet sind, dass sie repmovsb ohne Probleme schlagen.
Ach, und noch eine Sache: in diesem Thread ging es primär nicht um schnelles Kopieren von Daten, obwohl dieses im schlimmsten Fall notwendig wird, sondern um die Mapping-Verwaltung, die ich vorgeschlagen hatte. Ich habe diese nun implementiert und mit einem Produktivprogramm, was auf Linux bereits funktionierte, jetzt auf Windows getestet.
Stellt sich heraus, dass die Leute bei Microsoft die Vorteile von 64 Bit mal so gar nicht nutzen. Linux wie Windows haben im Userspace (der 47-Bit-Adressraum, der einem derzeit zur Verfügung steht) am Anfang einen Block Mappings, am Ende einen Block Mappings, und dazwischen gähnende Leere. Man sollte annehmen, dass für VirtualAlloc versucht wird, in dieser gähnenden Leere Speicher zu finden - aber anscheinend versucht das System eher, das Mapping so niedrig wie möglich anzulegen. Das ist natürlich kompletter Unsinn, denn wenn man nun versucht, ein weiteres Mapping danach anzulegen, um fortwährend Speicher zu reservieren, klappt das meist nicht, weil direkt nach dem eigenen Mapping ein reservierter/commiteter Block existiert. Und dann müssen wir Daten kopieren.
Ich habe bereits für beide Betriebssysteme Funktionen, mit der ich mir eine Mapping-Table ins Userspace ziehen kann. Unter Windows ist das allerdings der reine Overkill, da ich pro Region einmal einen Kernel-Call habe, um dann die Regioninformation aus den geschriebenen Daten zu ziehen.
Mein Plan war daher, dass ich mir die Speicherverwaltung unter 32-Bit- und 64-Bit-Prozessen ansehen und dann statisch den Beginn des leeren Blocks hinterlegen werde. Anstatt nun von Anfang an die Mappings durchzugehen, beginne ich am Anfang des Blocks zu suchen. Gleiches mache ich, wenn Mappings erfragt werden, die von oben nach unten wachsen sollen (Linux und Windows supporten das nativ, allerdings soll die Windows-Implementierung so ziemlich das langsamste im Universum sein), da fange ich dann halt von oben nach unten an zu suchen.
Irgendwelche Einwände?
EDIT: Für Linux habe ich bereits ein paar Änderungen eingefügt, die mir ein maps_binary für einen Prozess in procfs anlegen. Ich kann mir vorstellen, dass Leute jetzt vorschlagen werden, doch einen Treiber für Windows zu schreiben, der das Suchen nach freiem virtuellen Arbeitsspeicher durchführen soll.
Problem ist: bei Linux hatte ich ein bisschen Ahnung, was ich tat - bei Windows überhaupt keine. Deswegen stelle ich ja diese ganzen bescheuerten Fragen. Und einen Kerneltreiber zu schreiben wäre meines Erachtens eh Overkill, wobei ich noch nicht mal wüsste, wo da anfangen ...
EDIT2: Ach, das auch noch: Fences wären meines Wissens nur dann nötig, wenn das Schreiben non-temorary wäre. Da ich das aber nicht mache, wären diese nicht notwendig.
@hustbaer: Die Intrinsics-Seite kannte ich schon.