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.
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.
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.
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
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.
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.
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.
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
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.
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).
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.
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.