Wenn ich Daten in einem std::vector
mit SSE verarbeiten möchte, benötige ich eine 16-Byte-Ausrichtung. Wie kann ich das erreichen? Muss ich einen eigenen Verteiler schreiben? Oder richtet sich der Standardzuweiser bereits an 16-Byte-Grenzen aus?
Der C++ - Standard erfordert Zuweisungsfunktionen (malloc()
und operator new()
), um Speicherplatz zuzuordnen, der für jeden standard -Typ geeignet ausgerichtet ist. Da diese Funktionen die Ausrichtungsanforderung nicht als Argument erhalten, bedeutet dies in der Praxis, dass die Ausrichtung für alle Zuordnungen gleich ist und die Ausrichtung eines Standardtyps mit der größten Ausrichtungsanforderung ist, häufig long double
und/oder long long
( Siehe boost max_align union ).
Vektoranweisungen wie SSE und AVX haben höhere Ausrichtungsanforderungen (16-Byte-Ausrichtung für 128-Bit-Zugriff und 32-Byte-Ausrichtung für 256-Bit-Zugriff) als durch die Standard-C++ - Zuordnungsfunktionen. posix_memalign()
oder memalign()
können verwendet werden, um solche Zuordnungen mit höheren Ausrichtungsanforderungen zu erfüllen.
Sie sollten einen benutzerdefinierten Zuweiser mit std::
-Containern verwenden, z. B. vector
. Ich kann mich nicht erinnern, wer das folgende geschrieben hat, aber ich habe es einige Zeit verwendet und es scheint zu funktionieren (möglicherweise müssen Sie _aligned_malloc
in _mm_malloc
ändern, je nach Compiler/Plattform):
#ifndef ALIGNMENT_ALLOCATOR_H
#define ALIGNMENT_ALLOCATOR_H
#include <stdlib.h>
#include <malloc.h>
template <typename T, std::size_t N = 16>
class AlignmentAllocator {
public:
typedef T value_type;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef T * pointer;
typedef const T * const_pointer;
typedef T & reference;
typedef const T & const_reference;
public:
inline AlignmentAllocator () throw () { }
template <typename T2>
inline AlignmentAllocator (const AlignmentAllocator<T2, N> &) throw () { }
inline ~AlignmentAllocator () throw () { }
inline pointer adress (reference r) {
return &r;
}
inline const_pointer adress (const_reference r) const {
return &r;
}
inline pointer allocate (size_type n) {
return (pointer)_aligned_malloc(n*sizeof(value_type), N);
}
inline void deallocate (pointer p, size_type) {
_aligned_free (p);
}
inline void construct (pointer p, const value_type & wert) {
new (p) value_type (wert);
}
inline void destroy (pointer p) {
p->~value_type ();
}
inline size_type max_size () const throw () {
return size_type (-1) / sizeof (value_type);
}
template <typename T2>
struct rebind {
typedef AlignmentAllocator<T2, N> other;
};
bool operator!=(const AlignmentAllocator<T,N>& other) const {
return !(*this == other);
}
// Returns true if and only if storage allocated from *this
// can be deallocated from other, and vice versa.
// Always returns true for stateless allocators.
bool operator==(const AlignmentAllocator<T,N>& other) const {
return true;
}
};
#endif
Verwenden Sie es wie folgt (ändern Sie ggf. die 16 in eine andere Ausrichtung):
std::vector<T, AlignmentAllocator<T, 16> > bla;
Dies stellt jedoch nur sicher, dass der Speicherblock, den std::vector
verwendet, 16 Bytes ist. Wenn sizeof(T)
kein Vielfaches von 16 ist, werden einige Ihrer Elemente nicht ausgerichtet. Abhängig von Ihrem Datentyp kann dies ein Problem sein. Wenn T
int
(4 Byte) ist, werden nur Elemente geladen, deren Index ein Vielfaches von 4 ist. Wenn es sich um double
(8 Byte) handelt, werden nur Vielfache von 2 usw.
Das eigentliche Problem ist, wenn Sie Klassen als T
verwenden. In diesem Fall müssen Sie Ihre Ausrichtungsanforderungen in der Klasse selbst angeben (je nach Compiler kann dies anders sein; das Beispiel ist für GCC):
class __attribute__ ((aligned (16))) Foo {
__attribute__ ((aligned (16))) double u[2];
};
Wir sind fast fertig! Wenn Sie Visual C++ (mindestens Version 2010) verwenden, können Sie std::vector
nicht mit Klassen verwenden, deren Ausrichtung Sie aufgrund von std::vector::resize
angegeben haben.
Wenn Sie beim Kompilieren die folgende Fehlermeldung erhalten:
C:\Program Files\Microsoft Visual Studio 10.0\VC\include\vector(870):
error C2719: '_Val': formal parameter with __declspec(align('16')) won't be aligned
Sie müssen Ihre stl::vector header
-Datei hacken:
vector
-Headerdatei [C:\Programme\Microsoft Visual Studio 10.0\VC\include\vector].void resize( _Ty _Val )
-Methode [Zeile 870 in VC2010].void resize( const _Ty& _Val )
.Anstatt einen eigenen Zuweiser zu schreiben, wie vor vorgeschlagen, können Sie boost::alignment::aligned_allocator
für std::vector
folgendermaßen verwenden:
#include <vector>
#include <boost/align/aligned_allocator.hpp>
template <typename T>
using aligned_vector = std::vector<T, boost::alignment::aligned_allocator<T, 16>>;
Wenn sizeof(T)*vector.size() > 16
dann Ja.
Angenommen, Sie verwenden normale Zuweiser
Vorbehalt: Solange alignof(std::max_align_t) >= 16
die maximale Ausrichtung ist.
Aktualisiert 25/Aug/2017 neuer Standard n4659
Wenn es auf etwas ausgerichtet ist, das größer als 16 ist, wird es auch für 16 richtig ausgerichtet.
Ausrichtungen werden als Werte vom Typ std :: size_t dargestellt. Gültige Ausrichtungen enthalten nur die Werte, die von einem Alignof-Ausdruck für die grundlegenden Typen zurückgegeben werden, sowie eine zusätzliche durch die Implementierung definierte Gruppe von Werten, die möglicherweise leer ist. Jeder Ausrichtungswert muss eine nicht negative integrale Zweierpotenz sein.
Ausrichtungen haben eine Reihenfolge von schwächeren zu stärkeren oder strengeren Ausrichtungen. Strengere Ausrichtungen haben größere Ausrichtungswerte. Eine Adresse, die eine Ausrichtungsanforderung erfüllt, erfüllt auch alle schwächeren gültigen Ausrichtungsanforderungen.
new und new [] geben Werte zurück, die so ausgerichtet sind, dass Objekte in ihrer Größe korrekt ausgerichtet sind:
[Hinweis: Wenn die Zuweisungsfunktion einen anderen Wert als Null zurückgibt, muss sie ein Zeiger auf einen Speicherblock sein, in dem Platz für das Objekt reserviert wurde. Es wird angenommen, dass der Speicherblock entsprechend ausgerichtet ist und die angeforderte Größe hat. Die Adresse des erstellten Objekts muss nicht unbedingt mit der Adresse des Blocks identisch sein, wenn das Objekt ein Array ist. - Endnote]
Beachten Sie, dass die meisten Systeme eine maximale Ausrichtung haben. Dynamisch zugewiesener Speicher muss nicht auf einen größeren Wert als diesen ausgerichtet werden.
Eine grundlegende Ausrichtung wird durch eine Ausrichtung dargestellt, die kleiner oder gleich der größten unterstützten Ausrichtung ist durch die Implementierung in allen Kontexten, die gleich zu alignof (std :: max_align_t) (21.2) ist. Die Ausrichtung Erforderlich für einen Typ kann anders sein, wenn er als Typ eines vollständigen Objekts und als .__ verwendet wird. der Typ eines Unterobjekts.
Solange Ihr Vektorspeicher größer als 16 Byte ist, wird er korrekt an 16-Byte-Grenzen ausgerichtet.
Schreiben Sie Ihren eigenen Verteiler. allocate
und deallocate
sind die wichtigsten. Hier ist ein Beispiel:
pointer allocate( size_type size, const void * pBuff = 0 )
{
char * p;
int difference;
if( size > ( INT_MAX - 16 ) )
return NULL;
p = (char*)malloc( size + 16 );
if( !p )
return NULL;
difference = ( (-(int)p - 1 ) & 15 ) + 1;
p += difference;
p[ -1 ] = (char)difference;
return (T*)p;
}
void deallocate( pointer p, size_type num )
{
char * pBuffer = (char*)p;
free( (void*)(((char*)p) - pBuffer[ -1 ] ) );
}
Verwenden Sie declspec(align(x,y))
wie im Vektorisierungs-Tutorial für Intel beschrieben, http://d3f8ykwhia686p.cloudfront.net/1live/intel/CompilerAutovectorizationGuide.pdf