neuro2019-04-05std::alignを動的メモリ確保と一緒に使うC++はい。C++17を使っている人はnewが全部やってくれるようになったので不要な話です。C++17以前のコードのため。Introductionアライメントとは、ざっくり言うとメモリ上の位置調整であり、普段は気にしなくていい(コンパイラやmallocがちゃんとやってくれる)。SIMDなどの特殊用途や、キャッシュ効率の最適化などの際は考慮する必要がある。x8とか)を足し算するとかいう感じの命令だが、この256bitの値(普通は何かの配列)がメモリ上で32byte境界にぴったり乗っている(メモリアドレスが32の倍数になっている)と読み書きが楽だったりする。CPUキャッシュは大抵64byte境界から64byteを一単位として持ってくるのだが、キャッシュされて欲しいデータが一つのキャッシュラインに収まっていれば効率がよくなる。sharing"などでググってみると面白い話が出てくる。値がスタックに乗る場合、スタックメモリはコンパイラの管理下にあるので、そのアドレスはコンパイラが制御できる。ヒープアロケーションを伴う場合、普通のmallocやnewにはアライメント情報を渡せないので(newに関してはC++17以前の話)、適切なアライメントになっている保証はない。これを解決する方法は大きく分けて3つある。コンパイラを新しくしてC++17を使うか、posix_memalignや_mm_alloc(intel製CPUのみ)、_aligned_alloc(Windows)などの環境依存な関数を使うか、多めにメモリを確保してアライメントが満たされる領域を使うかだ。std::alignはこの第三の選択肢を実装するためにもってこいだ。これは、あるメモリ領域の中に、指定したアライメントを満たす指定したサイズの領域があれば、その領域の先頭を返す。思ったものの意外と難しかったという記録だ。aligned_free(void*)できる必要がある。これがあとで結構色々な制限に繋がってくる。基本的なアイデアを少し詰めていこう。大きめの領域を確保して、そこからアライメントがあっている箇所を抜き出してくる。min_alignだけ広く取れば、必ずアラインされている(req_alignの倍数になるメモリアドレスから始まる)領域があることになる。常に16byteアラインが満たされる時、64byteアライメントを満たすには、48byteだけ広く取っておけば大丈夫だ。req_alignここまではいい。メモリを解放しようと思うと、これでは足りない。いうのも、アライメントされた領域のポインタを返すと、もともと確保されたメモリ領域がどこまでだったかわからなくなるからだ。48の3通りあり得、先頭ポインタからはその情報は消えている)。パディングの量か、本来の先頭へのポインタを覚えておく必要があるのだ。どちらを覚えておくべきだろうか。パディングの量だとメモリ消費が少なくなりそうだが、よく考えると要求されるアライメントのサイズが不明なので結局ptrdiff_tを使うことになりそうだ。するとポインタを覚えておくのと特に変わりない。パディングに「パディングのサイズ」自体を含めるかどうかが曖昧で、こういうミスが許されないところで使うのは怖い(いや、プログラムは大抵どこでもミスは許されないのだが)。なると、本来の先頭を指すvoid*を覚えておくのが楽だろう。ここを覚えておきたいではどこにそのvoid*を置くか。今後使う領域に置くわけには行かないので(早晩書き換えられてしまうだろう)、選択肢は概ね3つだ。真の先頭、アラインされた領域の直前、アラインされた領域の直後だ。|void*|だが、実際にfreeする時のことを考えると、取り得る選択肢は1つに絞られる。上の図では真ん中にある、「アラインされた領域の直前」しかない。最初の選択肢は、先に述べてある「パディングのサイズが不明」という問題を解決できない。そもそもこのvoid*を取りに行けない。regionがどれくらいの長さかわからず、結局これも取りに行けないのだ。いうわけで、aligned_free(void*)からvoid*をsizeof(void*)バイトだけ一つ後ろにずらすだけで必ず取りに行ける、真ん中の選択肢が唯一生き残る。その場合、aligned_freeの実装は以下のようになるだろう。受け取ったvoid*をvoid**だと思い込み、一つ後ろを見る(これでvoid*一つぶん後ろに動く)。そこの内容を取ってきて、std::freeに渡す。}}ここまでの議論があれば、わりかしシンプルな実装に落ち着いたように思えるが、いきなりこれを見ると驚くかもしれない。いうわけでメモリ解放ができるようになったので、アロケーションの方をちゃんと実装しよう。アライメントされた領域を確保するには、パディングと、freeするためのvoid*分だけ大きな領域を取らなければならない。typeに対して正しくアラインされたものを返すらしいので、void*のためのアライメントは保証される。以降もvoid*は使うので、これを保証されたアライメントの値ということにしよう。...}この領域からstd::alignを使ってアライメント要求に合う領域を取り出すのだが、その前に、最低限void*を格納できる領域を先に確保しておく必要がある。...}この時点でaligned_ptrの値が欲していた領域の先頭を指すポインタになっている。忘れずその一つ前に本当の先頭ポインタをメモしておいて、完了だ。aligned_ptr;}こっちはまあ、想像通りの複雑さになった。加えて、本当はここに「req_alignがmin_alignと同じかそれより小さい場合」の対応を入れる必要がある。alignof(T)>>;みたいな使い方する時は普通にあり得るんだよな......。Resultvalgrindでリークチェックとかしたけど大丈夫そうでした、まる。Conclusionstd::alignとstd::mallocでaligned_alloc作ろうとすると超絶面倒くさいから、環境依存の関数をマクロで使いわけましょう。Contractそういえば最近はC++関連のことに触れていないなと思ったので、...2018-10-01アライメント解説しばらく放置してしまっていた。(7)はてなブログをはじめよう!tniinaさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。