Guten Morgen,
wie viel vorteilhafter sind die Funktionen memcpy und memcmp in c? Wenn
ich z. B. einen array mit 128 Einträgen habe. Lohnt es sich diese
Funktionen zu benutzen? Quasi könnte man das auch alles mit einer
for-loop machen. Wenn man diese Funktionen nicht verwenden würde,
bräuchte man auch die libs nicht zu inkludieren (es sei denn man
verwendet andere Funktionen aus dieser lib),was Platz sparen würde.
Kai
Kai schrieb:> Wenn man diese Funktionen nicht verwenden würde, bräuchte man auch die> libs nicht zu inkludieren (es sei denn man verwendet andere Funktionen> aus dieser lib),was Platz sparen würde.
Du glaubst also, dass deine eigene Funktion kleiner wäre? Da sie aber
nicht so stark optimiert ist, langsamer? Dann musst du wissen, was dir
wichtiger ist. Kompilieren beide Varianten und vergleiche das Ergebnis.
Kai schrieb:> Quasi könnte man das auch alles mit einer> for-loop machen.
Warum sollte man einen 1-Zeiler:
1
memcpy(...);
unnoetig aufblasen?
1
for(...){
2
a[i]=...;
3
}
Ein Vorteil von memcpy & Co.: Jeder sieht sofort am Namen was da
passiert. Bei einer Schleife muss man erstmal in die Schleife reingucken
und analysieren was da passiert.
Kai schrieb:> Lohnt es sich diese> Funktionen zu benutzen?
Na klar, warum sollte es sich nicht lohnen. Die Funktionen stehen nicht
umsonst in den Standardlibs eines jeden C-Compilers drin. ;)
Kai schrieb:> Quasi könnte man das auch alles mit einer> for-loop machen.
Dann schau doch mal in memcpy und memcmp rein ;)
Kai schrieb:> Wenn man diese Funktionen nicht verwenden würde,> bräuchte man auch die libs nicht zu inkludieren (es sei denn man> verwendet andere Funktionen aus dieser lib),was Platz sparen würde.
Das kommt auf deine Lösung an, das kann man nicht pauschal sagen, dass
eigene Funktionen Platz sparen ;)
Kai schrieb:> bräuchte man auch die libs nicht zu inkludieren (es sei denn man> verwendet andere Funktionen aus dieser lib),was Platz sparen würde.
Lustige Idee!
Zumindest über mein System kann ich sagen, dass keine unbenutzten Lib
Funktionen eingebunden werden.
Ist das bei dir anderes?
Warum?
Kai schrieb:> wie viel vorteilhafter sind die Funktionen memcpy und memcmp in c?
Die Frage ist unvollständig. Wieviel vorteilhafter im Vergleich womit?
Und welche Eigenschaften des Programms betrachtest du, um einen Vorteil
zu erkennen? Ausführungsgeschwindigkeit? Codegröße? Korrektheit?
> Wenn> ich z. B. einen array mit 128 Einträgen habe. Lohnt es sich diese> Funktionen zu benutzen?
Probiere es halt aus.
> Wenn man diese Funktionen nicht verwenden würde,> bräuchte man auch die libs nicht zu inkludieren
Libs werden nicht includiert. Hederfiles werden includiert. Libs werden
(dazu) gelinkt. Und es gibt kein Geld zurück, wenn man Libraryfunktionen
nicht verwendet. Genauso wie es auch kein Geld zurück gibt für
ungenutzten Flashspeicher.
Funktionen aus der Standardbibliothek sind meist hochoptimiert, nicht
selten sind wesentliche Teile in Assembler formuliert. In den meisten
Fällen sind diese Funktionen daher schneller und oft auch kleiner als
eine händisch hingeschriebene Lösch- oder Kopierschleife.
Die vier Funktionen memcmp, memcpy, memmove und memset sind im
Zusammenhang mit gcc speziell. Der Compiler darf jederzeit Aufrufe zu
diesen Funktionen generieren - auch ohne libc - und tut das, wenn
möglich auch.
Deine for-Schleife könnte also vom Compiler durch ein memcpy/memmove
ersetzt werden.
S. R. schrieb:> Die vier Funktionen … mit gcc speziell.
Nein. Nicht nur diese, und nicht nur mit GCC.
Alle Funktionen der Standardbibliothek sind „speziell“ und dürfen in
einem hosted environment vom Compiler so implementiert werden, dass er
implizites Wissen darüber besitzt, was sie tun.
Das ist eine gute Sache, denn ich kann beispielsweise schreiben:
1
#define MSG "foobar"
2
3
// …
4
5
memcpy(target,MSG,strlen(MSG));
… und der Compiler kann implizit auf eine ihm genehme Weise die 6
Zeichen des Strings "foobar" an die Zieladresse übertragen. Insbesondere
ist er nicht verpflichtet, für das strlen() irgendwie erst einen
Funktionsaufruf zu generieren, denn es ist völlig klar, dass dieser
immer eine 6 ergeben würde.
Auch ein memcpy() eines einzelnen Zeichens wird wohl so ziemlich jeder
Compiler auf diese Weise inline auflösen können, ohne erst irgendwelche
Funktionen zu bemühen. Schließlich und endlich könnte das System aus
Compiler und Bibliothek ("the implementation") auch Sonderfälle haben,
bei denen dem Compiler bekannt ist, dass unter bestimmten Umständen
Dinge optimierbar sind, die nicht direkt aus dem Quelltext ersichtlich
sind (bspw. Kopien in kompletten 32- oder 64-Bit-Einheiten durchführbar,
wenn die Adressen passend ausgerichtet sind).
Kai schrieb:> Quasi könnte man das auch alles mit einer for-loop machen.
Das wäre dann die worst-case-Implementierung. Eine solche primitive
Implementierung wird wohl jede Standardbibliothek allemal vorhalten
können, in der Regel aber (wie bereits geschrieben worden ist) wird sie
sehr wahrscheinlich eine bessere Implementierung anbieten können.
Aber dein Grund-Missverständnis war ja offensichtlich ohnehin, dass das
"Inkludieren" (also Linken) einer Bibliothek in irgendeiner Form da
gleich alles reinziehen würde, was sonst noch in der Bibliothek
vorhanden ist. Dem ist natürlich nicht so.
Kurz mein Senf dazu:
ich arbeite meist mit zeitkritischen Messsystemen, wo also 10 us schon
zu einer Verzögerung führen können. Da hab ich dann einfach mal memcpy
und eine for-loop zeitlich verglichen.
Also mit einem toggelden uC-Pin am Oszilloskop, bei mir kam meine ich
heraus, dass memcpy bei großen arrays zeit spart und eine for-loop bei
deinen 128 Bytes noch schneller sein sollte, eventuell wegen overheads
oder so, ich hab da jetzt nicht genauer reingeschaut und alle
eventualitäten abgeprüft, aber in meinem kruden Fall kam eben heraus das
eine for-loop zeit spart, jetzt dürfen sich die Experten melden wieso
das stimmt oder eventuell auch nicht :)
Viele Grüße.
keinerniemand schrieb:> eine for-loop bei deinen 128 Bytes noch schneller sein sollte, eventuell> wegen overheads oder so
Das hängt natürlich ganz entscheidend davon ab:
* welchen Prozessor du überhaupt benutzt
* wie "intelligent" der Compiler ist
Wie ich schon schrieb, ist es durchaus möglich, dass ein Compiler auch
solche Effekte "kennt" und berücksichtigen kann, d.h. einen
Funktionsaufruf nur dann generiert, wenn ihm klar ist, dass der einen
Vorteil bringt.
Pauschal kann man daher gar nichts sagen. Sowas im Einzelfall auf paar
Mikrosekungen mit der Hand zu "optimieren" riskiert stets, dass es mit
der nächsten Compilerversion wieder zerbricht, weil dort alles ganz
anders umgesetzt wird.
Kai schrieb:> Wenn> ich z. B. einen array mit 128 Einträgen habe. Lohnt es sich diese> Funktionen zu benutzen? Quasi könnte man das auch alles mit einer> for-loop machen.
Na eben.
Du prüfst nicht den gesamten Specherblock für 128 Einträge gleichzeitig,
sondern jeden Eintrag einzeln. Also wieder irgendeine Schleife. Ob da
memcmp wirlich was bringt....
Jörg W. schrieb:> S. R. schrieb:>> Die vier Funktionen … mit gcc speziell.>> Nein. Nicht nur diese, und nicht nur mit GCC.
Die Aussage von S.R. war ein andere, nämlich: Der GCC darf, wenn er den
Code des TO sieht, memcpy aufrufen obwohl der TO es nirgends nutzt.
Axel S. schrieb:> Libs werden> (dazu) gelinkt. Und es gibt kein Geld zurück, wenn man Libraryfunktionen> nicht verwendet.
Für den TO musst Du aber noch dazu sagen, dass aus eine LIB nur genau
die Einheiten ("~C-Files~") dazu gelinkt werden, die gebraucht werden.
Darum steht z.B. die Funtkion abs() (so sie als Funktion implementiert
ist) meist in einer eigenen Datei, damit nur genau diese 5 Bytes (oder
so) Code dazugelinkt werden.
Also auch wenn aller Code (printf und Co) einer Lib zusammen mehrere MB
belegen würde, der Compiler macht cherry-Picking. Bei Komplexen
Funktionen (z.b. printf) geht das allerdings nicht immer, so dass es
hier manchmal extra Compiler (Linker)-Schalter gibt, mit denen man z.B.
Floating-Point Unterstützung AN oder AUSschalten kann.
Kai schrieb:> Wenn man diese Funktionen nicht verwenden würde,> bräuchte man auch die libs nicht zu inkludieren (es sei denn man> verwendet andere Funktionen aus dieser lib),was Platz sparen würde.
Das verwenden der Lib-Funktionen macht Sinn. Man braucht sich mit einem
Debugger die Funktionen ansehen. Einfacherer Code geht nicht mehr.
Der Linker binden nur jenen Code ein, der verwendet wird! Es entsteht
durch die Verwendung von Librarys kein Overhead.
memcpy kopiert IMHO Byteweise. Mit einer for-Schleife könnte man 4Byte
Integer kopieren, was zumindest bei einer 32 Bit Architektur schneller
sein sollte.
Grüsse
Gebhard R. schrieb:> memcpy kopiert IMHO Byteweise.
Hat diese persönliche Meinung auch eine Quelle? Nein, es muss nur dazu
in der Lage sein, einzelne Bytes zu kopieren. Es kann sehr wohl
effizienter implementiert sein. Es könnte z.B. automatisch erkennen wenn
die Adressen 4-Byte-Aligned sind und dann entsprechende Instruktionen
nutzen, z.B. LDM und STM auf ARM.
Dr. Sommer schrieb:> Es könnte z.B. automatisch erkennen wenn> die Adressen 4-Byte-Aligned sind
könnte ist jetzt aber auch Konjunktiv! Weiß wer was genaueres?
Grüsse
Gebhard R. schrieb:> Weiß wer was genaueres?
Der Quelltext der C Bibliothek und des Compilers der jeweiligen
Plattform. Bei der newlib z.B. ist das so.
A. S. schrieb:>> Nein. Nicht nur diese, und nicht nur mit GCC.>> Die Aussage von S.R. war ein andere, nämlich: Der GCC darf, wenn er den> Code des TO sieht, memcpy aufrufen obwohl der TO es nirgends nutzt.
Wobei ich das noch nirgends in freier Wildbahn erlebt habe.
> Bei Komplexen> Funktionen (z.b. printf) geht das allerdings nicht immer, so dass es> hier manchmal extra Compiler (Linker)-Schalter gibt, mit denen man z.B.> Floating-Point Unterstützung AN oder AUSschalten kann.
Das liegt schlicht und ergreifend nicht daran, dass printf per se
"komplex" ist, sondern dass es erst zur Laufzeit anhand seiner
Format-Strings entscheidet, welche Umwandlungen wirklich erfolgen
müssen. Daher müssen sie als Bibliothekscode erst einmal alle verfügbar
sein. Für (zur Compileizeit) konstante Format-Strings könnte man sich
noch vorstellen, dass das System aus Compiler, Linker und Bibliothek da
irgendwelche Analysen und Entscheidungen durchführt, aber Format-Strings
lassen sich natürlich auch prinzipiell erst zur Laufzeit
zusammenbasteln.
Dr. Sommer schrieb:> Der Quelltext der C Bibliothek und des Compilers der jeweiligen> Plattform. Bei der newlib z.B. ist das so.
Hier zu bewundern:
https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=newlib/libc/string/memcpy.c
Es wird sogar nach Konfigurations-Einstellungen unterschieden.
Die newlib wird z.B. vom GCC-ARM-Embedded genutzt, welcher bei vielen
Cortex-M-IDEs mitgeliefert wird.
Jörg W. schrieb:> Daher müssen sie als Bibliothekscode erst einmal alle verfügbar> sein.
Genau - da steht im Code z.B. so etwas à la "if (format == 'f')
format_float(arg);". Dass dieser Fall nie eintritt, kann der Compiler
nicht sehen, insbesondere weil das ja bereits vorkompiliert in der
Bibliothek vorliegt.
Jörg W. schrieb:>>> Nein. Nicht nur diese, und nicht nur mit GCC.>> Die Aussage von S.R. war ein andere, nämlich: Der GCC darf, wenn er den>> Code des TO sieht, memcpy aufrufen obwohl der TO es nirgends nutzt.> Wobei ich das noch nirgends in freier Wildbahn erlebt habe.
Steht in der Manpage:
https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Link-Options.html
dort unter z.B. -nostdlib oder -nodefaultlibs:
"The compiler may generate calls to memcmp, memset, memcpy and memmove."
Wenn man größere Strukturen kopiert und auf Codegröße optimiert, dann
sieht man das auch in freier Wildbahn.
Wird dann interessant, wenn man memcpy implementieren möchte:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56888
Gebhard R. schrieb:>> Es könnte z.B. automatisch erkennen wenn>> die Adressen 4-Byte-Aligned sind>> könnte ist jetzt aber auch Konjunktiv! Weiß wer was genaueres?
Entscheidend ist nur, was hinten rauskommt. Es gibt klar definierte
Randbedingungen, innerhalb derer die Implementierung von memcpy tun
darf, was sie will. Viel genauer wirds nicht.
Berücksichtigung von Alignment ist ein uralter Hut.
Der Microcode von Intel/AMD x86 optimiert Befehle wie REP MOVS ebenfalls
abhängig von Alignment, Anzahl Interationen und wassweissich. Da dürften
dann auch die Caches Berücksichtigung finden.
Speziell zum GCC kann ich noch sagen:
Mit dem GCC (neue Versionen) habe ich festgestellt, dass Aufrufe von
memcpy() auch vom Compiler ausoptimiert werden können (er hat
tatsächlich nur noch LDR/STR Instruktionen inline generiert). Ich habe
es nicht im Detail nachverfolgt, sondern nur das passende Compiler Flag
gesucht womit man dies abschalten konnte. Hier nur ein Link als
Ansatzpunkt dafür, dass ich keinen Mist erzähle :-)
https://stackoverflow.com/questions/11747891/when-builtin-memcpy-is-replaced-with-libcs-memcpy
In vorgenannten Fall hatte der Compiler dies für ein relativ kleines und
zur Compilezeit bekanntes N getan. Aufgefallen ist das aber dadurch,
dass der Compiler dabei das Alignment nicht mehr korrekt berücksichtigte
was eine Exception generierte.
GCC Library Funktionen sind heute mehr auf Geschwindigkeit wie Größe
ausgelegt. Entsprechend kann es dann sinnvoller sein die Library zu
nutzen. Natürlich muss die Problemgröße hinreichend groß sein, damit
diese Funktionen ihren Vorteil ausspielen können. Die Geschwindigkeit
wird i.d.R. über Konstruktionen wie Duff's device
https://de.wikipedia.org/wiki/Duff%E2%80%99s_Device
und plattformspezifische Optimierungen (z.B. Laden/Speichern via LDM/STM
unter Nutzung mehrerer Register). Typisch auch das Erweitern der
Datengröße bis Erreichung des maximalen Alignments (LDRB/STRB =>
LDRH/STRH => LDR/STR).
Falls hingegen die Größe der Funktion ein Argument ist (eher
Embedded-Bereich), kann es sinnvoll sein diese Implementierungen durch
eigene zu ersetzen. Wir tun dies, wobei diese Implementierungen
zusätzlich NULL Pointer-Safe sind und mit überlappenden
Speicherbereichen generell korrekt funktionieren. Dies ist btw. der
subtile Unterschied zwischen Standard memcpy() und memmove().
x^y schrieb:> GCC Library Funktionen sind heute mehr auf Geschwindigkeit wie Größe> ausgelegt.
Kannst du so pauschal nicht sagen, denn es gibt nicht allgemein „GCC
Library Funktionen“. Die Standardbibliothek wird nicht vom GCC-Team
selbst gepflegt, sondern es gibt sehr verschiedene Implementierungen
dafür: glibc, BSD libc, newlib (teils BSD, teils aus anderen Quellen
zusammengekramt), avr-libc, …
> Wir tun dies, wobei diese Implementierungen> zusätzlich NULL Pointer-Safe sind und mit überlappenden> Speicherbereichen generell korrekt funktionieren. Dies ist btw. der> subtile Unterschied zwischen Standard memcpy() und memmove().
Eben. Daher halte ich es für unsinnig, auch memcpy() wie memmove() zu
implementieren, denn das verhindert Optimierungen. Das ist ja auch der
einzige Grund, warum es überhaupt für praktisch die gleiche Aufgabe im
Standard zwei verschiedene Funktionen gibt.
Auch NULL-Pointer-safe finde ich Quatsch, es hilft höchstens, Bugs zu
kaschieren statt sie zu entdecken.
Jörg W. schrieb:> Eben. Daher halte ich es für unsinnig, auch memcpy() wie memmove() zu> implementieren, denn das verhindert Optimierungen. Das ist ja auch der> einzige Grund, warum es überhaupt für praktisch die gleiche Aufgabe im> Standard zwei verschiedene Funktionen gibt.>> Auch NULL-Pointer-safe finde ich Quatsch, es hilft höchstens, Bugs zu> kaschieren statt sie zu entdecken.
Kann man so sehen, unsere Sicht bzw. Philosophie ist da anders. M.M.
gibt es da kein "richtig" und "falsch" weswegen ich unsere Gründe gerne
darlege:
Unser Ansatz ist undefiniertes Verhalten nach bester Möglichkeit auf
allen Ebenen zu verhindern, indem definiertes Verhalten implementiert
wird. Ebenen können sein: Einzelne Anweisungen (z.B. ++x + x++),
Funktionen, Module, Libraries, usw. Für uns hat sich gezeigt, dass dies
zu sehr zuverlässiger Software führt und nicht etwa das Auffinden von
Fehlern erschwert.
Wenn ich sagen würde, dass es gut ist Funktionen zu nutzen, so dass
undefiniertes Verhalten entstehen kann, muss ich konsequenterweise doch
auch sagen, dass es gut ist Anweisungen auszuführen bei denen
undefiniertes Verhalten entstehen kann - z.B. Signed overflow nicht
durch Checks/Limitierung zu verhindern. Das kann aber nur in Situationen
richtig sein, in denen ich mir sicher bin, dass diese Fälle -nicht
eintreten können- wodurch ich dann einen Optimierungsvorteil habe bzw.
gewinne. Wenn diese Fälle aber eintreten können, darf ich so nicht
denken. Dann erlaube ich die Entstehung von undefiniertem Verhalten was
keinesfalls zu einer "Erkennung" des Fehlers führen muss.
Zieht man unseren Ansatz konsequent durch, zeigt sich oft, dass ein
Fehler entweder gar keinen Effekt mehr hat oder auf höherer Ebene
automatisch behandelt wird. Es ist sehr schwierig das zu erklären oder
glaubhaft zu machen, man muss es in gewisser Weise einfach erlebt haben
:-)
Ich versuche mich trotzdem an einem Beispiel:
1
if (0 == memcmp(user_getname(), "Hans"))
2
{
3
// greet Hans here
4
send("Hi Hans, how are you?");
5
}
Fiktives Beispiel, aber hätte sich kein Benutzer angemeldet, würde die
Funktion user_getname() NULL zurückliefern, was der Programmierer hier
übersehen hätte. Was wäre die Konsequenz?
a) Klassisches memcmp()
Wenn an Adresse 0 kein Speicher vorliegt, undefiniertes Verhalten.
Wahrscheinlich Exception/Neustart/Absturz. Wenn an Adresse 0 Speicher
ist, undefiniertes Ergebnis, vermutlich FALSE aber mit einer geringen
Restwahrscheinlichkeit TRUE. Auch hier bliebe erstmal eine prompte
"Erkennung" des Fehler aus, da keine Exception, jedoch ist -das Ergebnis
undefiniert- und das ist der Unterschied zur Variante b.
b) memcmp() NULL pointer safe
Definition der Funktion könnte sein: Vergleiche mit NULL Pointer liefern
immer FALSE, außer NULL selbst. Benutzer ist also immer nicht "Hans".
Dies muss korrekt sein, definiert man NULL als Zeiger "auf Nichts", dann
kann "etwas" ja nie identisch sein zu "nichts". Das Verhalten von
memcmp() ist also nicht unlogisch oder falsch, aber eben -immer
definiert-.
A. K. schrieb:> Gebhard R. schrieb:>>> Es könnte z.B. automatisch erkennen wenn>>> die Adressen 4-Byte-Aligned sind>>>> könnte ist jetzt aber auch Konjunktiv! Weiß wer was genaueres?>> Entscheidend ist nur, was hinten rauskommt. Es gibt klar definierte> Randbedingungen, innerhalb derer die Implementierung von memcpy tun> darf, was sie will. Viel genauer wirds nicht.>> Berücksichtigung von Alignment ist ein uralter Hut.>> Der Microcode von Intel/AMD x86 optimiert Befehle wie REP MOVS ebenfalls> abhängig von Alignment, Anzahl Interationen und wassweissich. Da dürften> dann auch die Caches Berücksichtigung finden.
memcpy() ist wichtig. Da wird sich wohl jede C-Implementierung bemühen,
das möglichst effizient zu machen. Und wenn es auf Alignment ankommt,
wird die Implementierung das berücksichtigen.
Das gilt auch für andere oft verwendete Bibliotheksfuntionen wie
memcmp(), memset(), strchr(), strcpy(), strncpy(), etc.
Philipp
x^y schrieb:> Definition der Funktion könnte sein: Vergleiche mit NULL Pointer liefern> immer FALSE, außer NULL selbst. Benutzer ist also immer nicht "Hans".> Dies muss korrekt sein, definiert man NULL als Zeiger "auf Nichts", dann> kann "etwas" ja nie identisch sein zu "nichts". Das Verhalten von> memcmp() ist also nicht unlogisch oder falsch, aber eben -immer> definiert-.
Anstatt Bugs/Pfusch zu verstecken, könnte (sollte!) man das Programm
hier einfach kontrolliert abstürzen lassen.
Jemand schrieb:> Anstatt Bugs/Pfusch zu verstecken, könnte (sollte!) man das Programm> hier einfach kontrolliert abstürzen lassen.
Und wie genau willst du das sicherstellen?
x^y schrieb:> Jemand schrieb:>> Anstatt Bugs/Pfusch zu verstecken, könnte (sollte!) man das Programm>> hier einfach kontrolliert abstürzen lassen.>> Und wie genau willst du das sicherstellen?
Indem Zugriff auf einen Nullpointer halt eine Exception wirft.
Aber wir sind jetzt schon ziemlich weit weg vom Ursprungsthema. :)
A. S. schrieb:> Für den TO musst Du aber noch dazu sagen, dass aus eine LIB nur genau> die Einheiten ("~C-Files~") dazu gelinkt werden, die gebraucht werden.
Mit funktionierender LTO kann es durchaus sein dass nur ein kleiner Teil
vom Code einer Lib/funktion.c im Binary landet..