Wie repliziere ich schnell eine 6-Byte-Ganzzahl ohne Vorzeichen in eine Speicherregion?

8

Ich muss einen 6-Byte-Ganzzahlwert in eine Speicherregion replizieren, beginnend mit seinem Anfang und so schnell wie möglich. Wenn eine solche Operation in Hardware unterstützt wird, möchte ich sie verwenden (ich bin jetzt auf x64-Prozessoren, Compiler ist GCC 4.6.3).

memset passt nicht zum Job, da nur Bytes repliziert werden können. Die std::fill ist auch nicht gut, weil ich sogar keinen Iterator definieren kann, der zwischen 6 Byte-Breitenpositionen in der Speicherregion springt.

Also, ich hätte gerne eine Funktion:

%Vor%

Dies sieht wie memset aus, aber es gibt ein zusätzliches Argument width , um zu definieren, wie viele Bytes aus dem value repliziert werden sollen. Wenn so etwas in C ++ ausgedrückt werden könnte, wäre das noch besser.

Ich kenne bereits die offensichtliche myMemset Implementierung, die die memcpy in Schleife mit dem letzten Argument (zu kopierende Bytes) gleich der width aufrufen würde. Ich weiß auch, dass ich einen temporären Speicherbereich mit der Größe 6 * 8 = 48 Bytes definieren kann, fülle ihn mit 6-Byte-Ganzzahlen und dann memcpy es mit dem Zielbereich.

Können wir es besser machen?

    
HEKTO 16.02.2015, 22:03
quelle

5 Antworten

1

Schreiben Sie jeweils 8 Bytes.

Auf einer 64-Bit-Maschine kann der generierte Code sicherlich mit 8-Byte-Schreibvorgängen gut funktionieren. Nachdem Sie einige Setup-Probleme in einer engen Schleife behoben haben, schreiben Sie 8 Byte pro Schreibvorgang über num mal. Annahmen gelten - siehe Code.

%Vor%

Weitere Optimierung beinhaltet das Schreiben von 2 Blöcken zu einem Zeitpunkt width == 3 or 4 , 4 Blöcke zu einem Zeitpunkt, wenn width == 2 und 8 Blöcke gleichzeitig width == 1 .

    
chux 17.02.2015, 19:49
quelle
6

Etwas entlang @Mark Ransom Kommentar:

Kopieren Sie 6 Bytes, dann 6, 12, 24, 48, 96 usw.

%Vor%

Optimierung: skalieren n und width von 6.

[Bearbeiten]
Ziel @SchighSchagh korrigiert Hinzugefügt Cast (char *)

    
chux 16.02.2015 22:29
quelle
4

Bestimmen Sie die effizienteste Schreibgröße, die die CPU unterstützt; Finde dann die kleinste Zahl, die gleichmäßig durch 6 und die Schreibgröße geteilt werden kann und rufe diese "Blockgröße" auf.

Teilen Sie nun den Speicherbereich in Blöcke dieser Größe auf. Jeder Block ist identisch und alle Schreibvorgänge werden korrekt ausgerichtet (vorausgesetzt, der Speicherbereich selbst ist korrekt ausgerichtet).

Wenn beispielsweise die effizienteste Schreibgröße, die die CPU unterstützt, 4 Bytes beträgt (z. B. alt 80486), dann wäre die "Blockgröße" 12 Bytes. Sie würden 3 allgemeine Register einstellen und 3 Speicher pro Block machen.

Wenn die effizienteste Schreibgröße, die die CPU unterstützt, 16 Bytes (z.B. SSE) beträgt, dann wäre die "Blockgröße" 48 Bytes. Sie würden 3 SSE-Register setzen und 3 Speicher pro Block machen.

Ich würde auch empfehlen, die Größe des Speicherbereichs zu runden, um sicherzustellen, dass es ein Vielfaches der Blockgröße ist (mit etwas "nicht unbedingt notwendiger" Auffüllung). Ein paar unnötige Schreibvorgänge sind weniger teuer als Code, um einen "Teilblock" zu füllen.

Die zweiteffizienteste Methode könnte eine Speicherkopie sein (aber nicht memcpy() oder memmove() ). In diesem Fall würden Sie die ersten 6 Bytes schreiben (oder 12 Bytes oder 48 Bytes oder was auch immer), dann kopieren Sie von (z. B.) &area[0] nach &area[6] (vom niedrigsten zum höchsten), bis Sie das Ende erreichen. Dafür wird memmove() nicht funktionieren, weil es bemerkt, dass sich der Bereich überschneidet und stattdessen vom höchsten zum niedrigsten arbeitet; und memcpy() funktioniert nicht, da angenommen wird, dass Quelle und Ziel nicht überlappen. Sie müssten also Ihre eigene Speicherkopie erstellen. Das Hauptproblem dabei ist, dass Sie die Anzahl der Speicherzugriffe verdoppeln - "Lesen und Schreiben" ist langsamer als "Schreiben alleine".

    
Brendan 16.02.2015 23:43
quelle
3

Wenn Ihr Num groß genug ist, können Sie versuchen, die AVX-Vektorbefehle zu verwenden, die 32 Bytes gleichzeitig bearbeiten ( (_mm256_load_si256 / _mm256_store_si256 oder ihre nicht ausgerichteten Varianten).

Da 32 kein Vielfaches von 6 ist, müssen Sie zuerst das 6-Byte-Muster 16 Mal replizieren, indem Sie kurze Memcys oder 32/64 Bit-Bewegungen verwenden.

%Vor%

Sie werden auch mit einem kurzen Memcpy fertig.

    
Yves Daoust 16.02.2015 23:08
quelle
3

Probieren Sie __movsq intrinsic (nur x64; in Assembly, rep movsq ) aus, die jeweils 8 Bytes mit einem geeigneten Wiederholungsfaktor verschieben und die Zieladresse 6 Bytes nach der Quelle setzen. Überprüfen Sie, ob überlappende Adressen intelligent gehandhabt werden.

    
Yves Daoust 16.02.2015 23:31
quelle

Tags und Links