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

Lesezeit: 7 Minuten

Benutzer-Avatar
gnobal

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(), also können wir mit ihr Folgendes tun:

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

Dadurch wird der Wert 0xDEADBEEF in allen Elementen des Arrays gesetzt. Derzeit scheint mir dies nur mit einer Schleife möglich zu sein.

Insbesondere interessiere ich mich für eine 64-Bit-Version von memset(). Kennen Sie so etwas?

Benutzer-Avatar
Mondschatten

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 angefordert: Wenn Sie einem Zeiger zuweisen, geht der Compiler davon aus, dass der Zeiger an der natürlichen Ausrichtung des Typs ausgerichtet ist; für uint64_t sind das 8 Bytes. memcpy() macht keine solche Annahme. Auf einigen Hardware nicht ausgerichtet Zugriffe sind unmöglich, daher ist die Zuweisung keine geeignete Lösung, es sei denn, Sie wissen, dass nicht ausgerichtete Zugriffe auf der Hardware mit geringen oder keinen Nachteilen funktionieren oder wissen, dass sie niemals auftreten werden, oder beides. Der Compiler ersetzt kleine memcpy()s und memset() s mit geeigneterem Code, damit es nicht so schrecklich ist, wie es aussieht; aber wenn Sie genug wissen, um sicherzustellen, dass die Zuweisung immer funktioniert, und Ihr Profiler Ihnen sagt, dass es schneller ist, können Sie die memcpy durch eine Zuweisung ersetzen. Schleife ist vorhanden, falls die zu füllende Speichermenge kein Vielfaches von 64 Bit ist. Wenn Sie wissen, dass dies immer der Fall sein wird, können Sie diese Schleife einfach löschen.)

  • Diese Implementierung ist mehr, als ich mit der Frage erwartet habe 🙂 Danke! Es wäre schön gewesen, wenn du die Umsetzung erklärt hättest. Ich kann zum Beispiel nicht verstehen, warum ein Funktionsaufruf für memcpy() anstelle einer Zuweisung verwendet wird.

    – gnobal

    21. September 2008 um 8:20 Uhr

Benutzer-Avatar
Steve Jessop

Es gibt keine Standardbibliotheksfunktion afaik. Wenn Sie also portablen Code schreiben, sehen Sie sich eine Schleife an.

Wenn Sie nicht-portablen Code schreiben, sehen Sie in Ihrer Compiler-/Plattform-Dokumentation nach, aber halten Sie nicht den Atem an, da Sie hier selten viel Hilfe bekommen. Vielleicht meldet sich noch jemand mit Beispielen von Plattformen, die etwas bieten.

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

Wenn Sie mit Nichtausrichtung fertig werden müssen, brauchen Sie so etwas wie die Antwort von moonshadow. Der Compiler kann diesen Memcpy mit einer Größe von 8 inline/unrollieren (und 32- oder 64-Bit-nicht ausgerichtete Schreiboperationen verwenden, falls vorhanden), daher sollte der Code ziemlich flott sein, aber ich vermute, dass dies wahrscheinlich kein Sonderfall ist die gesamte Funktion für das Ziel wird ausgerichtet. Ich würde gerne korrigiert werden, aber fürchte, ich werde es nicht 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 Bytes ist, gibt, dann schreiben Sie eine einfache Schleife und schreiben Sie ein uint64_t (oder was auch immer das 64-Bit-Int in Ihrer ist Compiler) und Sie werden wahrscheinlich (kein Versprechen) mit schnellerem Code enden. Sie werden sicherlich kürzeren Code haben.

Was auch immer der Fall ist, wenn Ihnen Leistung wichtig ist, dann profilieren Sie sie. Wenn es nicht schnell genug ist, versuchen Sie es erneut mit mehr Optimierung. Wenn es immer noch nicht schnell genug ist, stellen Sie eine Frage zu einer asm-Version für die CPU(s), auf denen es nicht schnell genug ist. memcpy/memset kann durch die Optimierung pro Plattform massive Leistungssteigerungen erzielen.

  • @Steve Jessop, bitte erläutern Sie mir die Überlegungen zur 64-Bit-Windows- oder Linux-Ausrichtung.

    – Frank

    8. August 2015 um 8:30 Uhr

Benutzer-Avatar
Jewgeni Sergejew

