日別アーカイブ: 2014年3月11日

C++11 alignas/alignof メモリアライメント

SSE 等の SIMD 命令やキャッシュを意識したプログラムではメモリのアライメントを
考慮する必要があります。
これまではコンパイラ毎の拡張命令に頼っていましたが、
C++11 では言語仕様に含まれるようになりました。
メモリアクセスの同期命令も含めて、低レベルなメモリ命令を積極的に取り込んでいる印象です。

CC           alignas                         alignof
-----------------------------------------------------------------
VisualC++    __declspec(align(byte))         __alignof(type)
gcc/clang    __attribute__((aligned(byte))   __alignof__(type)
C++11        alignas(byte)                   alignof(type)

alignas はメモリ配置時のアライメントを宣言します。

// 変数宣言
__declspec(align(32)) int  array[8];      // VC
int  array[8] __attribute((aligned(32));  // gcc/clang
alignas(32) int  array[8];                // C++11

↑変数 array は 32byte 単位のアドレス境界に配置されます。

// 型宣言

// VC
__declspec(align(32)) struct AVECT { float pos[4]; };
struct __declspec(align(32)) AVECT { float pos[4]; };

// gcc/clang
struct AVECT { float pos[4]; } __attribute((aligned(32));
struct __attribute((aligned(32)) AVECT { float pos[4]; };

// C++11
struct alignas(32) AVECT { float pos[4]; };

// 使用時
AVECT position;

↑ position も 32byte 単位のアドレスに配置します。

VC と gcc/clang の attribute は若干書式に違いがあります。
alignas を VC のように struct の前に記述すると変数への修飾となり、
型宣言に含まれません。VC では含まれているようです↓。

alignas(32) struct AVECT2 { float pos[4]; }  va;

// VC Nov 2013 CTP : alignof(AVECT2) == 32
// gcc4.8/clang3.4 : alignof(AVECT2) == 4

static (global) に宣言した変数は静的にアドレスが求まるので、
コンパイル時 (link時) に解決します。
ただしプログラム (data segment) がロードされるアドレスが、
より大きなアドレス境界に配置していることが条件となります。

(1) 基準となるメモリアドレスのアライメント
(2) メモリを pack したときの整合性

つまりコンパイラ側が行うのは (2) と (1) の一部になります。

基準アドレス(1)の生成
------------------------------------------------
data    OS が行う
stack   実行時に行う (コンパイラが自動的に生成)
heap    ライブラリまたはユーザー任せ

stack に宣言した変数は、実行するまでアドレスがわからないので
実行時に端数を切り捨てるコードが埋め込まれます。

; clang x64 alignas(32)
    pushq   %rbps
    andq    $-32, %rsp  ; alignas(32)
    subq    $32, %rsp   ; int array[8]
; clang armv7l alignas(32)
    add    r11, sp, #8
    sub    sp, sp, #80
    bic    sp, sp, #31  ; alignas(32)

heap の場合は確保したメモリが alignment に従っていない可能性があるため
利用時にアドレス整合を意識しなければなりません。

alignof を用いるとコンパイル時に型情報を判断できるので、alignment に
対応したアロケータを作ることができます。

template
inline T* flNew( A0&&... a0 )
{
    return  new( malloc_aligned( sizeof(T), alignof(T) ) ) T( std::forward(a0)... );
}

template
inline void flDelete( T* ptr )
{
    ptr->~T();
    free_aligned( ptr );
}

この flNew() で作成した AVECT ↓は、宣言時に alignas(32) が指定されているため
32byte 単位で確保されます。(malloc_aligned が実装されていると仮定する)

AVECT* ap= flNew(); // alignas(32)

関連エントリ
VisualStudio と C++11 、コンパイラの違いなど
C++11 Lambda function ラムダ関数
C++11 Variadic Templates 可変長引数のテンプレート
スレッド同期命令の比較 C++11 とコンパイラ
C++11 Rvalue Reference 右辺値参照