やねうら王miniをx86(32bit環境)でコンパイルが通るように作業してたのですが、どうもアライメントを合わせるのが壊滅的に難しいです。
SSE命令を使うときに16byte単位にアライメントが合っていないと実行時にエラーになるものがあります。_mm_store_si128()等です。
まあそれはそれで仕方ないのでアライメントを合わせようとBitboardなどのstructにalignas(16)をつけたわけです。
ところが、ヒープから割り当てたときにはこれがアライメントされていない可能性があるようです。
1 2 3 4 5 6 7 8 |
thread.cpp template std::thread* th = new T(); *th = std::thread(&T::idle_loop, (T*)th); return (T*)th; } > source\thread.cpp(15): warning C4316: 'MainThread': ヒープで割り当てられたオブジェクトが 16 にアラインメントされていない可能性があります |
なぜx64環境でこの問題が顕在化しないかというと、x64環境ではnewは16byteにアライメントされたポインターが返るからで、x86環境は8byte単位にアライメントされたポインターが返ることが原因のようです。(コンパイルオプションでどうにかならないのかな?)
てか、何故、コンパイラーがそのことを知っていて、警告を出してくるのか不思議ですが、きっと、何か泥臭いコードが書いてあるのでしょう。
それはそれとして次のようなallocatorを書きました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// ---------------------------- // aligned memory allocator // ---------------------------- // heapから確保したときにalignasが利かないので自前のallocatorが必要になるという..。 // x64環境だと16byte単位でalignされたメモリになっているため、alignas(16)をつけておけば、このaloocator自体不要だと思う。 // 256bit hash keyを使う場合など、32byteでalignasしたいときには要ると思う。 #ifdef IS_64BIT template inline void aligned_free(void* ptr) { free(ptr); } #else template inline T* aligned_new() { const auto ptr_alloc = sizeof(void*); // この分余分に確保して[-1]のところに元のポインターを保存しておく。 const auto align_size = alignof(T); size_t request_size = sizeof(T) + align_size; const auto needed = ptr_alloc + request_size; auto alloc = ::operator new(needed); void* alloc2 = (uint8_t*)alloc + ptr_alloc; auto ptr = std::align(align_size, sizeof(T), alloc2, request_size); ((void**)ptr)[-1] = alloc; return new((void*)ptr) T(); } inline void aligned_free(void *ptr) { if (ptr) { void* alloc = ((void**)ptr)[-1]; ::operator delete(alloc); } } #endif |
そもそもヒープから取ってくるときもalignasつけてるならそれを考慮して欲しいわけですけど。こんなallocatorを書かないといけないのはマジ勘弁です。std::align()みたいな関数は要らないので、aligned_newでも用意していただきたいものです。
これでめでたしめでたしかと思ったら、Tはvirtual関数を含むので仮想関数テーブルへのポインタの分(x86環境では4バイト)、ここで返された値が増えてしまうのです。あれれ..せっかくalignしてポインターを返してるのにな…。どうなるのだ、これ。alignasがついてるから、アドレスの下位bitを切り捨てるなどしてそこはアライメントが合ってることが保証されるのか…。そうか…。
それはそうと、AVX256のstore命令(_mm256_store_si256)等で32バイトでアライメントを要求してくる命令があるんですけど…。その場合は、上のallocatorが必要になります。
それで、そこまではさっと直せたんですけど、x86用にコンパイルした実行ファイル、スレッド生成のところで落ちるので、ランタイムとか追いかけてたのですが、どうもスタックのサイズを400MBにしているとこれが大きすぎるのか落ちるようです。協力詰めsolverで4万手とかの作品を解くのに必要だったので大きくしたのですが、400MBは32bit環境では大きすぎたようです。VC++のランタイムのバグ(?)のような気がしなくもないですが…。
とりあえずスタックサイズは100MBに変更するとうまく動きました。
ちなみにx86だと指し手生成などはx64のときの半分ぐらいの速度しか出ません。悲しい…。
2016/01/18 12:00 追記
コメント欄で教えていただきました。
std::thread* th = new (_mm_malloc(sizeof(T),alignof(T))) T();
のように、_mm_malloc() ~_mm_free()を使うと一発で書けるようです。上のコード要らんかったのか!!なにこれ…。
>CSAライブラリ、特定のバージョンを指定して申請しないといけないのが割とオープンソースのプロジェクトと相性悪い感じある(`・?・´)(@平岡)
>登録は随時可能ですが、5月の選手権で使えるのは1月31日登録分までですね(かず@なのは)
想定外はいつものこと。
そうして、締め切りに追われるのは人気作家の宿命です。
最後の追い込み、がんばってください。
きっとサンタさん達も応援してくれますよ。
https://twitter.com/yaneuraou/status/688667958785146880?ref_src=twsrc%5Etfw
SIMDを使っているのなら、_mm_malloc, _mm_free が正に同じ目的のための関数です。
なるほろ。そんな便利な命令があったとは!MSVCでも、gccでも使えるみたいですね。
https://msdn.microsoft.com/ja-jp/library/8z34s9c6.aspx
こういうのもあります。が使えるでしょうか??
http://en.cppreference.com/w/c/memory/aligned_alloc
C11から、こんなのも。
使ったことないので動くかは知りません。
前者はgccで使えるかどうかよくわからないので_mm_mallocのほうがマシ。後者はVC++2015ではstdlib.hにその定義がないようで使えないようです。
なるほど。残念です。
お目汚しスミマセン。
>x86用にコンパイルした実行ファイル、スレッド生成のところで落ちるので、ランタイムとか追いかけてたのですが、どうもスタックのサイズを400MBにしているとこれが大きすぎるのか落ちるようです。
何スレッド起動しようとしたのでしょうか?
6スレッド目でメモリアドレス使用量が2GBをこえて落ちるのは必然な気がするのですが……
(/LARGEADDRESSAWAREをコンパイルオプションで指定していれば64bitOSでは4GBまで使えるので落ちるのは11スレッド目までは引っ張れますが)
4スレッド起動してたのですが、確かにメモリが確保できないんですね…。Windows10なので実際にアクセスに行くまではタスクマネージャーで見ても使用しているようには見えなくて(Windows10のメモリマネージメント、賢い!)、気づきませんでした。なるほどです。
new (_mm_malloc(sizeof(T),alignof(T))) T();
ほんと、ナニコレー ですわ。
AVX使うと 32 にアラインメントされていない とかなんとかー が出て困っとったんです。
おおきにー!
(`・ω・´)b