35 Stimmen

Gibt es memset(), die ganze Zahlen größer als char akzeptiert?

Gibt es eine Version von memset(), die einen Wert setzt, der größer als 1 Byte (char) ist? Nehmen wir zum Beispiel an, wir haben eine Funktion memset32(), mit der wir Folgendes tun können:

int32_t array[10];
memset32(array, 0xDEADBEEF, sizeof(array));

Dadurch wird der Wert 0xDEADBEEF in allen Elementen des Arrays gesetzt. Derzeit scheint es mir, dies kann nur mit einer Schleife getan werden.

Konkret bin ich an einer 64-Bit-Version von memset() interessiert. Kennen Sie so etwas?

38voto

moonshadow Punkte 81155
void memset64( void * dest, uint64_t value, uintptr_t size )
{
  uintptr_t i;
  for( i = 0; i < (size & (~7)); i+=8 )
  {
    memcpy( ((char*)dest) + i, &value, 8 );
  }  
  for( ; i < size; i++ )
  {
    ((char*)dest)[i] = ((char*)&value)[i&7];
  }  
}

(Erklärung, wie in den Kommentaren gewünscht: Wenn Sie einem Zeiger zuweisen, nimmt der Compiler an, dass der Zeiger an der natürlichen Ausrichtung des Typs ausgerichtet ist; für uint64_t sind das 8 Byte. memcpy() macht keine solche Annahme. Auf mancher Hardware sind nicht ausgerichtete Zugriffe unmöglich, so dass die Zuweisung keine geeignete Lösung ist, es sei denn, Sie wissen, dass nicht ausgerichtete Zugriffe auf der Hardware mit geringen oder gar keinen Nachteilen funktionieren, oder Sie wissen, dass sie nie auftreten werden, oder beides. Der Compiler wird kleine memcpy()s und memset()s durch geeigneteren Code ersetzen, so dass es nicht so schrecklich ist, wie es aussieht; aber wenn Sie genug wissen, um zu garantieren, dass die Zuweisung immer funktioniert, und Ihr Profiler Ihnen sagt, dass sie schneller ist, können Sie das memcpy durch eine Zuweisung ersetzen. Die zweite for()-Schleife ist für den Fall vorgesehen, dass die Menge des zu füllenden Speichers kein Vielfaches von 64 Bit ist. Wenn Sie wissen, dass dies immer der Fall sein wird, können Sie diese Schleife einfach weglassen).

10voto

Steve Jessop Punkte 264569

Es gibt afaik keine Funktion in der Standardbibliothek. Wenn Sie also portablen Code schreiben, haben Sie es mit einer Schleife zu tun.

Wenn Sie nicht portierbaren Code schreiben, sollten Sie in der Dokumentation Ihres Compilers/Plattforms nachsehen, aber halten Sie nicht den Atem an, denn es ist selten, hier viel Hilfe zu bekommen. Vielleicht wird jemand anderes mit Beispielen von Plattformen, die etwas anbieten, beitragen.

Die Art und Weise, wie Sie Ihre eigene schreiben würden, hängt davon ab, ob Sie in der API definieren können, dass der Aufrufer garantiert, dass der dst-Zeiger ausreichend für 64-Bit-Schreibvorgänge auf Ihrer Plattform (oder Plattformen, falls portabel) ausgerichtet ist. Auf jeder Plattform, die überhaupt einen 64-Bit-Integer-Typ hat, gibt malloc zumindest entsprechend ausgerichtete Zeiger zurück.

Wenn Sie mit Nichtausrichtung zurechtkommen müssen, dann brauchen Sie etwas wie die Antwort von moonshadow. Der Compiler kann das memcpy mit einer Größe von 8 inline/unrollieren (und 32- oder 64-Bit unaligned write ops verwenden, wenn es sie gibt), so dass der Code ziemlich flink sein sollte, aber meine Vermutung ist, dass er wahrscheinlich nicht die ganze Funktion für das Ziel, das aligned ist, spezialisieren wird. Ich würde gerne korrigiert werden, aber ich fürchte, das wird nicht der Fall sein.

