web-dev-qa-db-de.com

Wie werden die Daten eines Vektors ausgerichtet?

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?

36
fredoverflow

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.

17

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 Tint (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:

  1. Suchen Sie die vector-Headerdatei [C:\Programme\Microsoft Visual Studio 10.0\VC\include\vector].
  2. Suchen Sie die void resize( _Ty _Val )-Methode [Zeile 870 in VC2010].
  3. Ändern Sie es in void resize( const _Ty& _Val ).
25
user1071136

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>>;
8
Dev Null

Kurze Antwort:

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.

Lange Antwort:

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.

6.11 Ausrichtung (Absatz 4/5)

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:

8.3.4 Neu (Absatz 17)

[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.

6.11 Ausrichtung (Absatz 2)

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.

3
Martin York

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 ] ) );
}
3
moose

Verwenden Sie declspec(align(x,y)) wie im Vektorisierungs-Tutorial für Intel beschrieben, http://d3f8ykwhia686p.cloudfront.net/1live/intel/CompilerAutovectorizationGuide.pdf

0
octoback