Verwenden einer Union (gekapselt in einer Struktur), um Konvertierungen für Neon-Datentypen zu umgehen

Lesezeit: 7 Minuten

Verwenden einer Union gekapselt in einer Struktur um Konvertierungen fur
Antonio

Ich habe meinen ersten Ansatz mit intrinsischen Vektorisierungsfunktionen mit SSE gemacht, wo es im Grunde nur einen Datentyp gibt __m128i. Beim Wechsel zu Neon fand ich die Datentypen und Funktionsprototypen viel spezifischer, z uint8x16_t (ein Vektor von 16 unsigned char), uint8x8x2_t (2 Vektoren mit 8 unsigned char jeder), uint32x4_t (ein Vektor mit 4 uint32_t) etc.

Zuerst war ich begeistert (viel einfacher die genaue Funktion beim gewünschten Datentyp zu finden), dann sah ich, was für ein Durcheinander es war, wenn man die Daten auf unterschiedliche Weise behandeln wollte. Verwenden von spezifische Casting-Betreiber würde mich ewig brauchen. Auch hier wird das Problem angesprochen. Dann kam mir die Idee einer in eine Struktur gekapselten Union und einiger Casting- und Zuweisungsoperatoren.

struct uint_128bit_t { union {
        uint8x16_t uint8x16;
        uint16x8_t uint16x8;
        uint32x4_t uint32x4;
        uint8x8x2_t uint8x8x2;
        uint8_t uint8_array[16] __attribute__ ((aligned (16) ));
        uint16_t uint16_array[8] __attribute__ ((aligned (16) ));
        uint32_t uint32_array[4] __attribute__ ((aligned (16) ));
    };

    operator uint8x16_t& () {return uint8x16;}
    operator uint16x8_t& () {return uint16x8;}
    operator uint32x4_t& () {return uint32x4;}
    operator uint8x8x2_t& () {return uint8x8x2;}
    uint8x16_t& operator =(const uint8x16_t& in) {uint8x16 = in; return uint8x16;}
    uint8x8x2_t& operator =(const uint8x8x2_t& in) {uint8x8x2 = in; return uint8x8x2;}

};

Dieser Ansatz funktioniert für mich: Ich kann eine Variable vom Typ verwenden uint_128bit_t als Argument und Ausgabe mit verschiedenen Neon-Intrinsics, zB vshlq_n_u32, vuzp_u8, vget_low_u8 (in diesem Fall nur als Eingabe). Und ich kann es bei Bedarf um weitere Datentypen erweitern. Hinweis: Die Arrays dienen dazu, den Inhalt einer Variablen einfach auszugeben.

Ist das die richtige Vorgehensweise?
Gibt es einen versteckten Fehler?
Habe ich das Rad neu erfunden?
(Ist das ausgerichtete Attribut notwendig?)

  • Schauen Sie sich diese Frage (und die oberste Antwort) an: stackoverflow.com/questions/2310483/…

    – simon

    23. März ’15 um 11:56

  • @gurka Ja, danke, ziemlich klar!

    – Antonio

    23. März ’15 um 20:49

Nach dem C++-Standard ist dieser Datentyp fast nutzlos (und sicherlich für den von Ihnen beabsichtigten Zweck). Das liegt daran, dass das Lesen von einem inaktiven Mitglied einer Union ein undefiniertes Verhalten ist.

Es ist jedoch möglich, dass Ihr Compiler verspricht, dass dies funktioniert. Sie haben jedoch nicht nach einem bestimmten Compiler gefragt, daher ist es unmöglich, dazu weiter zu kommen.

  • Danke für den Hinweis! Ich habe das Tag gcc hinzugefügt. Was bedeutet es “inaktives Mitglied einer Gewerkschaft”? Ein Gewerkschaftsbegriff, der noch nicht “explizit berührt” wurde?

    – Antonio

    23. März ’15 um 11:41


  • ich Überlegen dass Sie nur von dem Mitglied lesen dürfen, an das zuletzt geschrieben wurde. Wenn Sie also eine Gewerkschaft mit zwei Mitgliedern haben: foo und bar und schreib etwas an foo, dann darfst du nur lesen foo – Vorlesen aus bar wäre undefiniertes Verhalten.

    – simon

    23. März ’15 um 11:46

