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.

InformationsquelleAutor Cyan | 2015-08-18
Schreibe einen Kommentar