Forum: Mikrocontroller und Digitale Elektronik Effizienz memcpy


von uC (Gast)


Lesenswert?

Hallo Leute,

jeder von euch kennt ja vermutlich memcpy (void *dest, const void *src, 
size_t len).

Den Sourcecode hierzu findet man hier: 
https://code.woboq.org/gcc/libgcc/memcpy.c.html

Allerdings frage ich mich, wie es um die Effizienz dieser Funktion 
bestellt ist, sobald man Datentypen kopieren will, welche nativ 16-bit 
oder 32 bit sind (z.B. uint16, uint32, float, etc.)

Der "size_t len" Parameter gibt ja die Länge in Form von byte an.
Will man also 32-bit floats kopieren, würde die memcpy faktisch 4 
Schleifendurchläufe machen und byte für byte einzeln kopieren obwohl 
eine typische 32-bit MCU einen 32-Bit wert auch in einem Durchlauf 
kopieren könnte.

Hat da jemand mal Benchmarks gemacht? Im Regelfall versuche ich auf DMA 
zu gehen wo möglich, manchmal ist die memcpy Funktion aber doch recht 
unerlässlich meiner Meinung nach.

von Programmierer (Gast)


Lesenswert?

uC schrieb:
> Den Sourcecode hierzu findet man hier:
> https://code.woboq.org/gcc/libgcc/memcpy.c.html

Ganz sicher dass das exakt die Version von memcpy ist die dein Programm 
nutzt? Die meisten memcpy-Versionen (z.B. die in der vom ARM-GCC 
verwendeten newlib) benutzen sehr clevere Optimierungen, teilweise auch 
in Assembler implementiert, welche sehr wohl 32bit oder mehr auf einmal 
kopieren, natürlich unter Beachtung des Alignments der beiden Pointer. 
Das schneller hinzubekommen dürfte schwierig werden; mit DMA vielleicht, 
wobei man auch hier sehr genau auf das Alignment achten muss.

von Helge (Gast)


Lesenswert?

was wird als assemblercode daraus gemacht? "Damals" für Z80/8086 gabs 
eine handmassierte schlanke (inline asm) memcpy (und eine memcpy16), das 
war viel schneller als das ausm compiler. Krieg ich aber nicht mehr 
zusammen. Zu lang her.

von Dergute W. (derguteweka)


Lesenswert?

Moin,

Helge schrieb:
> Krieg ich aber nicht mehr
> zusammen. Zu lang her.

Der Z80 hat memcpy praktisch als einzelnen Assemblerbefehl eingebaut: 
LDIR oder LDDR (Welcher ist wurscht, das wird nur bei memmove 
interessant). Muss halt nur noch vorher src*, dest*, und len im 
passenden HL,DE,BC Register rumliegen.

Gruss
WK

von (prx) A. K. (prx)


Lesenswert?

Dergute W. schrieb:
> Der Z80 hat memcpy praktisch als einzelnen Assemblerbefehl eingebaut:
> LDIR oder LDDR

Das bedeutet aber nicht, dass es der schnellste Weg ist. Unrolling ist 
schneller. 16x LDI ist schneller als 1x LDIR mit BC=16 (256 vs 331 
Takte). Zilog hatte das nämlich als externe Schleife implementiert, mit 
bedingtem Sprung auf sich selbst.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

uC schrieb:
> Hat da jemand mal Benchmarks gemacht?

Bei Prozessoren mit Cache, besonders bei mehreren Levels, kann die 
Optimierung von memcpy ziemlich kompliziert werden und sehr gute 
Kenntnis der Version des genutzten Prozessors erfordern.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Programmierer schrieb:
> Die meisten memcpy-Versionen (z.B. die in der vom ARM-GCC
> verwendeten newlib) benutzen sehr clevere Optimierungen, teilweise auch
> in Assembler implementiert, welche sehr wohl 32bit oder mehr auf einmal
> kopieren, natürlich unter Beachtung des Alignments der beiden Pointer.
> Das schneller hinzubekommen dürfte schwierig werden; mit DMA vielleicht,
> wobei man auch hier sehr genau auf das Alignment achten muss.

Bei gcc ist memcpy schon im Compiler selbst eingebaut. Was er aus dem 
Aufruf macht, hängt dadurch sehr von der Situation ab und führt nicht 
unbedingt zu einem Aufruf der tatsächlich in C implementierten Funktion. 
Der Compiler zieht dabei natürlich auch das Alignment mit in Betracht.

von Dergute W. (derguteweka)