Verwenden einer Union gekapselt in einer Struktur um Konvertierungen fur
Antonio

Da die ursprünglich vorgeschlagene Methode in C++ ein undefiniertes Verhalten aufweist, habe ich etwa Folgendes implementiert:

template <typename T>
struct NeonVectorType {

    private:
    T data;

    public:
    template <typename U>
    operator U () {
        BOOST_STATIC_ASSERT_MSG(sizeof(U) == sizeof(T),"Trying to convert to data type of different size");
        U u;
        memcpy( &u, &data, sizeof u );
        return u;
    }

    template <typename U>
    NeonVectorType<T>& operator =(const U& in) {
        BOOST_STATIC_ASSERT_MSG(sizeof(U) == sizeof(T),"Trying to copy from data type of different size");
        memcpy( &data, &in, sizeof data );
        return *this;
    }

};

Dann:

typedef NeonVectorType<uint8x16_t> uint_128bit_t; //suitable for uint8x16_t, uint8x8x2_t, uint32x4_t, etc.
typedef NeonVectorType<uint8x8_t> uint_64bit_t; //suitable for uint8x8_t, uint32x2_t, etc.

Die Verwendung von memcpy wird hier (und hier) diskutiert und vermeidet das Brechen der strengen Aliasing-Regel. Beachten Sie, dass es im Allgemeinen weg optimiert wird.

Wenn Sie sich den Bearbeitungsverlauf ansehen, habe ich eine benutzerdefinierte Version mit Kombinationsoperatoren für Vektoren von Vektoren implementiert (zB uint8x8x2_t). Das Problem wurde hier erwähnt. Da diese Datentypen jedoch als Arrays deklariert sind (siehe führen, Abschnitt 12.2.2) und daher in aufeinanderfolgenden Speicherorten liegt, muss der Compiler die memcpy korrekt.

Um schließlich den Inhalt der Variablen auszugeben, könnte man eine Funktion wie diese verwenden.

  • Auch dies ist undefiniertes Verhalten – Verletzung der strengen Aliasing-Regel.

    – Ben Voigt

    23. März ’15 um 16:31

  • Du liegst völlig falsch. Beim strikten Aliasing geht es nicht um Syntax, sondern darum, Hinweise auf Dinge zu haben, die Sie nicht sollten. Ihre Casting-Hacks sind genauso schlecht und undefiniert wie die Gewerkschafts-Hacks des OP.

    – Welpe

    24. März ’15 um 10:02

  • @Antonio: Praktisch alle. Diese Art von Pointer Cast ist ein völlig undefiniertes Verhalten, wenn T und U keine hochspezifischen Anforderungen erfüllen, was im Grunde nur char-verbunden.

    – Welpe

    24. März ’15 um 10:22

  • @Antonio: Haben Sie Probleme, Ressourcen zu finden, die erklären, was für Wortwitze und die strikte Aliasing-Regel sind? Hier auf Stack Overflow gibt es eine Reihe von Fragen mit einigen sehr detaillierten Antworten.

    – Ben Voigt

    24. März ’15 um 15:17

  • @Antonio: Das Risiko, das Sie eingehen, besteht darin, dass Sie denken, dass strikte Aliasing-Verstöße als eine Änderung definiert sind, die unter bestimmten Umständen unbemerkt bleibt und zur Verwendung veralteter Werte führt. Aber strenge Aliasing-Verletzungen werden weder auf diese Weise noch auf andere Weise definiert. Sie sind undefiniertes Verhalten. Der Compiler muss nicht alle Instanzen von undefiniertem Verhalten diagnostizieren.

    – Ben Voigt

    24. März ’15 um 15:28

