10

Windows 64ビットターゲット用のGCC 4.6.1に基づくMinGW64ビルドを使用しています。新しい Intel の AVX 命令をいじっています。私のコマンドライン引数は-march=corei7-avx -mtune=corei7-avx -mavx.

しかし、スタックにローカル変数を割り当てるときに、セグメンテーション違反エラーが発生し始めました。GCC は整列された move と をVMOVAPS使用して移動し、これらの命令には 32 バイトの整列が必要です。ただし、Windows 64 ビットのスタックには 16 バイトのアラインメントしかありません。VMOVAPD__m256__m256d

GCC のスタック アラインメントを 32 バイトに変更するにはどうすればよいですか?

使用してみ-mstackrealignましたが、16バイトにしか整列しないため、役に立ちませんでした。とにかく16バイトに整列し__attribute__((force_align_arg_pointer))ます。これに対処する他のコンパイラ オプションを見つけることができませんでした。どんな助けでも大歓迎です。

編集: を使用してみ-mpreferred-stack-boundary=5ましたが、GCC によると、このターゲットでは 5 はサポートされていません。私はアイデアがありません。

4

3 に答える 3

17

この問題を調査し、GCC バグ レポートを提出したところ、これが MinGW64 関連の問題であることがわかりました。GCC Bug#49001を参照してください。どうやら、GCC は Windows で 32 バイトのスタック アラインメントをサポートしていないようです。これにより、256 ビット AVX 命令の使用が効果的に防止されます。

この問題に対処する方法をいくつか調べました。最も単純で鈍い解決策は、アラインされたメモリ アクセス VMOVAPS/PD/DQA をアラインされていない代替 VMOVUPS などに置き換えることです。 GCC によって生成された入力アセンブラー ファイル:

import re
import fileinput
import sys

# fix aligned stack access
# replace aligned vmov* by unaligned vmov* with 32-byte aligned operands 
# see Intel's AVX programming guide, page 39
vmova = re.compile(r"\s*?vmov(\w+).*?((\(%r.*?%ymm)|(%ymm.*?\(%r))")
aligndict = {"aps" : "ups", "apd" : "upd", "dqa" : "dqu"};
for line in fileinput.FileInput(sys.argv[1:],inplace=1):
    m = vmova.match(line)
    if m and m.group(1) in aligndict:
        s = m.group(1)
        print line.replace("vmov"+s, "vmov"+aligndict[s]),
    else:
        print line,

このアプローチは非常に安全で簡単です。まれにパフォーマンスの低下が見られましたが。スタックがアライメントされていない場合、メモリ アクセスはキャッシュ ラインの境界を越えます。幸いなことに、コードはほとんどの場合、整列アクセスと同じくらい高速に実行されます。私の推奨事項:重要なループではインライン関数を使用してください!

また、別の Python スクリプトを使用して、すべての関数プロローグのスタック割り当てを修正し、常に 32 バイト境界に揃えようとしました。これは一部のコードでは機能するようですが、他のコードでは機能しないようです。GCC が (スタック ポインターに関して) アラインされたローカル変数を割り当てるという GCC の善意に頼る必要がありますが、これは通常行われます。これは常に当てはまるとは限りません。特に、関数呼び出しの前にすべての ymm レジスタを保存する必要があるため、重大なレジスタ スピルが発生する場合はそうです。(すべての ymm レジスターは callee-save です)。興味があればスクリプトを投稿できます。

最善の解決策は、GCC MinGW64 ビルドを修正することです。残念ながら、先週使い始めたばかりで、内部の仕組みについては知りません。

于 2011-05-17T02:19:44.543 に答える
1

あなたが望む効果を得ることができます

  1. 変数を変数としてではなく、構造体のフィールドとして宣言する
  2. 適切な量​​のパディングによって構造体よりも大きい配列を宣言する
  3. ポインター/アドレス演算を実行して、配列内の 32 バイトで整列されたアドレスを見つける
  4. そのアドレスを構造体へのポインターにキャストする
  5. 最後に、構造体のデータ メンバーを使用する

malloc() がヒープ上の要素を適切に整列させない場合は、同じ手法を使用できます。

例えば

void foo() {
    struct I_wish_these_were_32B_aligned {
          vec32B foo;
          char bar[32];
    }; // not - no variable definition, just the struct declaration.
    unsigned char a[sizeof(I_wish_these_were_32B_aligned) + 32)];
    unsigned char* a_aligned_to_32B = align_to_32B(a);
    I_wish_these_were_32B_aligned* s = (I_wish_these_were_32B_aligned)a_aligned_to_32B;
    s->foo = ...
}

どこ

unsigned char* align_to_32B(unsiged char* a) {
     uint64_t u = (unit64_t)a;
     mask_aligned32B = (1 << 5) - 1;
     if (u & mask_aligned32B == 0) return (unsigned char*)u;
     return (unsigned char*)((u|mask_aligned_32B) + 1);
}
于 2012-04-26T06:20:59.317 に答える