Lesenswert?

Moin,

(prx) A. K. schrieb:
> Dergute W. schrieb:
>> Der Z80 hat memcpy praktisch als einzelnen Assemblerbefehl eingebaut:
>> LDIR oder LDDR
>
> Das bedeutet aber nicht, dass es der schnellste Weg ist. Unrolling ist
> schneller. 16x LDI ist schneller als 1x LDIR mit BC=16 (256 vs 331
> Takte). Zilog hatte das nämlich als externe Schleife implementiert, mit
> bedingtem Sprung auf sich selbst.

again what learned! Nachdem ich aber derlei Faxen auf einem ZX81 mit am 
Anfang nur 1KByte RAM gemacht hab', waere loop-unrolling auch wieder 
schmerzhaft beim Speicherverbrauch gewesen :-) Irgendwas ist immer...

Gruss
WK

von Andreas S. (Firma: Schweigstill IT) (schweigstill) Benutzerseite


Lesenswert?

Rolf M. schrieb:
> und führt nicht
> unbedingt zu einem Aufruf der tatsächlich in C implementierten Funktion.
> Der Compiler zieht dabei natürlich auch das Alignment mit in Betracht.

Darauf war ich vor ewiger Zeit auch mal hereingefallen, allerdings nicht 
mit dem GCC, sondern ARM ADS (Norcroft C). Ich setzte einen Breakpoint 
in der Bibliotheksversion, um einen bestimmten Speicherpinsler zu 
finden. Und wartete, wartete, wartete... Der Fehler trat aber trotzdem 
auf. Erst als ich mir den Maschinencode ansah, fiel mir auf, dass 
memcpy() an vielen Stellen direkt vom Compiler erzeugt wurde, und zwar 
gerne mit Hilfe von LDM und STM.

Die Bibliotheksfunktionen wurden dann verwendet, wenn nicht 
sichergestellt war, dass Quell- und Zielspeicher langwortausgerichtet 
waren.

: Bearbeitet durch User
von Jim M. (turboj)


Lesenswert?

Programmierer schrieb:
> Ganz sicher dass das exakt die Version von memcpy ist die dein Programm
> nutzt? Die meisten memcpy-Versionen (z.B. die in der vom ARM-GCC
> verwendeten newlib) benutzen sehr clevere Optimierungen,

Newlib ist optimiert, newlib-nano aber benutzt die o.g. Variante mit 
einzelnen Bytes weil die kleineren Code erzeugt.

Da kann man ergo alleine mit dem Ändern der Linker Settings drauf 
reinfallen.

Andreas S. schrieb:
> Erst als ich mir den Maschinencode ansah, fiel mir auf, dass
> memcpy() an vielen Stellen direkt vom Compiler erzeugt wurde, und zwar
> gerne mit Hilfe von LDM und STM.

Mach er auch bei direktem Kopiern via Pointer Variablen. Ich hatte mal 
den Fall dass er eine garantierte 8-Byte Kopie durch LDRD/STRD ersetzt 
hatte, welche dann wegen falschem Alignment abschmierte. Beim Cortex-M3 
(und anderen Cortex-M) sind LDRD und STRD die einzigen Befehle die 
4-Byte Alignment unbedingt haben wollen (abgesehen von FPU bei -M4).

von Programmierer (Gast)


Lesenswert?

Jim M. schrieb:
> Ich hatte mal den Fall dass er eine garantierte 8-Byte Kopie durch
> LDRD/STRD ersetzt hatte, welche dann wegen falschem Alignment
> abschmierte.

Das war dann aber kein memcpy Aufruf. Wenn das Ziel des Pointers z.B. 
double ist (bei ARM), darf der Compiler annehmen dass der Pointer 
4-Byte-Aligned ist und LDRD/STRD verwenden; wenn er falsch aligned ist, 
ist der Programmierer selbst schuld... memcpy hingegen funktioniert 
garantiert auch mit unaligned Pointern, daher die Fallunterscheidung in 
dessen Implementation.

von Andreas S. (Firma: Schweigstill IT) (schweigstill) Benutzerseite


Lesenswert?

Jim M. schrieb:
> Mach er auch bei direktem Kopiern via Pointer Variablen. Ich hatte mal
> den Fall dass er eine garantierte 8-Byte Kopie durch LDRD/STRD ersetzt
> hatte, welche dann wegen falschem Alignment abschmierte.

Dann hast Du vorher schon unzulässige Pointeroperationen durchgeführt, 
insbesondere wild herumgecastet.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.