Verwenden einer Union gekapselt in einer Struktur um Konvertierungen fur
auselen

Wenn Sie versuchen, das Casting durch verschiedene Datenstrukturen-Hacker auf vernünftige Weise zu vermeiden, werden Sie am Ende Speicher / Wörter durcheinander bringen, was jede Leistung, die Sie von NEON erhoffen, zunichte macht.

Sie können Quad-Register wahrscheinlich leicht in Doppelregister umwandeln, aber ein anderer Weg ist möglicherweise nicht möglich.

Darauf läuft alles hinaus. In jedem Befehl gibt es einige Bits zum Indexieren von Registern. Wenn der Befehl Quad-Register erwartet, zählt er Register zwei mal zwei wie Q(2*n), Q(2*n+1) und verwendet nur n in codierten Befehlen, (2*n+1) wird für Core implizit sein . Wenn Sie an irgendeinem Punkt in Ihrem Code versuchen, zwei Double in ein Quad zu wandeln, befinden Sie sich möglicherweise in einer Position, in der diese nicht aufeinander folgen, was den Compiler dazu zwingt, Register in den Stack und zurück zu mischen, um ein aufeinanderfolgendes Layout zu erhalten.

Ich denke, es ist immer noch die gleiche Antwort in anderen Worten https://stackoverflow.com/a/13734838/1163019

NEON-Anweisungen sind für das Streaming konzipiert, Sie laden in großen Blöcken aus dem Speicher, verarbeiten sie und speichern dann, was Sie möchten. Dies sollte alles sehr einfache Mechanik sein, wenn nicht, verlieren Sie zusätzliche Leistung, die es bietet, was die Leute fragen lässt, warum Sie Neon überhaupt verwenden, um sich das Leben zu erschweren.

Stellen Sie sich NEON als unveränderliche Werttypen und Operationen vor.

  • In Assembler-NEON-Befehlen oder in SSE-Intrinsics wird dieses Konvertierungs-Chaos vollständig vermieden. Ich mache Bildverarbeitung und die Art der Konvertierung, die ich durchführe, ist (im Moment) Integer in Integer, also bin ich ziemlich sicher. In der von mir implementierten Antwort werden die Anweisungen zum Mischen von Datentypen gut verwaltet (Paare von d Register sind immer in a coupled gekoppelt q register) und alle memcpys sind wegoptimiert, die generierte Assembly sieht so aus, wie sie sein sollte, meine verbleibenden Zweifel sind, ob das, was ich tue, für meine Verwendung absolut sicher ist.

    – Antonio

    26. März ’15 um 9:08

  • Ich habe einen Vorteil bei der Verwendung von Neon, z. B. Einsparung von 75%, wenn der Compiler die Autovektorisierung ermöglicht, und zusätzliche 33% bei der manuellen Vektorisierung mit Intrinsic (Gesamteinsparung etwa 85%). Auch wenn mir Anweisungen fehlen und gegen Ende 16 Werte berechnet werden, von denen nur 4 gültig sind und die ich am Ende speichern werde.

    – Antonio

    26. März ’15 um 9:10


  • @Antonio Niemand gibt in der SW-Welt eine “völlig sichere” Garantie. Wenn es also für Sie funktioniert, machen Sie weiter mit dem, was Sie tun. Bei trivialen Fällen kann es sein, dass die Dinge einfach funktionieren, bei komplexen Fällen müssen Sie möglicherweise zusätzliche Sorgfalt / Handhabung walten lassen.

    – auselen

    26. März ’15 um 9:46

  • @auselan Ich möchte garantiert sein, abgesehen von Fehlern des Compilers und von Kosmische Strahlung 🙂 🙂

    – Antonio

    26. März ’15 um 11:13


  • Ich denke, die Überprüfung der produzierten Baugruppe ist ausreichend Garantie.

    – auselen

    26. März ’15 um 11:50

.

316360cookie-checkVerwenden einer Union (gekapselt in einer Struktur), um Konvertierungen für Neon-Datentypen zu umgehen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy