Wie kann festgestellt werden, ob der Speicher ausgerichtet ist?

Lesezeit: 9 Minuten

Benutzeravatar von user229898
Benutzer229898

Ich bin neu in der Optimierung von Code mit SSE/SSE2-Anweisungen und bis jetzt bin ich nicht sehr weit gekommen. Meines Wissens würde eine übliche SSE-optimierte Funktion so aussehen:

void sse_func(const float* const ptr, int len){
    if( ptr is aligned )
    {
        for( ... ){
            // unroll loop by 4 or 2 elements
        }
        for( ....){
            // handle the rest
            // (non-optimized code)
        }
    } else {
        for( ....){
            // regular C code to handle non-aligned memory
        }
    }
}

Wie kann ich jedoch richtig feststellen, ob der Speicher ptr zeigt auf ist ausgerichtet auf zB 16 Bytes? Ich denke, ich muss den regulären C-Codepfad für nicht ausgerichteten Speicher einschließen, da ich nicht sicherstellen kann, dass jeder Speicher, der an diese Funktion übergeben wird, ausgerichtet wird. Und die Verwendung der Intrinsics zum Laden von Daten aus dem nicht ausgerichteten Speicher in die SSE-Register scheint schrecklich langsam zu sein (sogar langsamer als normaler C-Code).

Danke im Voraus…

  • random-name, nicht sicher, aber ich denke, es könnte effizienter sein, die ersten paar “nicht ausgerichteten” Elemente einfach separat zu behandeln, wie Sie es mit den letzten paar tun. Dann können Sie immer noch SSE für die “mittleren” verwenden …

    – Rehno-Lindeque

    21. Dezember 2009 um 12:27 Uhr

  • Hm, das ist ein guter Punkt. Ich werde es versuchen. Vielen Dank!

    – Benutzer229898

    22. Dezember 2009 um 16:15 Uhr

  • Besser: Verwenden Sie einen skalaren Prolog, um die falsch ausgerichteten Elemente bis zur ersten Ausrichtungsgrenze zu behandeln. (gcc tut dies bei der automatischen Vektorisierung mit einem Zeiger unbekannter Ausrichtung.) Oder wenn Ihr Algorithmus idempotent ist (wie a[i] = foo(b[i])), führen Sie einen möglicherweise nicht ausgerichteten ersten Vektor aus, dann die Hauptschleife beginnend an der ersten Ausrichtungsgrenze nach dem ersten Vektor, dann einen letzten Vektor, der am letzten Element endet. Wenn das Array tatsächlich falsch ausgerichtet war und / oder die Zählung kein Vielfaches der Vektorbreite war, überlappen sich einige dieser Vektoren, aber das schlägt immer noch Skalar.

    – Peter Cordes

    23. August 2017 um 13:50 Uhr

  • Am besten: Stellen Sie eine Zuweisung bereit, die 16-Byte-ausgerichteten Speicher bereitstellt. Arbeiten Sie dann mit dem ausgerichteten 16-Byte-Puffer, ohne dass führende oder abschließende Elemente korrigiert werden müssen. Das machen Bibliotheken wie Botan und Crypto++ für Algorithmen, die SSE, Altivec und Co. verwenden.

    – jww

    24. August 2018 um 14:10 Uhr


Benutzeravatar von Christoph
Christoph

#define is_aligned(POINTER, BYTE_COUNT) \
    (((uintptr_t)(const void *)(POINTER)) % (BYTE_COUNT) == 0)

Die Besetzung zu void * (oder Äquivalent, char *) ist notwendig, da der Standard nur eine invertierbare Konvertierung zu garantiert uintptr_t zum void *.

Wenn Sie Typsicherheit wünschen, sollten Sie eine Inline-Funktion verwenden:

static inline _Bool is_aligned(const void *restrict pointer, size_t byte_count)
{ return (uintptr_t)pointer % byte_count == 0; }

