2016-07-23 7 views
0

イントリンシックスでプログラミングしている間、次の問題が発生しました。 ローカル変数をロードまたは格納する場合、インライン関数でメモリ違反エラーが発生しますが、関数がインライン化されている場合に限ります。インライン関数でスタック変数が整列していない理由がわかりません。インライン関数でローカル変数が整列していません

私はGCC 4.9、5.3、6.1の多くの異なるバージョンでそれをテストしました。失敗した

例:

static inline foo(double *phi){ 
    double localvar[4]; 
    __m256d var = _mm256_load_pd (phi); 
    __m256d res = _mm256_mul_pd(var, var); 
    _mm256_store_pd (localvar, res); // <- failed due to memory violation 
    ... 
} 

私は__attribute__ ((aligned (32)))を追加したり、inlineを削除する場合、この関数は、それが必要のように動作します。

なぜ私は説明してくれますか(詳細をお聞かせください)、どうして一般的なローカル変数が__attribute__ ((aligned (32)))とインライン関数のローカル変数を追加せずに整列するのですか?

+2

偶然、私は思います。 – MikeCAT

+0

ご使用の環境に追加のローカル変数を使用して[このコード](http://www.tutorialsp.com/compile_c_online.php?PID=0Bw_CjBb95KQMNFRDcGd3NmxDQmc)を試してください。配列はまだ整列していますか? – MikeCAT

+1

標準Cでは、ローカル変数のアラインメントは必要ありません。あなたが望む結果を得るためには、あなたが言及したようなコンパイラ固有の拡張を使用する必要があります。 –

答えて

2

32バイトアライメントを提供するには、余分な指示が必要です(ABIは16バイトのアラインメントしか保証しないため、alignas(32)または__attribute__((aligned(32)))のバージョンのasmを参照してください)。 もちろん、無料ではないので、を求めないとコンパイラはそれをしません。 (ABIドキュメントへのリンクについては、gccの-mpreferred-stack-boundary which controls thisおよびタグwikiを参照してください)。

double localvar[4];は、各要素が自然に整列されるために8バイト整列する必要があります。 SysV x86-64 ABIは、C99可変サイズ配列に対して16バイトのアライメントを保証します。通常のコンパイル時定数の配列がデフォルトで16-B配列を取得するかどうかはわかりません。

しかし、何らかの理由により、GCCの現在のバージョンは__m256dローカル変数を有するテスト機能に32bにスタックを整列させます。 -O3では、スタックにそれらを流出させないので、無駄になります(このようなバグのあるコードを動作させる以外)。 gccがこれを削除しないという事実は、欠けている最適化です。

私のテスト関数(実際にコンパイルする)には他のlocalsがないので、2倍の配列も32Bで整列されています。おそらく、あなたはそれを他の地元の人がいる呼び出し元にインライン展開しており、それは配列のさまざまな配置につながります。

Here's the code on the Godbolt compiler explorer:あなたのコードがそれをインラインいないときに動作するためになぜ起こるか

extern void use_buffer(double*); 
// static inline 
void no_alignment(const double *phi){ 
    double localvar[4]; 
    __m256d var = _mm256_load_pd (phi); 
    __m256d res = _mm256_mul_pd(var, var); 
    _mm256_storeu_pd (localvar, res);   // use an unaligned store since we didn't request alignment for the buffer 
    use_buffer(localvar); 
} 

    lea  r10, [rsp+8]     // save old RSP (in a clumsy way) 
    and  rsp, -32      // truncate RSP to the next 32B boundary 
    push QWORD PTR [r10-8]   // save more stuff 
    push rbp 
    mov  rbp, rsp 
    push r10 
    sub  rsp, 40 
    ...   vmovupd YMMWORD PTR [rbp-48], ymm0  ... // function body 
    add  rsp, 40 
    pop  r10 
    pop  rbp 
    lea  rsp, [r10-8] 

です。たとえ最適化せずにコンパイルした場合や、別の定義が必要でないことをコンパイラに知らせるためにstaticを使用しなかった場合を除き、inlineキーワードがなくてもインライン化されないのは不思議です。

2

_mm256_store_pdでは、格納するメモリアドレスを32バイトの境界に合わせる必要があります。しかし、Cでは、8バイトのdoubleの標準的な配置は8バイトの境界だと思っています。

関数がインライン化されていないときには、32バイト境界でlocalvar配列が開始されます。これが保証か運が良いかわかりません。理論上関数をインライン展開すると何も変わってはいけないので、私は運があると推測しています。コンパイラは、適切な数のバイトをスタックにプッシュして整列させることができます。また、32バイトのアライメントが保証される理由もありません。

インライン展開すると、関数を呼び出す場所にコードが入力されたかのように動作します。したがって、保証されている32バイトの整列ではなく、localvarが8バイト整列されることのみが保証されます。私は適切な解決策はあなたの問題を解決する整列された属性を使用することだと思います。また、アラインメント要件なしで同じことを行う組み込み関数_mm256_storeu_pdを使用することもできます。私のhaswell CPUでの私の経験から、それはちょうど速いです。

+0

「インライン」なしで動作するのは純粋な運です。 x86-64 ABIは通常、関数呼び出し時にスタック16Bを整列した状態に保ちます。 'storeu'はこのような小さな1ベクトル全体の配列にとっては良い選択ですが、より大きなスクラッチ配列を持っていれば、それを実行時に並べることは安いです。これにより、キャッシュラインの分割が回避され、店舗運営に役立つことがあります(忘れてしまいます)。 –

+0

更新、純粋な運だけではないことが判明しました。これはgccの欠落最適化に関連しています。 –

関連する問題