Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ruby_1.9.3_GVLおよびロックの改善.pdf

 Ruby_1.9.3_GVLおよびロックの改善.pdf

kosaki

May 04, 2023
Tweet

More Decks by kosaki

Other Decks in Programming

Transcript

  1. What’s GVL What’s GVL • Global(or Giant) VM Lock •

    あらゆる処理を単一のロックで保護 • 実装が簡単。multi thread issueの多くをRuby scriptから隠 蔽できる。などの利点 • 一方システムが複数のCPUを持つときにスケールしない 元凶でもある • Pythonも同様のしくみ • Global(or Giant) VM Lock • あらゆる処理を単一のロックで保護 • 実装が簡単。multi thread issueの多くをRuby scriptから隠 蔽できる。などの利点 • 一方システムが複数のCPUを持つときにスケールしない 元凶でもある • Pythonも同様のしくみ
  2. What’s GVL (cont.) What’s GVL (cont.) •GVLを持っているスレッドだけが走行できる •よって、GVL実装がマルチスレッドプログラ ムの挙動を決める •OSのスケジューラと悩みが似ていて面白い

    •GVLを持っているスレッドだけが走行できる •よって、GVL実装がマルチスレッドプログラ ムの挙動を決める •OSのスケジューラと悩みが似ていて面白い
  3. モチベーション モチベーション f = false Thread.new { ....; f =

    true } Thread.pass until f f = false Thread.new { ....; f = true } Thread.pass until f •1.9.2でハングする(かもしれ ない)スクリプト例 •特にLinux •従来の実装は最近のCPUと相性 がよくなかった •1.9.2でハングする(かもしれ ない)スクリプト例 •特にLinux •従来の実装は最近のCPUと相性 がよくなかった
  4. 1.9.2のGVL design 1.9.2のGVL design • Lockといいつつ、基本的にはずっと保持しっぱなし。 waitする可能性のあるシステムコール(e.g. write)を呼ぶと きだけ離す •

    lock, unlockはコストが高い操作なので出来る限り回数を 減らしたい。メソッド呼び出しのたびにロック取得開放 なんてバカバカしい。Python, QEMU, 昔のLinuxも似たよ うなもの • GVL acquire/releaseの実態はただの pthread_mutex_{lock,unlock} • Lockといいつつ、基本的にはずっと保持しっぱなし。 waitする可能性のあるシステムコール(e.g. write)を呼ぶと きだけ離す • lock, unlockはコストが高い操作なので出来る限り回数を 減らしたい。メソッド呼び出しのたびにロック取得開放 なんてバカバカしい。Python, QEMU, 昔のLinuxも似たよ うなもの • GVL acquire/releaseの実態はただの pthread_mutex_{lock,unlock}
  5. 1.9.2のGVL design (cont.) 1.9.2のGVL design (cont.) •それだけではビジーループ時にまったくスレ ッドスイッチしなくなって悲しい •そこでタイマースレッド。10msに一回 「GVL

    を開放してください」フラグをONにしてた •それだけではビジーループ時にまったくスレ ッドスイッチしなくなって悲しい •そこでタイマースレッド。10msに一回 「GVL を開放してください」フラグをONにしてた
  6. 1.9.2のGVL design (cont.) 1.9.2のGVL design (cont.) if (th->interrupt_flag) { gvl_release();

    sched_yield(); gvl_acquire(); } if (th->interrupt_flag) { gvl_release(); sched_yield(); gvl_acquire(); } •フラグがONされると右の 擬似コードのような処理 を行っていた •フラグがONされると右の 擬似コードのような処理 を行っていた
  7. 動かないコード例 動かないコード例 •右は別のOSSで実際にトラ ブル(ハング)に発展したコ ードの抜粋 •なぜ? •thread1が常にlockを取っ てしまうのでhoge=1は実 行されない •右は別のOSSで実際にトラ

    ブル(ハング)に発展したコ ードの抜粋 •なぜ? •thread1が常にlockを取っ てしまうのでhoge=1は実 行されない while (true) { lock(); if (hoge) { unlock(); break; } unlock(); rep_nop(); } lock(); hoge = 1; unlock(); thread1 thread2
  8. if (th->interrupt_flag) { gvl_release(); sched_yield(); gvl_acquire(); } if (th->interrupt_flag) {

    gvl_release(); sched_yield(); gvl_acquire(); } •gvl_release()とgvl_acquire()の間にsched_yield()あ るじゃん。こいつは何をするものなんだぜ? •なぜsched_yield()は、ちゃんとyieldしてくれない のか ちょっと戻って ちょっと戻って
  9. 現代のス(ry 現代のス(ry cor e Thread Thread cor e Thread cor

    e Thread Thread Thread CPUの数だけ実行待ちキ ュー Thread
  10. ありがちなこと ありがちなこと cor e Thread Ruby Thread1 cor e Thread

    Ruby Thread2 おいら結局順番 まわってこなか った。ショボー ン thread listのheadに きたぞー
  11. ありがちなこと ありがちなこと cor e Thread Ruby Thread1 cor e Thread

    Ruby Thread2 POSIX「くくく、譲る、譲るとはいったがお前に譲る と約束した覚えはない!」
  12. CFS yield問題 CFS yield問題 •LinuxではRHEL5世代とRHEL6世代でスケジューラが違う •RHEL6世代のスケジューラ(CFS)はsched_yieldを無視する (RHEL5と同等動作にするknobはある) •なぜってJavaがクレイジーだから •Linux upstreamでは上記knobも消えてるので、結局1CPU

    でもなんらかの対策が必要 •LinuxではRHEL5世代とRHEL6世代でスケジューラが違う •RHEL6世代のスケジューラ(CFS)はsched_yieldを無視する (RHEL5と同等動作にするknobはある) •なぜってJavaがクレイジーだから •Linux upstreamでは上記knobも消えてるので、結局1CPU でもなんらかの対策が必要
  13. アイデア アイデア gvl_acquire mutex_lock(&lock->lock) cond_wait(&lock->wait) lock->acquired = 1; // ロックとったよと通知

    cond_signal(&lock->switch_cond) mutex_unlock(&lock->lock) mutex_lock(&lock->lock) lock->acquired = 0; cond_signal(&lock->wait_cond) // ロック取るまで寝る cond_wait(&lock->switch_cond) mutex_unlock(&lock->lock) gvl_release •ならば無理やりfairnessにしてやろう •同一スレッドが二回連続GVLを取れなくしてみる •ならば無理やりfairnessにしてやろう •同一スレッドが二回連続GVLを取れなくしてみる
  14. 結果 結果 1byteずつ書くスレッドと1byte ずつ読むスレッド。 小IO多発 = GVL switch多発 before: 1.26

    after: 2.49 1byteずつ書くスレッドと1byte ずつ読むスレッド。 小IO多発 = GVL switch多発 before: 1.26 after: 2.49 lmax = 100_000 r, w = IO.pipe [Thread.new{ lmax.times{ w.write('a') } p "w:exit" }, Thread.new{ lmax.times{ r.read(1) } p "r:exit" vm_thread_pipe
  15. 1.9.3のデザイン 1.9.3のデザイン •1.9.3ではIO時のGVL releaseと、タイマースレッド経由の GVL releaseを分けた •IO等からの自発的GVLは1.9.2と同じ。小IOではGVLが switchしないこともある •タイマースレッド経由、つまりスレッドが長時間走行し すぎと判定された場合のGVL

    releaseは強制switchあり。絶 対自分がもう一度GVLを取らないよう寝る •手抜きだけどだいたい動く •1.9.3ではIO時のGVL releaseと、タイマースレッド経由の GVL releaseを分けた •IO等からの自発的GVLは1.9.2と同じ。小IOではGVLが switchしないこともある •タイマースレッド経由、つまりスレッドが長時間走行し すぎと判定された場合のGVL releaseは強制switchあり。絶 対自分がもう一度GVLを取らないよう寝る •手抜きだけどだいたい動く
  16. 1.9.2: 3245.6秒 (~ 1H) 1.9.3: 4.4秒 1.9.2: 3245.6秒 (~ 1H)

    1.9.3: 4.4秒 vm_thread_mutex3 vm_thread_mutex3
  17. 今日おぼえて帰ってほしいこと 今日おぼえて帰ってほしいこと • 現代的なCPUはメモリアクセス速度が均一じゃない • GVLとイマドキのCPUは大変相性が悪い • みなさんが気持よくRubyが書けるように中の人は一生懸 命魔法をかけてます •

    現代的なCPUはメモリアクセス速度が均一じゃない • GVLとイマドキのCPUは大変相性が悪い • みなさんが気持よくRubyが書けるように中の人は一生懸 命魔法をかけてます