ErupteDを使ってDからVulkan触ろうと思ったけどできなかった話
最近しょうもない記事ばっかり書いてるけど、無職だとこうやってメリハリつけないとだめかなーと思っての行為なので許容して。
DでVulkanさわろうとおもうと https://github.com/ParticlePeter/ErupteD というライブラリが一番スターついてるっぽいのでこれを使うことにする。
もろもろ必要そうなやつを入れて
$ apt install libvulkan-dev vulkan-utils
git cloneしたeruptedでexampleを動かそうとしたら、無事に死んだ。
$ dun run :devices (...) erupted:devices 2.0.15+v1.1.91: building configuration "application"... Linking... Running ./erupted_devices object.Exception@examples/devices.d(13): VK_ERROR_INCOMPATIBLE_DRIVER ---------------- /home/kubo39/dlang/dmd-2.083.0/linux/bin64/../../src/phobos/std/exception.d:515 pure @safe void std.exception.bailOut!(Exception).bailOut(immutable(char)[], ulong, scope const(char)[]) [0x7b31f62] /home/kubo39/dlang/dmd-2.083.0/linux/bin64/../../src/phobos/std/exception.d:436 pure @safe bool std.exception.enforce!().enforce!(bool).enforce(bool, lazy const(char)[], immutable(char)[], ulong) [0x7b31ede] examples/devices.d:13 void devices.enforceVK(erupted.types.VkResult) [0x7b2c6c4] examples/devices.d:34 _Dmain [0x7b2c80a] Program exited with code 1
手元の環境だとだめそうですね。
LDC向けのSIMDライブラリを作っているけど、いろいろつらい
x86intrin
勉強がてらにLDCでx86intrinsicっぽくSIMDを書けるライブラリを書いてみている。まだまだ全然途中なので使わないでください。
_m128iとかじゃなくてそのままbyte16とかを直接 _mm_XXX
の引数にとったりするようにしてるけど、将来的にはたぶん変えます。
そういう作業をしてる中でいくつか問題にあたったので、書き残しておきます。
LDCでunittestブロックとDFLAGSの指定を共存できない
そういう問題があるので dub test --compiler
だと DFLAGS="-mattr=+sse4.2
つけてもsse4.2環境のテストができない。別にスクリプトを作ってそっちを実行するようにして回避。
intrinsicがundefined reference
clangやrustと同じバージョンのLLVMを使っているけどLDCだけundefined referenceといわれるintrinsicがあったりする。cvt系とか全部死亡です、ありがとうございました。
Comment out convert intrinsics since undefined reference error · kubo39/x86intrin@4c831f9 · GitHub
普通の関数がundefined reference
sse2でintrinsic使ってない関数を呼び出そうとしたらundefined referenceで無事死亡。
おわりに
perfを使ってD言語のプロファイリング結果をみる
まず手元の /proc/sys/kernel/perf_event_paranoid を確認する。Ubuntuとかだと最弱設定になってるので変えとく。
$ cat /proc/sys/kernel/perf_event_paranoid 3 $ sudo sh -c 'echo 1 > /proc/sys/kernel/perf_event_paranoid'
普通に perf record にかける。
$ perf record dmd fib.d [ perf record: Woken up 1 times to write data ] [kernel.kallsyms] with build id ef8b1ed123757213c70bca103ce70b59825d9c11 not found, continuing without symbols [ perf record: Captured and wrote 0.052 MB perf.data (1240 samples) ]
perf report で出力するときに ddemangle に食わせるとsymbol nameがdemangleされるので便利。
$ perf report| ddemangle| head -20 [kernel.kallsyms] with build id ef8b1ed123757213c70bca103ce70b59825d9c11 not found, continuing without symbols # To display the perf.data header info, please use --header/--header-only options. # # # Total Lost Samples: 0 # # Samples: 1K of event 'cycles:uppp' # Event count (approx.): 706573056 # # Overhead Command Shared Object Symbol # ........ ........ ....................... ...................................................................................................................................................................................................................................................... # 12.51% ld libbfd-2.30-system.so [.] bfd_hash_lookup 7.98% ld libc-2.27.so [.] __gconv_transform_utf8_internal 5.34% dmd dmd [.] void dmd.lexer.Lexer.scan(dmd.tokens.Token*) 3.80% ld libc-2.27.so [.] __strcmp_sse2_unaligned 3.50% ld libc-2.27.so [.] __mbsrtowcs_l 3.32% ld x86_64-linux-gnu-ld.bfd [.] walk_wild_section_general 3.31% ld libc-2.27.so [.] internal_fnwmatch 3.25% ld x86_64-linux-gnu-ld.bfd [.] walk_wild_section_specs3_wild2 2.81% ld libc-2.27.so [.] __strnlen_avx2
まあここまでは別によい。
昔 dmd -profile と perf(oprofile) の使い分けってどうなの?みたいなの聞かれたときに dmd -profile はLinuxだとrbtsc命令使っててコア間同期がうんたら〜みたいな話をしてしまって、まあそれは間違いじゃないのだけれど、実際そもそもdmd -profileはtracing方式(すべてのDの関数コールにフックする)のに対しperfはsampling方式(タイマーの間隔を決めて現在のスタックの状態を収集してどの関数が統計的に多くよばれてるか)という違いがあり、用途によって使い分けられるという話をすべきだったなー、と思った。(長い)
でも大体のケースはperfでいいんじゃないのかな、みたいな。特にsampling方式だと実際に負荷がかかってる本番環境とかでも計測できるのが嬉しい。実はこのパスがめっちゃ呼ばれてる、みたいなのとかは開発環境じゃわからなかったりするので。
D言語で一ヶ月前を表すとき
日付の扱いは忘れそうなのでメモ。
一ヶ月前を表す場合、可能であれば前月の同日同時刻を表し、前月に同日が存在しない場合は差分を計算して付け足す。
import std.datetime; import std.stdio; void main() { auto currTime = Clock.currTime(UTC()); writeln(currTime); // 2018-Sep-30 20:12:00.0198441Z writeln(currTime.add!"months"(-1)); // 2018-Aug-30 20:12:00.0198441Z currTime = Clock.currTime(UTC()); writeln(currTime.add!"months"(1)); // 2018-Oct-30 20:12:00.0199153Z auto date = Date(2018, 7, 31); writeln(date.add!"months"(-1)); // 2018-Jul-01 date = Date(2018, 7, 30); writeln(date.add!"months"(-1)); // 2018-Jun-30 }
なので3月とかはこうなる。
import std.datetime; import std.stdio; void main() { auto date = Date(2018, 3, 31); writeln(date.add!"months"(-1)); // 2018-Mar-03 date = Date(2018, 3, 29); writeln(date.add!"months"(-1)); // 2018-Mar-01 // うるう年は考慮される date = Date(2016, 3, 29); writeln(date.add!"months"(-1)); // 2016-Feb-29 }
D言語のprecise(正確な) GC
(追記: 編集あり)
実は結構前に保守的なGCから正確なGCになって いた いなかった。仕組みはマーク・アンド・スィープで変わらないが性能は大幅に向上が期待できそう。
これによって、
- メモリ確保時
- ゴミ集め時
- 並列にスキャンするようになったのでパフォーマンスが向上
- 特定の条件を満たしていればストップ・ザ・ワールド(STW)が発生しなくなる
- 例えば新たに追加された型レベルの仕組み isolated によって静的にスレッドローカルなヒープのみを使っているかを判定(isolatedはスレッドローカルなヒープにつく型といえる)でき、isloatedしかないと判定したときはSTWしない
わりとGoに近い感じで、アロケータの柔軟性でGoより優れていて、インクリメンタルGCによるレイテンシの仕組みはシステムプログラミング言語という特性上入れなかったという感じか。 さすがにJVMに比べると厳しいが、これまでの保守的GCに比べるとずいぶんよくなっている、はず。
(追記ここから)
まだマージされてなかった。。すまんな、、
OpenSUSEを試してみてる
デスクトップ環境として最新のLeap使ってみてる。 感想としては、
- IM(Mozx <-> US)の切り替えがめちゃくちゃ遅い。普通にタイピングに支障が出るレベル。
- 方向関係なく、「hankakuzenkaku」で切り替えると遅く、「hankakuzenkaku+Shift」だと気にならない程度の遅さになる。追ってみてるがよくわからん。
- キーボードレイアウトの切り替えが何故か反映されない?setxkbmapでは変更できずYaSTを使って一時的にできたと思ったがいつのまにかまたUS配列に戻っていた。
- 「設定」とYaSTの切り分けが意味不明。設定の「デバイス」->「キーボード」でbindingとかを変更するようになっているのにキーボードレイアウトはYaSTとか意味がわからん。
- GNOMEデフォルトのキーバインドの操作性がいまいち
- これは慣れもある気がするけどなんとなく使いづらい感じはしてる、こっちもi3wmにしようかどうか
総じてクソと言わざるをえない、なんのためにわざわざLeap選んだと思ってんだよ。こういう雑事に悩まされないための初心者向けのやつなんじゃねえのか。 Ubuntuもいろいろ不満に思うことはあるけど、完成度は天地ほどの差がある。
まあ単に慣れてない、わかってないだけならいいんだけど、まさかこのレベルではまるとはなあ。。。とりあえず初心者にはおすすめできないですね。
プロセス起動でposix_spawnとかvforkとかを使うはなし
posix_spawn/vfork/clone(CLONE_VFORK)
はメモリを共有するので速い. 速いが、親子でメモリを共有するので危険である.
それでも速いのでプロセス起動で気をつけて使っている言語があり、どういう実装をしているか調べた.
前提
- fork-exec間はasync-singal-safeな関数しか使ってはいけない http://mkosaki.blog46.fc2.com/blog-entry-886.html
- mallocを使っても問題ないかは実装依存 http://mimosa-pudica.net/linux-closefrom.html
jemallocはおそらくだめそんなことはなかった- いまどきどれもpthread_atforkでロックの開放処理をしているのかも
Rust
- どうも元ネタを追ってみるとFreeBSDでmalloc(jemalloc)が固まる問題らしい https://github.com/rayon-rs/rayon/issues/540
- これ自体は別問題で, 速度がほしいからという理由のようだ
posix_spawnを使う条件
- ENOENTを直接返すこと
- 単にposix_spawn側の実装バグの問題
- Linux/MacOS/FreeBSDであること
- ENOENTを直接返すことができるOSのため
- Linuxの場合, Glibcのバージョンが2.24以上であること
- ENOENTを返すposix_spawnはこのバージョンから
- getcwdでディレクトリを返すこと
- getgidが成功する
- getuidが成功する
- PATHが変更されていないこと
- race conditionがある
どう使っているか
- posix_spawnに持たせる属性を決めている
posix_spawnattr_setsigmask
シグナルをマスクしている- forkするスレッドはfork-exec間でシグナルを受け付けたくない
posix_spawnattr_setsigdefault
SIGPIPEをSIG_DFLにしている- シグナルハンドラでグローバル変数を参照するかもしれないので避けたい
- 別スレッドがシグナルをうけたときにメモリを書き換えるかも、という危険があるので
- (追記): そういえばRustはランタイム初期化時にSIGPIPEをSIG_IGNにセットしているので、その設定によって起動するプロセスが影響を受けることを避けたいためにSIG_DFLにしなおしているというほうがメインな気がする
- (追記): glibc2.29+だとchdirも指定できるようになっている
まとめ
Dellの中の人でFreeBSDのコアコミッタの人なので、けっこう信頼できるんじゃないだろうか. ただいくつか気になる点はあり、
すべてのシグナルに対してSIG_DFLを定義しなおさなくてもいいのか- してたわ
- uid/gidが特権ユーザでないことはチェックしなくてよいのか
- これはposix_spawn側で対策されていそうな雰囲気がある
- PATHの変更をみていること
- PATHは状態変わることで振るまいは変わるが具体的にどういった問題があるんだろう
- race conditionがある
(追記) あとでいろいろ調べ直したけどめちゃくちゃよく考えられてる実装だった。
Go
clone(CLONE_VM | CLONE_VFORK)を使う条件
どう使っているか
- clone(2)に
SIGCHLD|CLONE_VFORK|CLONE_VM
を指定して使っている - cloneでシステムコール呼び出しをしている場合, 親子はスタックを共有しないらしい
- 引数にmmap()で確保したメモリを渡すことで制御できるようだ
fork-exec間に子プロセスは何をしているか
- keep capabilitiesをprctlでセットしている(実行ユーザとかが変更されたかチェック)
- ほかにもいろいろやっているがforkと共通なので割愛
- 主にcapabilitiesとかなので権限まわりをセキュアにしたいのだろう
まとめ
自前でがんばっているためコードはかなり複雑になる. 現時点では制限がかなり厳しい(Linuxかつamd64のみ)ので、逆に意図しない問題は起きづらそう. 当然ゴルーチンが全部停止するためスループットが悪化するという懸念はされている.
Ruby
vforkを使う条件
- vforkを実装していること
- uid/gidが特権ユーザでないこと
- setuidとかされたくない
fork-exec間に子プロセスは何をしているか
- シグナルに対してSIG_DFLを再定義
- invalidなsignumは無視
SIG_IGN
が定義されてたらSIG_IGN
に- その場合でもSIGPIPEはSIG_DFLにする
- sigprocmaskでシグナルをmaskする
- pthread_sigmaskでないのはasync signal safeなものを使いたいため
pthread_setcancelstate
でPTHREAD_CANCEL_DISABLE
して cancel が無効であることを宣言するpthread_cleanup_push
で登録した cancellation cleanup handler も呼び出されるのは困る- シグナルハンドラと同様の理由
まとめ
だいぶ安全よりに倒しているのではないか.
Nim
posix_spawnを使う条件
cloneを使う条件
- Linuxだったら基本使う
- fork-exec間はasync-signal-safeな関数しか使わないようになっている
どう使っているか
- 基本Rustと一緒だけどシグナルまわりが怪しい
- 具体的にいうとSIGPIPEをSIG_DFLにセットしていないとかが違う
- 追記:これはRustがランタイム起動時にSIGPIPEをSIG_IGNにセットする特殊事情なので特におかしいことではない
- clone()を使っているときに親がchdirしてからclone()を呼び、また親のディレクトリを戻している?
- chdirしてからcloneが返ってくるまで親のカレントディレクトリが変わるというわりとめちゃくちゃな実装
まとめ
基本的に優良な実装にみえる.
ただしいろいろ不明すぎて安全に使えるのか不安.
シグナルまわりとか.
clone()を使っていてGoっぽいけどカレントディレクトリ指定とかやたら怪しい実装になっている