Nur fürs Protokoll, die folgenden Verwendungen memcpy(..) in folgendem Muster. Angenommen, wir möchten ein Array mit 20 Ganzzahlen 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 einigen Funktionen zum Kopieren von Hardware-Blockspeichern optimiert, aber es stellt sich heraus, dass eine einfache Schleife schneller ist als die obige mit -O2 und -O3. (Zumindest mit MinGW GCC unter Windows mit meiner speziellen Hardware.) Ohne den Schalter -O ist der obige Code auf einem 400-MB-Array etwa doppelt so schnell wie eine äquivalente Schleife und dauert auf meinem Computer 417 ms, während sie optimiert werden beide gehen auf etwa 300 ms. Das bedeutet, dass es ungefähr die gleiche Anzahl von Nanosekunden wie Bytes dauert und ein Taktzyklus etwa eine Nanosekunde dauert. Also gibt es auf meinem Rechner entweder keine Funktion zum Kopieren des Hardware-Blockspeichers oder die memcpy(..) Die Umsetzung nutzt sie nicht aus.

  • Moderne Prozessoren können eine einfache Schleife schnell genug ausführen, um den Speicherbus zu sättigen, wodurch Anweisungen zum Verschieben/Kopieren von Blöcken überflüssig werden.

    – Markieren Sie Lösegeld

    28. Oktober 2014 um 22:30 Uhr

  • @MarkRansom Ich hatte gehofft, dass es eine Möglichkeit mit einer einzigen Anweisung gibt, um zB eine ganze Seite auf Nullen zu setzen. Denn es ist denkbar, dass ein bestimmtes Design eines RAM-Chips es ermöglicht, diese Funktionalität kostenlos hinzuzufügen. Aber es ist so viel einfacher zu überprüfen, ob das passiert, als die Spezifikation für die Technologie oder sogar den richtigen Fachbegriff dafür zu finden.

    – Jewgeni Sergejew

    30. Oktober 2014 um 1:27 Uhr

  • @evgeniSergeev, Sie könnten das Betriebssystem bitten, die Seite auf Nullen neu zuzuordnen, was auf einigen Plattformen möglicherweise schneller ist als Speicherzugriffe. Auch dies setzt voraus, dass das Betriebssystem Ihnen einige vorgelöschte Seiten zur Verfügung hat.

    – rsaxvc

    23. Februar 2018 um 17:26 Uhr

Suchen Sie in Ihrer Betriebssystemdokumentation nach einer lokalen Version und ziehen Sie dann in Betracht, nur die Schleife zu verwenden.

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

Fassen Sie es als Bibliothek zusammen und kompilieren Sie es mit allen geschwindigkeitsverbessernden Optimierungen, die der Compiler zulässt.

Benutzer-Avatar
Alex M

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

  • -1 für 16-Bit. Es ist wchar_t Dies ist 32-Bit bei jeder Implementierung, die Unicode ordnungsgemäß unterstützt. Es ist nur 16-Bit unter Windows, das den C-Standard ignoriert und UTF-16 in speichert wchar_t.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    7. August 2010 um 18:45 Uhr

Benutzer-Avatar
Kosmin

Wenn Sie nur auf einen x86-Compiler abzielen, können Sie Folgendes versuchen (VC++-Beispiel):

inline void memset32(void *buf, uint32_t n, int32_t c)
{
  __asm {
  mov ecx, n
  mov eax, c
  mov edi, buf
  rep stosd
  }
}

Andernfalls machen Sie einfach eine einfache Schleife und vertrauen Sie darauf, dass der Optimierer weiß, was er tut, einfach so etwas wie:

for(uint32_t i = 0;i < n;i++)
{
  ((int_32 *)buf)[i] = c;
}

Wenn Sie es kompliziert machen, ist es wahrscheinlich, dass es am Ende langsamer als einfacher ist, Code zu optimieren, ganz zu schweigen davon, dass es schwieriger zu warten ist.

  • -1 für 16-Bit. Es ist wchar_t Dies ist 32-Bit bei jeder Implementierung, die Unicode ordnungsgemäß unterstützt. Es ist nur 16-Bit unter Windows, das den C-Standard ignoriert und UTF-16 in speichert wchar_t.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    7. August 2010 um 18:45 Uhr

Benutzer-Avatar
Kervin

Sie sollten wirklich den Compiler dies für Sie optimieren lassen, wie jemand anderes vorgeschlagen hat. In den meisten Fällen wird diese Schleife vernachlässigbar sein.

Aber wenn dies eine besondere Situation ist und es Ihnen nichts ausmacht, plattformspezifisch zu sein, und Sie die Schleife wirklich loswerden müssen, können Sie dies in einem Montageblock tun.

//pseudo code
asm
{
    rep stosq ...
}

Sie können wahrscheinlich den stosq-Assembly-Befehl für die Einzelheiten googeln. Es sollte nicht mehr als ein paar Zeilen Code sein.

1383400cookie-checkGibt es memset(), das ganze Zahlen akzeptiert, die größer als char sind?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy