Frage

Ich bin neu in der Optimierung Code mit SSE / SSE2 Anweisungen und bis jetzt habe ich nicht sehr weit gekommen. Mein Wissen eine gemeinsame SSE-optimierte Funktion würde wie folgt 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
        }
    }
}

Doch wie kann ich feststellen, richtig, wenn die Speicher ptr Punkte durch beispielsweise ausgerichtet ist, 16 Bytes? Ich glaube, ich habe zu dem regulären C-Code-Pfad für blockfrei Speicher enthalten, da ich nicht sicherstellen kann, dass jede Erinnerung an diese Funktion übergeben wird ausgerichtet werden. Und die Spezifika unter Verwendung von Daten aus nicht ausgerichteten Speichern in das SSE-Register zu laden scheint langsam schrecklich zu sein (noch langsamer als normaler C-Code).

Vielen Dank im Voraus ...

War es hilfreich?

Lösung

EDIT: zu long Casting ist eine billige Art und Weise, sich gegen die wahrscheinlichste Möglichkeit, int und Zeiger zu schützen heute verschieden Größen.

Wie weiter unten in den Kommentaren darauf hingewiesen, gibt es bessere Lösungen, wenn Sie bereit sind, umfassen einen Header sind ...

Ein Zeiger p ausgerichtet ist, auf einem 16-Byte-Grenze iff ((unsigned long)p & 15) == 0.

Andere Tipps

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

Die Besetzung void * (oder equivalenty, char *) ist notwendig, da die Norm nur eine umkehrbare Umwandlung für uintptr_t void * garantiert.

Wenn Sie Typsicherheit wollen, sollten Sie mit einer Inline-Funktion:

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

und hofft für Compiler-Optimierungen, wenn byte_count ein Kompilierung-Konstante ist.

Warum brauchen wir konvertieren void * ?

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

Die Umwandlung foo * -> void * könnte eine tatsächliche Berechnung einzubeziehen, zB das Hinzufügen eines Offsets. Der Standard läßt es auch auf die Umsetzung, was passiert, wenn (beliebigen) Zeiger auf ganze Zahlen konvertieren, aber ich vermute, dass es oft als noop umgesetzt wird.

Für eine solche Implementierung foo * -> uintptr_t -> foo * funktionieren würde, aber foo * -> uintptr_t -> void * und void * -> uintptr_t -> foo * wollte nicht. Die Ausrichtung Berechnung würde auch nicht zuverlässig arbeiten, weil Sie nur Offset Ausrichtung in Bezug auf das Segment prüfen, was sein könnte oder auch nicht, was Sie wollen.

Fazit:. Immer void * verwenden, um die Implementierung unabhängige Verhalten zu bekommen

Andere Antworten deuten darauf hin, eine UND-Verknüpfung mit dem niedrigen Bits gesetzt, und im Vergleich zu Null.

Aber ein Straight-Forward-Test wäre eine MOD mit dem gewünschten Ausrichtung Wert zu tun, und vergleichen zu Null.

#define ALIGNMENT_VALUE     16u

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

Mit einer Funktion Vorlage wie

#include <type_traits>

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

Sie Ausrichtung zur Laufzeit durch den Aufruf so etwas wie

überprüfen könnten
struct foo_type{ int bar; }foo;
assert(is_aligned(&foo)); // passes

Um zu überprüfen, dass die schlechten Ausrichtungen scheitern, könnten Sie tun

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

Dies ist im Grunde, was ich verwende. Indem die integer eine Vorlage, sicherzustellen, dass ich es an der Zeit kompilieren erweitert hat, so dass ich nicht mit einer langsamen Modulo-Operation am Ende, was ich tue.

Ich habe immer gerne meine Eingabe überprüft, so damit die Kompilierung Behauptung. Wenn die Ausrichtung Wert falsch ist, nun ja, dann wird es nicht kompilieren ...

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 diese 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;

Können Sie sich 'und' die ptr mit 0x03 (ausgerichtet auf 4s), 0x07 (auf 8s ausgerichtet) oder 0x0f (ausgerichtet auf 16s), um zu sehen, wenn eine der niedrigsten Bits gesetzt ist?

Ü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

Wie wäre:

void *mem = malloc(1024+15); 
void *ptr =( (*(char*)mem) - (*(char *)mem % 16) );
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top