und auf Compiler-Optimierungen hoffen, wenn byte_count ist eine Kompilierzeitkonstante.

Warum müssen wir umwandeln? void * ?

Die C-Sprache erlaubt verschiedene Darstellungen für verschiedene Zeigertypen, zB könnten Sie einen 64-Bit haben void * Typ (der gesamte Adressraum) und ein 32-Bit foo * Typ (ein Segment).

Die Umwandlung foo * -> void * kann eine tatsächliche Berechnung beinhalten, z. B. das Hinzufügen eines Offsets. Der Standard überlässt es auch der Implementierung, was passiert, wenn (beliebige) Zeiger in Ganzzahlen umgewandelt werden, aber ich vermute, dass es oft als Noop implementiert wird.

Für eine solche Implementierung foo * -> uintptr_t -> foo * würde funktionieren, aber foo * -> uintptr_t -> void * und void * -> uintptr_t -> foo * würde nicht. Die Alignment-Berechnung würde auch nicht zuverlässig funktionieren, weil Sie nur die Alignment relativ zum Segment-Offset prüfen, was Ihren Wünschen entsprechen kann oder auch nicht.

Fazit: Immer verwenden void * um implementierungsunabhängiges Verhalten zu erhalten.

  • Dieses Makro sieht wirklich fies und raffiniert zugleich aus. Ich werde es auf jeden Fall testen.

    – Benutzer229898

    14. Dezember 2009 um 17:06 Uhr

  • Bitte geben Sie alle Ihnen bekannten Beispiele für Plattformen an, in denen non-void * erzeugt keinen ganzzahligen Wert im Bereich von uintptr_t. Und/oder wissen Sie, warum der Standard so formuliert ist?

    – Craig McQueen

    25. November 2010 um 23:07 Uhr

  • Warum einschränken?, sieht so aus, als würde es nichts tun, wenn es nur einen Zeiger gibt?

    – Michail

    23. September 2015 um 6:45 Uhr


  • @Mikhail: die Kombination von const * mit restrict ist eine stärkere Garantie als einfach const *: ohne restrictes ist legal, die wegzuwerfen const und den Speicher modifizieren; mit restrict vorhanden, ist es nicht; leider habe ich gelernt, dass dies in der Praxis nicht sinnvoll ist, da es nur dann zum Tragen kommt, wenn der Zeiger tatsächlich verwendet wird, was der Aufrufer nicht generell annehmen kann (dh der Nutzen liegt ausschließlich auf Seiten des Angerufenen); In diesem speziellen Fall ist es sowieso überflüssig, da wir es mit einer Inline-Funktion zu tun haben, sodass der Compiler ihren Körper sehen und selbst schlussfolgern kann, dass kein Speicher geändert wird

    – Christoph

    23. September 2015 um 16:52 Uhr


  • Wenn ein float * kann (theoretisch) eine andere Darstellung haben als a void *bedeutet das, dass die Ausrichtungsprüfung mit einem anderen als dem beabsichtigten Wert durchgeführt werden könnte?

    – mwfearnley

    13. März 2019 um 21:07 Uhr

Benutzeravatar von Pascal Cuoq
Pascal Cuoq

EDIT: Gießen zu long ist eine billige Möglichkeit, sich gegen die wahrscheinlichste Möglichkeit zu schützen, dass int und Zeiger heutzutage unterschiedliche Größen haben.

Wie in den Kommentaren unten erwähnt, gibt es bessere Lösungen, wenn Sie bereit sind, einen Header einzufügen …

