2015-09-16 6 views
7

Ich habe Ausrichtung Problem bei der Verwendung ymm Registern, mit einigen Code-Schnipsel, die mir gut scheint. Hier ist ein minimales Arbeitsbeispiel:Wie löst man das 32-Byte-Alignment-Problem bei AVX-Lade-/Speicheroperationen?

#include <iostream> 
#include <immintrin.h> 

inline void ones(float *a) 
{ 
    __m256 out_aligned = _mm256_set1_ps(1.0f); 
    _mm256_store_ps(a,out_aligned); 
} 

int main() 
{ 
    size_t ss = 8; 
    float *a = new float[ss]; 
    ones(a); 

    delete [] a; 

    std::cout << "All Good!" << std::endl; 
    return 0; 
} 

Sicherlich sizeof(float) ist 4 auf meiner Architektur (Intel(R) Xeon(R) CPU E5-2650 v2 @ 2.60GHz) und ich bin Kompilieren mit gcc-O3 -march=native Flags verwenden. Natürlich verschwindet der Fehler mit dem nicht ausgerichteten Speicherzugriff, d. H. Spezifizierung _mm256_storeu_ps. Ich habe dieses Problem auch nicht auf xmm Register, das heißt

inline void ones_sse(float *a) 
{ 
    __m128 out_aligned = _mm_set1_ps(1.0f); 
    _mm_store_ps(a,out_aligned); 
} 

Bin ich dumm etwas zu tun? Was ist die Umgehung dafür?

+2

Ein bisschen off topic, aber denken Sie daran, 'delete []' zu verwenden, wenn Sie etwas löschen, das mit 'new []' zugewiesen wurde. – anorm

+1

hast du _mm_malloc statt neu versucht? – Alexander

+0

@anorm wahr. Bearbeitet – romeric

Antwort

8

Die Standardverteilern wahrscheinlich nur bis 8B (die Breite des breitesten Standardtyp) ausgerichtet wird, oder vielleicht 16B, wenn der breiteste Typ hat diese Anforderung (z.B. long double in einigen x86-64 ABIs).

Optionen:

  • std::aligned_alloc: ISO C++ 17. Hauptnachteil: Größe muss ein Vielfaches der Ausrichtung sein. Diese Braindead-Anforderung macht es zum Beispiel ungeeignet, ein 64B Cache-Line Aligned Array einer unbekannten Anzahl von floats zuzuweisen. Oder insbesondere ein 2M-ausgerichtetes Array, um die Vorteile von transparent hugepages zu nutzen.

    Die C-Version von aligned_alloc wurde in ISO C11 hinzugefügt. Es ist in einigen, aber nicht in allen C++ - Compilern verfügbar. Wie auf der cppreference-Seite erwähnt, musste die C11-Version nicht fehlschlagen, wenn die Größe kein Vielfaches der Ausrichtung ist (es ist ein undefiniertes Verhalten), so dass viele Implementierungen das offensichtliche gewünschte Verhalten als "Erweiterung" lieferten. Discussion is underway to fix this, aber jetzt kann ich aligned_alloc nicht als eine portable Methode zur Zuweisung von Arrays beliebiger Größe empfehlen.

    Auch Kommentatoren melden, dass es in MSVC++ nicht verfügbar ist. Eine brauchbare #ifdef für Windows finden Sie unter best cross-platform method to get aligned memory. Aber AFAIK gibt es keine Windows Alignment-Allocation Funktionen, die Zeiger produzieren, die mit Standard free kompatibel sind.

  • posix_memalign: Teil von POSIX 2001, kein ISO C oder C++ Standard. Clunky Prototyp/Schnittstelle im Vergleich zu aligned_alloc. Ich habe gesehen, dass gcc Neuladevorgänge des Zeigers erzeugt, weil es nicht sicher war, dass Speicher in dem Puffer den Zeiger nicht modifiziert haben. (Seit posix_memalign wird die Adresse des Zeigers übergeben.) Wenn Sie dies verwenden, kopieren Sie den Zeiger in eine andere C++ - Variable, deren Adresse nicht außerhalb der Funktion übergeben wurde.

#include <stdlib.h> 
int posix_memalign(void **memptr, size_t alignment, size_t size); // POSIX 2001 
void *aligned_alloc(size_t alignment, size_t size);    // C11 (and ISO C++17) 
  • _mm_malloc: Verfügbar auf jeder Plattform, wo _mm_whatever_ps verfügbar ist, aber Sie können keine Zeiger von ihm free passieren. Auf vielen C- und C++ - Implementierungen sind _mm_free und free kompatibel, aber es ist nicht garantiert tragbar. (Und im Gegensatz zu den anderen beiden wird es zur Laufzeit nicht kompilieren Zeit.) Auf MSVC unter Windows verwendet _mm_malloc_aligned_malloc, die nicht kompatibel mit free; es stürzt in der Praxis ab.

  • In C++ 11 und höher: Verwenden Sie alignas(32) float avx_array[1234] als das erste Mitglied einer Struktur/Klasse Mitglied (oder direkt in einer Ebene Array) so statische und automatische Speicherobjekte dieses Typs haben 32B Ausrichtung. std::aligned_storage documentation hat ein Beispiel für diese Technik zu erklären, was std::aligned_storage tut.

    Dies funktioniert nicht für dynamisch zugewiesenen Speicher (wie std::vector<my_class_with_aligned_member_array>), siehe Making std::vector allocate aligned memory.

    In C++ 17, alignas wird schließlich für die ausgerichtete dynamische Zuordnung verwendbar sein.