Wenn Sie also wissen, dass der Aufrufer Ihnen immer einen dst mit ausreichender Ausrichtung für Ihre Architektur und einer Länge, die ein Vielfaches von 8 Byte ist, geben wird, dann machen Sie eine einfache Schleife, die einen uint64_t (oder was auch immer der 64-Bit-Int in Ihrem Compiler ist) schreibt, und Sie werden wahrscheinlich (keine Versprechungen) mit schnellerem Code enden. Auf jeden Fall wird Ihr Code kürzer.

Wie auch immer, wenn Ihnen die Leistung wichtig ist, sollten Sie ein Profil erstellen. Wenn es nicht schnell genug ist, versuchen Sie es noch einmal mit weiteren Optimierungen. Wenn es immer noch nicht schnell genug ist, fragen Sie nach einer asm-Version für die CPU(s), auf denen es nicht schnell genug ist. memcpy/memset kann durch plattformspezifische Optimierung massive Leistungssteigerungen erzielen.

8voto

Evgeni Sergeev Punkte 20596

Nur für das Protokoll, die folgenden Verwendungen memcpy(..) nach dem folgenden Muster. Angenommen, wir wollen ein Array mit 20 ganzen Zahlen füllen:

--------------------

First copy one:
N-------------------

Then copy it to the neighbour:
NN------------------

Then copy them to make four:
NNNN----------------

And so on:
NNNNNNNN------------

NNNNNNNNNNNNNNNN----

Then copy enough to fill the array:
NNNNNNNNNNNNNNNNNNNN

Dies erfordert O(lg(num)) Anwendungen von memcpy(..) .

int *memset_int(int *ptr, int value, size_t num) {
    if (num < 1) return ptr;
    memcpy(ptr, &value, sizeof(int));
    size_t start = 1, step = 1;
    for ( ; start + step <= num; start += step, step *= 2)
        memcpy(ptr + start, ptr, sizeof(int) * step);

    if (start < num)
        memcpy(ptr + start, ptr, sizeof(int) * (num - start));
    return ptr;
}

Ich dachte, es könnte schneller sein als eine Schleife, wenn memcpy(..) wurde mit Hilfe von Hardware-Blockspeicher-Kopierfunktionen optimiert, aber es stellt sich heraus, dass eine einfache Schleife schneller ist als die obige mit -O2 und -O3. (Zumindest unter Verwendung von MinGW GCC auf Windows mit meiner speziellen Hardware.) Ohne den -O Schalter ist der obige Code auf einem 400 MB Array etwa doppelt so schnell wie eine äquivalente Schleife und benötigt 417 ms auf meinem Rechner, während mit Optimierung beide auf etwa 300 ms sinken. Das bedeutet, dass der Code ungefähr die gleiche Anzahl von Nanosekunden wie Bytes benötigt, und ein Taktzyklus ist etwa eine Nanosekunde. Also gibt es entweder keine Hardware-Blockspeicher-Kopierfunktionalität auf meinem Rechner, oder die memcpy(..) Die Umsetzung macht sich dies nicht zunutze.

6voto

Schauen Sie in der Dokumentation Ihres Betriebssystems nach, ob es eine lokale Version gibt, und erwägen Sie dann, nur die Schleife zu verwenden.

Der Compiler weiß wahrscheinlich mehr über die Optimierung des Speicherzugriffs auf einer bestimmten Architektur als Sie selbst, also lassen Sie ihn die Arbeit machen.

Packen Sie es als Bibliothek ein und kompilieren Sie es mit allen geschwindigkeitssteigernden Optimierungen, die der Compiler erlaubt.

5voto

Alex M Punkte 2438

wmemset(3) ist die breite (16-Bit) Version von memset. Ich denke, das ist das Beste, was man in C ohne Schleife machen kann.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X