Ein Zeiger p ist an einer 16-Byte-Grenze iff ausgerichtet ((unsigned long)p & 15) == 0.

  • Sie könnten stattdessen verwenden uintptr_t – Es hat garantiert die richtige Größe, um einen Zeiger zu halten. Vorausgesetzt natürlich, Ihr Compiler definiert es.

    – Anonym.

    13. Dezember 2009 um 23:26 Uhr

  • Es spielt keine Rolle, ob die Zeiger- und Integer-Größen nicht übereinstimmen. Sie kümmern sich nur um die unteren paar Bits.

    – Richard Pennington

    13. Dezember 2009 um 23:29 Uhr

  • Ich würde normalerweise verwenden p % 16 == 0da Compiler die Potenzen von 2 normalerweise genauso gut kennen wie ich, und ich das besser lesbar finde

    – Hasturkun

    13. Dezember 2009 um 23:30 Uhr

  • @Hasturkun Division/Modulo über vorzeichenbehaftete Ganzzahlen werden in C99 nicht in bitweisen Tricks kompiliert (einige dumme Sachen zum Runden gegen Null), und es ist in der Tat ein intelligenter Compiler, der erkennt, dass das Ergebnis des Modulo mit Null verglichen wird (in dem falls das bitweise Zeug wieder funktioniert). Nicht unmöglich, aber nicht trivial. Im Allgemeinen ist es besser, in eine Ganzzahl ohne Vorzeichen umzuwandeln, wenn Sie % verwenden und den Compiler & kompilieren lassen möchten.

    – Pascal Cuoq

    13. Dezember 2009 um 23:34 Uhr

  • @Pascal Cuoq, gcc bemerkt dies und gibt genau den gleichen Code für aus (p & 15) == 0 und (p % 16) == 0 mit dem -O Flagge gesetzt. Ich habe eine Reihe anderer Compiler gesehen, die ganzzahlige Division/Modulo/Multiplikation mit einer Potenz von 2 erkennen und das Schlaue daran tun. (Ich stimme jedoch dem Casting auf unsigned zu)

    – Hasturkun

    13. Dezember 2009 um 23:43 Uhr

Andere Antworten schlagen eine UND-Operation vor, bei der niedrige Bits gesetzt sind und mit Null verglichen werden.

Ein einfacherer Test wäre jedoch, eine MOD mit dem gewünschten Ausrichtungswert durchzuführen und mit Null zu vergleichen.

#define ALIGNMENT_VALUE     16u

if (((uintptr_t)ptr % ALIGNMENT_VALUE) == 0)
{
    // ptr is aligned
}

  • Ich habe dich positiv bewertet, aber nur, weil du Ganzzahlen ohne Vorzeichen verwendest 🙂

    – Pascal Cuoq

    13. Dezember 2009 um 23:36 Uhr

  • Ich glaube, das schlägt mit fehl uint8_t Typen, die manchmal Ausrichtungsanforderungen von 1 haben.

    – jww

    24. August 2018 um 14:07 Uhr

  • @jww Ich bin mir nicht sicher, ob ich verstehe, was du meinst. Eine Ausrichtungsanforderung von 1 würde im Wesentlichen keine Ausrichtungsanforderung bedeuten. Sie müssen sich keine Gedanken über die Ausrichtung machen uint8_t. Aber bitte klären, wenn ich falsch verstehe.

    – Craig McQueen

    29. August 2018 um 12:13 Uhr

  • Das u Suffix auf der Ganzzahl macht sie vorzeichenlos. Es ist gut, das Mischen von vorzeichenbehafteten und vorzeichenlosen Ausdrücken in Ausdrücken zu vermeiden, um mögliche Fallstricke zu vermeiden, die bei der Arithmetik mit gemischten Vorzeichen auftreten können. Siehe GCC-Warnung „Vergleich zwischen vorzeichenbehafteten und vorzeichenlosen Ganzzahlausdrücken“. In diesem Fall spielt es wahrscheinlich keine Rolle, aber es ist gut, sich gute Gewohnheiten anzueignen. (Ich vermute die 0 sollte sein 0u zu)

    – Craig McQueen

    8. August 2019 um 5:10 Uhr


  • Beachten Sie, dass Sie keine echte MOD-Operation verwenden sollten, da dies eine ziemlich teure Operation ist und so weit wie möglich vermieden werden sollte. Sie sollten immer die Operation and verwenden. Aber ich glaube, wenn Sie einen ausreichend ausgefeilten Compiler mit allen aktivierten Optimierungsoptionen haben, wird er Ihre MOD-Operation automatisch in einen einzigen Opcode konvertieren. (Linux-Kernel verwendet und funktioniert zu fyi)

    – rez

    11. September 2021 um 8:59 Uhr


