Nutzen Sie ARM unaligned memory access) beim schreiben von sauberen C-code
Es verwendet zu sein, dass ARM-Prozessoren nicht in der Lage waren richtig zu handhaben, unaligned memory access (ARMv5 und unten). So etwas wie u32 var32 = *(u32*)ptr;
würde fehlschlagen (raise exception), wenn ptr
war nicht korrekt ausgerichtet auf 4-bytes.
Schreiben eine solche Anweisung würde funktionieren, für x86/x64, aber, da diese CPU haben immer behandelt diese situation sehr effizient. Aber laut C standard ist dies nicht eine "richtige" Art zu schreiben. u32
ist offenbar äquivalent zu einer Struktur von 4 bytes, die muss ausgerichtet werden 4 bytes.
Einen richtigen Weg, um das gleiche Ergebnis zu erzielen, während die Orthodoxie Richtigkeit und gleichzeitig die volle Kompatibilität mit jeder cpu ist :
u32 read32(const void* ptr)
{
u32 result;
memcpy(&result, ptr, 4);
return result;
}
Dies ist korrekt, erzeugen richtige code für any cpu oder nicht in der Lage zu Lesen nicht ausgerichteter Positionen. Noch besser ist, auf x86/x64, es ist richtig optimiert zu einem einzigen Lesevorgang, hat daher die gleiche Leistung wie die erste Aussage. Ist es tragbar, sicher und schnell. Wer kann mehr Fragen ?
Gut, problem ist, auf der ARM, wir sind nicht so glücklich.
Schreiben der memcpy
version ist in der Tat sicher, aber es scheint in der Folge der systematischen vorsichtig Operationen, die sehr langsam für ARMv6 und ARMv7 (im Grunde jedes smartphone).
In eine performance-orientierte Anwendung, die stützt sich stark auf read-Operationen ist der Unterschied zwischen der 1. und 2. Fassung gemessen werden konnte : es steht bei > 5x bei gcc -O2
Einstellungen. Das ist viel zu viel, um ignoriert zu werden.
Versuchen, einen Weg finden, um ARMv6/v7-Fähigkeiten, habe ich mich für die Leitung auf ein paar Beispiel-codes um. Leider scheinen Sie zu select die erste Anweisung (direkte u32
access), der ist eigentlich nicht korrekt zu sein.
Das ist nicht alles : neue GCC-Versionen sind jetzt zu versuchen, auto-Vektorisierung. Auf x64, das bedeutet, dass die SSE/AVX, auf ARMv7-das bedeutet, dass NEON. ARMv7 unterstützt auch einige neue "Laden Mehrere" (LDM) und "Speichern von Mehreren" (STM) - opcodes, die erfordern Zeiger ausgerichtet werden.
Was bedeutet das ? Auch der compiler ist frei, nutzen Sie diese erweiterte Anleitung, auch wenn Sie nicht ausdrücklich genannt, aus dem C-code (keine innere). Eine solche Entscheidung, es verwendet die Tatsache, die eine u32* pointer
soll ausgerichtet werden auf 4 Byte. Wenn es nicht ist, dann sind alle Wetten ab : undefiniert Verhalten, Abstürze.
Was das bedeutet, ist, dass auch auf der CPU, die Unterstützung unaligned memory access, es ist jetzt gefährlich, zu verwenden direkte u32
zugreifen, ebenso kann es zu fehlerhaftem code generation an high-Optimierung Einstellungen (-O3
).
So, jetzt, das ist ein Dilemma : wie Sie den Zugriff auf die native performance von ARMv6/v7 auf unaligned memory access ohne schreiben Sie die falsche version u32
Zugang ?
PS : ich habe auch versucht __packed()
Anweisungen, und aus einem performance-Sicht, Sie scheint zu funktionieren genau die gleiche wie die memcpy
Methode.
[Edit] : Dank für die ausgezeichnete Elemente bisher erhalten.
Blick auf die generierte assembly, konnte ich bestätigen @Notlikethat Feststellung, dass memcpy
version hat in der Tat erzeugen die richtige ldr
opcode (nichtlineare Last). Allerdings habe ich auch festgestellt, dass die generierte assembly nutzlos ruft str
(Befehl). Also der komplette Vorgang wird nun eine nichtlineare Last, ein ausgerichtet-Shop, und dann ein Finale ausgerichtet zu laden. Das ist eine Menge mehr Arbeit als nötig.
Beantworten @haneefmubarak, ja der code ist richtig eingebettet. Und Nein, memcpy
ist sehr weit von der Bereitstellung der bestmöglichen Geschwindigkeit, da zwingen, den code zu akzeptieren, direkte u32
access übersetzt in große performance-Gewinne. So manche bessere Möglichkeit geben muss.
Einen großen Dank an @artless_noise. Der link zu godbolt service ist unvaluable. Ich habe nie in der Lage, um zu sehen, so deutlich die äquivalenz zwischen einem C-source-code und dessen Montage-Darstellung. Das ist sehr inspirierend.
Absolvierte ich eine @artless Beispiele, und es gibt die folgenden :
#include <stdlib.h>
#include <memory.h>
typedef unsigned int u32;
u32 reada32(const void* ptr) { return *(const u32*) ptr; }
u32 readu32(const void* ptr)
{
u32 result;
memcpy(&result, ptr, 4);
return result;
}
einmal compiliert mit ARM-GCC 4.8.2 at-O3 oder -O2 :
reada32(void const*):
ldr r0, [r0]
bx lr
readu32(void const*):
ldr r0, [r0] @ unaligned
sub sp, sp, #8
str r0, [sp, #4] @ unaligned
ldr r0, [sp, #4]
add sp, sp, #8
bx lr
Recht aufschlussreich ....
- Ich bezweifle, dass Sie werde in der Lage sein, etwas zu finden, schneller als memcpy, leider.
- Ist es nicht gefährlich, zu verwenden u32. Es ist gefährlich zu sagen, der compiler, dass Sie es besser wissen als er, was dem, was es ist, Zugriff auf (explicit casting), wenn dies in der Tat nicht wahr.
- Keine repro. Mit einem Linaro GCC 4.8.3, mit-march=armv6 und O1 die oben genannten Funktion, kompiliert im wesentlichen
ldr r0, [r0]; str r0, [sp, #4]; ldr r0, [sp, #4]
. Scham kann es nicht umgehen, aber die Verwendung der lokalen Variablen ganz, aber es ist Ihr nicht ausgerichtete Wort-laden, direkt dort; kein multiple-byte-Lasten-oder out-of-line-Aufruf von memcpy. - Zum Beispiel godbolt gibt der realen Produktion und der ein Beispiel mit main.
- Vielen Dank für diese aufschlussreiche Elemente. Ich habe aktualisiert die Frage mit neuen Informationen Dank godbolt.
- Ehrlich gesagt, ich denke, die nutzlos-Stapel berühren, ist wahrscheinlich nur ein bug/fehlende Optimierung im Bereich der ARM-GCC. Mit AArch64 GCC, die unoptimised code sieht so aus ARM-code; auf -O1 kompiliert das ganze zu
ldr w0, [x0]; ret
- Ich kann verstehen und akzeptieren, das argument, dass der GCC ist einfach fehlende eine triviale Optimierung. That being said, GCC ist der compiler gibt, und ich kann nicht einfach standardmäßig auf "hey, die performance ist Mist, aber es ist der Fehler von GCC". Ich habe eine Lösung zu finden, und es muss heute arbeiten, mit aktuellen Compilern.
- Yup, Clang stimmt, die der GCC ist einfach nur Müll.
- ja, natürlich, die Sie brauchen, um menschenwürdige Leistung mit dem Compiler heraus dort heute. Aber Sie haben nicht noch gezeigt, die alle richtigen code verursacht suboptimale Verhalten - und wenn das, was Sie arbeiten, auf (die das Beispiel, das Sie gepostet haben zeigt) ist immer illegal, C-code-arbeiten durch einfügen von code-Beilagen zu befassen sich mit falsch angegebenen Ausrichtung von Variablen, stattdessen fixieren Sie die falsche Ausrichtung Spezifikationen, die Sie nicht bekommen, da unabhängig von compiler. Jede chance, Sie könnten ... ich weiß nich, post eine neue Frage mit einer bestimmten code Abfolge generiert suboptimalen code?
- um zu klären - ich möchte schreiben, eine Antwort, aber sehe nicht ganz, wie kann ich mit der Art, wie diese Frage geschrieben. Kurze riskieren, wie es sich in einer generic "get off my lawn"-Stil semi-rant über die übel von C-code, der konnte immer nur Arbeit auf x86.
- es ist nicht so einfach. Wenn Sie x86-Ziel, sogar mit GCC erhalten Sie die gleichen kompakten Baugruppe Ausgang. Das problem scheint spezifisch für GCC/ARM-Kombination; und godbolt kein Klappern - /Arm-Kombination zu vergleichen.
- Hier ist ein Beispiel-code, wenn Sie möchten
- auf Ihre Reaktion auf Notlikethat) um genau Zu sein, das problem scheint spezifisch für die nicht-x86/GCC-Kombination. Wie für den code, den Sie verlinkt sind, ist es im Grunde tut, ist damndest um den Kampf der compiler. Wenn ptr ist eigentlich ein u32 gespeichert nicht, warum nicht passieren, es als ein Zeiger auf ein __attribute__((packed)) struct holding nur ein u32-Wert, und lassen Sie den compiler Sortieren Sie die Ausrichtung Gefummel aus? Wie so?: goo.gl/PDevc2
- dies ist auch mein Aktuelles Fazit, aber ich hatte gehofft, zu finden. Das problem habe ich mit gepackten Attribut ist, dass es nicht standard ist C : es ist compiler-spezifische Erweiterung. Als Folge, Ausdruck diese Eigenschaft anders ist das bei jedem compiler, und manchmal unterscheidet sich auch je nach compiler-version. Für einen portablen code, das ist ein Albtraum zu verwalten. Das ist, warum ich zunächst eingeschaltet zu
memcpy
version, die ist viel sauberer zu Lesen und ist voll der standard (zumindest so lange wie die standard libs vorhanden sind ...) - Lassen Sie uns weiter, diese Diskussion im chat.
- Huh? Das Beispiel, das ich verbunden ist, mit dem ARM LLVM-backend (daher das "-Ziel arm" - option, um Clang) - seit Wann war
bx lr
eine x86-Instruktion? Ja, das ist eine GCC/ARM-problem, das ist eher mein Punkt - den anderen ARM-gezielte Compiler Optimierung der memcpy zu einer einzigen nichtlinearen Last (zumindest habe ich überprüft, Clang und armcc, ich habe keine andere wie IAR oder die TI eine hand zum testen). GCC 5.2 ist immer noch doof. Realistisch, ich bezweifle eher, dass es eine einfache Lösung, die klar, richtig, portable, optimale und überall funktioniert, um einen GCC-Fehler Leistung... - ja sorry. Der compiler sagt "clang x86" und ich Las es wie "x86-gcc". Aber diese Compiler haben in der Tat verschiedene Wege im Umgang mit der mehrere Ziele : gcc braucht eine andere binäre name für jede Plattform, während clang man es von command-line-parameter.
- Ich nehme an, das bezog sich auf die xxhash und anderen Ihre Projekte. Da wir wissen, dass memcpy ist wahrscheinlich der Schnellste Weg, um Lesen Sie nicht ausgerichteten Speicher, warum nicht Sie memcpy, um einen Puffer von 128 int-Werte oder so etwas wie, und dann verwenden Sie anstelle von memcpy Sie eins nach dem anderen?..
- Du meinst, für die CPU das nicht unterstützen, unaligned access ? Das ist wohl eine gute Idee für die lese-performance auf solche Gegner. Es ist jedoch auch verbrauchen Speicher (128x4=512 bytes). Während es scheint nicht viel, einige Umgebungen, wie kernel-space zum Beispiel, wird es nicht mögen. Es gibt auch Mikro-threads-Umgebungen, wo jeder thread hat eine kleine Menge von stack-Speicherplatz zur Verfügung. Trotzdem, was ich meine ist, daß dieser trade-off kann vom user-space scheint aber out of scope für eine Bibliothek, wie xxhash, wie es wäre, Begrenze die Anzahl der kompatiblen Umgebungen.
Du musst angemeldet sein, um einen Kommentar abzugeben.
OK, die situation ist mehr verwirrend, als man möchte. So, in einer Bemühung um zu klären, hier sind die Erkenntnisse auf dieser Reise :
Zugriff auf unaligned Speicher
memcpy
ein. Ich hoffte auf ein weiteres über diese Frage, aber anscheinend ist es die einzige bisher Gefundene.Beispiel-code :
Diese Lösung ist unter allen Umständen sicher. Es kompiliert auch in eine triviale
load register
Betrieb auf x86-target-GCC verwenden.Jedoch, die auf der ARM-target-GCC verwenden, übersetzt es in einen viel zu großen und nutzlos Montage-Sequenz, die abbricht Leistung.
Mit Clang auf ARM-target
memcpy
funktioniert (siehe @notlikethat Kommentar unten). Es wäre einfach, die Schuld GCC zu groß, aber es ist nicht so einfach : diememcpy
Lösung funktioniert gut auf GCC mit x86/x64, PPC-und ARM64-Ziele. Schließlich versuchen, einen anderen compiler, icc13, die memcpy-version ist überraschend schwerer auf x86/x64 - (4-Anweisungen, während eine sollte reichen). Und das sind nur die Kombinationen, die ich testen konnte bisher.Habe ich zu danken godbolt ' s Projekt, um solche Aussagen machen leicht zu beobachten.
__packed
Strukturen. Diese Lösung ist nicht C-standard, und hängt ganz von compiler-Erweiterung. Als Konsequenz, die Art und Weise zu schreiben, hängt es von der compiler, und manchmal auf seine version. Dies ist ein Durcheinander für die Wartung von portablen code.Dass gesagt wird, in den meisten Fällen, es führt zu einer besseren code-Generierung als
memcpy
. In den meisten Fällen nur ...Zum Beispiel in Bezug auf die oben genannten Fällen, in denen
memcpy
Lösung funktioniert nicht, hier sind die Ergebnisse :__packed
Lösung funktioniert__packed
Lösung funktioniertauf ARMv6 mit GCC : funktioniert nicht. Die Montage sieht sogar noch hässlicher als
memcpy
.u32
Zugriff auf unaligned memory-Positionen. Diese Lösung verwendet, um die Arbeit für Jahrzehnte auf x86-cpus, aber ist nicht empfehlenswert, da es gegen einige C-standard Grundsätze : der compiler ist berechtigt, zu prüfen, diese Aussage als eine Garantie, dass die Daten korrekt ausgerichtet ist, führt zu fehlerhaftem code generation.Leider in mindestens einem Fall, es ist die einzige Lösung in der Lage zu extrahieren, die Leistung vom Gegner. Nämlich für den GCC auf ARMv6.
Nicht verwenden diese Lösung für ARMv7 obwohl : man kann den GCC Anweisungen erzeugen, die reserviert sind für ausgerichtete Speicherzugriffe, nämlich
LDM
(Laden Mehrere), was zu Abstürzen.Sogar auf x86/x64, wird es gefährlich für Ihren code schreiben, diese Art und Weise heutzutage, wie die neue generation der Compiler kann versuchen, automatisch zu Vektorisieren einige kompatible loops generieren SSE/AVX-code basiert auf der Annahme, dass dieser memory-Positionen korrekt ausgerichtet, stürzt das Programm ab.
Als Erinnerung, hier sind die Ergebnisse zusammengefasst und in Form einer Tabelle, mit der Konvention : memcpy > verpackt > direkt.
-march=armv7-a
werden gut mit dermemcpy()
Variante. Das Problem ist die Art und Weise, dass ältere ARM-CPUs umgehen würden, nicht Lesen/schreiben. Sollte also jemand diesen Beitrag Lesen, derzeit bis zum Jahr 2019 sollten sich bewusst sein, dass-march
Werte Einfluss auf die Dinge deutlich. Es ist möglich, dass der GCC-ARM-Backend (und Infrastruktur) wurde aktualisiert, um zu verstehen, dass die neueren ARM-cpus sind OK unaligned access. Siehe: Linux-trapping un-aligned access für einige mehr zu dem Thema.Teil des Problems ist wahrscheinlich, dass Sie nicht erlaubt eine einfache inlinability und weitere Optimierung. Dass eine spezialisierte Funktion für die Last bedeutet, dass eine Funktion aufrufen kann ausgegeben werden, bei jedem Aufruf, die könnte reduzieren die Leistung.
Eine Sache, die Sie tun könnten, ist die Verwendung
static inline
, die es erlauben, dass der compiler die inline-Funktionload32()
, damit die Leistung erhöht. Jedoch auf den höheren Ebenen der Optimierung, die der compiler sollte schon inlining dies für Sie.Wenn der compiler die inlines-ein 4-byte-memcpy, wird es wahrscheinlich wandeln Sie in die leistungsfähige Serie von Lasten oder Shops, die noch Arbeit auf unaligned Grenzen. Deshalb, wenn Sie sind immer noch zu wenig Leistung auch mit der compiler-Optimierungen aktiviert, kann es so sein, dass , die die maximale Leistung für die unaligned liest und schreibt auf die Prozessoren, die Sie verwenden. Da Sie sagte "
__packed
Anweisungen" sind nachgiebig identische Leistung zumemcpy()
wäre dies der Fall zu sein scheint.An diesem Punkt, es gibt sehr wenig, dass Sie tun können, außer Sie Ihre Daten. Allerdings, wenn Sie sind den Umgang mit einer zusammenhängenden Reihe von nichtlinearen
u32
's, es ist eine Sache, die Sie tun könnten:Diese nur verwendet, weist ein neues array mit
malloc()
, weilmalloc()
und Freunde reservieren von Speicher mit der richtigen Ausrichtung für alles:Dieser sollte relativ schnell sein, Sie sollten nur tun dies einmal pro Satz von Daten. Auch, während des kopieren,
memcpy()
passen nur für den ursprünglichen Mangel an Ausrichtung und verwenden Sie dann die Schnellste ausgerichtet load-und store-Anweisungen zur Verfügung, nach denen Sie in der Lage, um Ihre Daten unter Verwendung der normalen ausgerichtet sind, liest und schreibt bei voller Leistung.