Und schließlich die letzte Option ist so schlecht, dass es nicht einmal Teil der Liste ist: ein größeren Puffer zuweisen und p+=31; p&=~31ULL mit entsprechendem Guss tun hinzuzufügen. Zu viele Nachteile (schwer zu löschen, Speicher zu verschwenden), die es wert sind zu diskutieren, da Aligned-Allocation-Funktionen auf jeder Plattform verfügbar sind, die Intel _mm256 intrinsics unterstützen. Aber es gibt sogar Bibliotheksfunktionen, die Ihnen dabei helfen werden, IIRC.

Die Forderung _mm_free statt free wahrscheinlich für die Möglichkeit der Umsetzung _mm_malloc auf einem einfachen alten malloc mit dieser Technik besteht zu verwenden.

+0

Könnten Sie bitte erklären, warum Sie POSIX-only-Funktion gegenüber plattformunabhängigen '_mm_malloc' bevorzugen? – stgatilov

+0

Ist '_mm_malloc' nicht eine informell unterstützte, nicht standardisierte Intel-Erweiterung? Wie könnte das plattformunabhängiger sein als POSIX? – Useless

+0

@stgatilov: Der Hauptvorteil ist, dass Sie sie mit 'free' befreien können. Wenn Sie Code haben, der mit jeder Ausrichtung funktioniert, aber schneller mit der 32B-Ausrichtung ist, können Sie eine ausgerichtete Zuweisung an geeigneter Stelle vornehmen, sodass Sie normalerweise den schnellen Fall erhalten. Außerdem ist 'aligned_alloc' ISO C11, also sollte es überall verfügbar sein (wenn Compiler aufholen). Es gibt nur ein wichtiges non-POSIX x86-Betriebssystem, also denke ich, dass Sie an MSVC denken. Hat es keine dieser Funktionen? Ich nahm an, MSVC würde so viel POSIX unterstützen, wie es leicht möglich war, nur die Systemaufrufe, die Windows nicht zuordnen. –

5

Es gibt die zwei intrinsics für die Speicherverwaltung. _mm_malloc funktioniert wie ein Standard-malloc, benötigt jedoch einen zusätzlichen Parameter, der die gewünschte Ausrichtung angibt. In diesem Fall eine 32-Byte-Ausrichtung. Wenn diese Zuweisungsmethode verwendet wird, muss Speicher durch den entsprechenden _mm_free-Aufruf freigegeben werden.

float *a = static_cast<float*>(_mm_malloc(sizeof(float) * ss , 32)); 
... 
_mm_free(a); 
3

Sie benötigen ausgerichtete Zuordner.

Aber es ist keinen Grund, warum Sie sie nicht bündeln können:

template<class T, size_t align> 
struct aligned_free { 
    void operator()(T* t)const{ 
    ASSERT(!(uint_ptr(t) % align)); 
    _mm_free(t); 
    } 
    aligned_free() = default; 
    aligned_free(aligned_free const&) = default; 
    aligned_free(aligned_free&&) = default; 
    // allow assignment from things that are 
    // more aligned than we are: 
    template<size_t o, 
    std::enable_if_t< !(o % align) >* = nullptr 
    > 
    aligned_free(aligned_free<T, o>) {} 
}; 
template<class T> 
struct aligned_free<T[]>:aligned_free<T>{}; 

template<class T, size_t align=1> 
using mm_ptr = std::unique_ptr< T, aligned_free<T, align> >; 
template<class T, size_t align> 
struct aligned_make; 
template<class T, size_t align> 
struct aligned_make<T[],align> { 
    mm_ptr<T, align> operator()(size_t N)const { 
    return mm_ptr<T, align>(static_cast<T*>(_mm_malloc(sizeof(T)*N, align))); 
    } 
}; 
template<class T, size_t align> 
struct aligned_make { 
    mm_ptr<T, align> operator()()const { 
    return aligned_make<T[],align>{}(1); 
    } 
}; 
template<class T, size_t N, size_t align> 
struct aligned_make<T[N], align> { 
    mm_ptr<T, align> operator()()const { 
    return aligned_make<T[],align>{}(N); 
    } 
}: 
// T[N] and T versions: 
template<class T, size_t align> 
auto make_aligned() 
-> std::result_of_t<aligned_make<T,align>()> 
{ 
    return aligned_make<T,align>{}(); 
} 
// T[] version: 
template<class T, size_t align> 
auto make_aligned(size_t N) 
-> std::result_of_t<aligned_make<T,align>(size_t)> 
{ 
    return aligned_make<T,align>{}(N); 
} 

jetzt mm_ptr<float[], 4> ist ein einzigartiger Zeiger auf ein Array von float s, die 4 Byte ausgerichtet sind. Sie erstellen es über make_aligned<float[], 4>(20), die 20 floats 4-Byte-ausgerichtet oder make_aligned<float[20], 4>() erstellt (Kompilierzeitkonstante nur in dieser Syntax). make_aligned<float[20],4> gibt mm_ptr<float[],4> nicht mm_ptr<float[20],4> zurück.

Ein mm_ptr<float[], 8> kann sich bewegen mm_ptr<float[],4> aber nicht umgekehrt, was ich denke, ist nett.

mm_ptr<float[]> kann jede Ausrichtung übernehmen, garantiert aber keine.

Overhead, wie mit einem std::unique_ptr, ist grundsätzlich Null pro Zeiger. Code Overhead kann durch aggressive inline ing minimiert werden.

+0

Ich mag die Idee von 'move-construction' von weniger zu einem mehr ausgerichteten Zeiger. – romeric

+0

@romeric von mehr zu weniger – Yakk

+0

Oh ja, ja! – romeric