Benutzeravatar von rubicks
Rubine

Mit einer Funktionsvorlage wie

#include <type_traits>

template< typename T >
bool is_aligned(T* p){
    return !(reinterpret_cast<uintptr_t>(p) % std::alignment_of<T>::value);
}

Sie könnten die Ausrichtung zur Laufzeit überprüfen, indem Sie so etwas wie aufrufen

struct foo_type{ int bar; }foo;
assert(is_aligned(&foo)); // passes

Um zu überprüfen, ob schlechte Ausrichtungen fehlschlagen, könnten Sie dies tun

// would almost certainly fail
assert(is_aligned((foo_type*)(1 + (uintptr_t)(&foo)));

Dies ist im Grunde das, was ich benutze. Indem ich die Ganzzahl zu einer Vorlage mache, stelle ich sicher, dass die Kompilierzeit verlängert wird, sodass ich bei allem, was ich tue, nicht mit einer langsamen Modulo-Operation enden werde.

Ich überprüfe immer gerne meine Eingaben, daher die Behauptung zur Kompilierzeit. Wenn Ihr Ausrichtungswert falsch ist, wird es nicht kompiliert …

template <unsigned int alignment>
struct IsAligned
{
    static_assert((alignment & (alignment - 1)) == 0, "Alignment must be a power of 2");

    static inline bool Value(const void * ptr)
    {
        return (((uintptr_t)ptr) & (alignment - 1)) == 0;
    }
};

Um zu sehen, was los ist, können Sie Folgendes verwenden:

// 1 of them is aligned...
int* ptr = new int[8];
for (int i = 0; i < 8; ++i)
    std::cout << IsAligned<32>::Value(ptr + i) << std::endl;

// Should give '1'
int* ptr2 = (int*)_aligned_malloc(32, 32);
std::cout << IsAligned<32>::Value(ptr2) << std::endl;

Benutzeravatar von alfC
alfC

Überlassen Sie das den Profis,

https://www.boost.org/doc/libs/1_65_1/doc/html/align/reference.html#align.reference.functions.is_aligned

bool is_aligned(const void* ptr, std::size_t alignment) noexcept; 

Beispiel:

        char D[1];
        assert( boost::alignment::is_aligned(&D[0], alignof(double)) ); //  might fail, sometimes

Benutzeravatar von Paul Tomblin
Paul Tomblin

Können Sie den ptr einfach mit 0x03 (ausgerichtet auf 4s), 0x07 (ausgerichtet auf 8s) oder 0x0f (ausgerichtet auf 16s) ‘und’, um zu sehen, ob eines der niedrigsten Bits gesetzt ist?

  • Nein, das kannst du nicht. Ein Zeiger ist kein gültiges Argument für den &-Operator.

    – Steve Jessop

    13. Dezember 2009 um 23:34 Uhr

  • @SteveJessop, zu dem du casten könntest uintptr_t.

    Benutzer6754053

    20. Dezember 2016 um 23:10 Uhr

  • @MarkYisri: Ja, ich erwarte, dass in der Praxis jede Implementierung, die SSE2-Anweisungen unterstützt, eine implementierungsspezifische Garantie bietet, die funktioniert 🙂

    – Steve Jessop

    10. Januar 2017 um 11:42 Uhr


1409150cookie-checkWie kann festgestellt werden, ob der Speicher ausgerichtet ist?

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

Privacy policy