6
5

More than 3 years have passed since last update.

parallel_tests が並列数を決定する仕組み

Last updated at Posted at 2020-07-27

Rubyのテストは、素のままですと直列実行なので、テストケース数が増えると実行時間も増えていきます。

CIの実行環境は、マルチプロセッサを選択できることがあるため、テストも並列実行できると、時間短縮を狙うことができます。

Rubyでは parallel_tests というgem越しに、めぼしいテストフレームワークを並列実行することができます。

ところで bin/parallel_test -n 4 のように並列数を指定すると4並列で動かしてくれますが、指定しなかった場合は、どうやら実行環境のプロセッサの数を、どこかから取ってきて動いているようです。README.md を眺めてもよくわからなかったので、ソースを読んでみました。

手元に持ってくる

この記事を書いてる時点の最新版 v3.1.0 を読んでみます。

ghq get git@github.com:grosser/parallel_tests.git
cd parallel_tests
git checkout -b tag3.1.0 refs/tags/v3.1.0

rake 越しの実行の場合

このように実行することで、テスト用に create database と、RSpecを実行してくれます。そのときの並列数は、実行環境のプロセッサの数らしいです。4コアなら4並列です。

bundle exec rake parallel:setup
bundle exec rake parallel:spec 

[] に数字を入れると、実行環境のプロセッサ数のことは忘れて、指定した並列数で実行してくれます。

bundle exec rake parallel:setup[6]
bundle exec rake parallel:spec[6]

これが parallel_tests のソースコードでは、 :count というパラメータを取るRakeタスクとして定義されています。

parallel:setup はさらに run_in_parallel に渡して、 bin/parallel_test を呼ぶコマンドライン文字列を組み立てています。

このとき :count を渡していれば、 -n を付けるようにしています。しかし -n を付けなかった場合に、どのように決まるかは不明です。

parallel:spec も同様で、なんなら :count を渡していようがいまいが、 -n を付けています。 :count を渡さなかった場合は -n "" となりそうですし、 -n を付けなかった場合に、どのように決まるかは不明です。

Rakeタスクは、パラメータとして並列数を受けとっていればその数を bin/parallel_test に渡し、受けとっていなければ bin/parallel_test に判断させているようです。

bin/parallel_test

CLI に丸投げしていますね

実体はこれで、並列数に関心を集中して読むと、

コマンドライン引数が最も強く、環境変数 $PARALLEL_TEST_PROCESSORSParallel.processor_count の順に、並列数の取得元としているようです。

-n "" と渡ってきても、以下でイイかんじに対処できてそうに読めます。

Parallel.processor_count

Parallel.processor_count は、 parallel_tests を漁っても存在しておらず、 parallel に定義されているようです。

gem parallel を漁ると、環境変数 $PARALLEL_TEST_PROCESSORSEtc.nprocessors の順に、並列数の取得元としているようです。

Etc.nprocessors

Etc.nprocessorsetc というライブラリに定義されてそう。

Etc.nprocessors でググると、Rubyの標準ライブラリのひとつとして実装されていることがわかります。

2.7.0 で読んでみます。

ghq get git@github.com:ruby/ruby.git
cd ruby
git checkout -b tag2_7_0 refs/tags/v2_7_0

どこに実装があるのか何もわからねえな...とりあえず ripgrep で漁ってみます。

rg nprocessors
doc/ChangeLog-2.2.0
2564:   * ext/etc/etc.c (etc_nprocessors_affin): maximum "n" should be 16384.
2568:   * ext/etc/etc.c (etc_nprocessors_affin): minor spell fix.
2572:   * ext/etc/etc.c (etc_nprocessors_affin): optimize memory usage a
2924:   * ext/etc/etc.c (etc_nprocessors_affin): Test CPU_ALLOC availability.
2929:   * ext/etc/etc.c (etc_nprocessors_affinity): use sched_getaffinity
2932:   * ext/etc/etc.c (etc_nprocessors): use etc_nprocessors_affinity if
2935:     [Feature #10267] etc-nprocessors-kosaki2.patch
3539:   * ext/etc/etc.c (etc_nprocessors): Windows support.
3544:   * ext/etc/etc.c (etc_nprocessors): New method.

test/etc/test_etc.rb
167:  def test_nprocessors
168:    n = Etc.nprocessors

ext/etc/etc.c
927:etc_nprocessors_affin(void)
992: *   p Etc.nprocessors #=> 4
1000: *   linux$ taskset 0x3 ./ruby -retc -e "p Etc.nprocessors"  #=> 2
1004:etc_nprocessors(VALUE obj)
1013:    ncpus = etc_nprocessors_affin();
1033:#define etc_nprocessors rb_f_notimplement
1092:    rb_define_module_function(mEtc, "nprocessors", etc_nprocessors, 0);

doc/NEWS-2.2.0
162:    * Etc.nprocessors

lib/bundler/installer.rb
223:      Etc.nprocessors

spec/ruby/library/etc/nprocessors_spec.rb
4:describe "Etc.nprocessors" do
6:    Etc.nprocessors.should be_kind_of(Integer)
7:    Etc.nprocessors.should >= 1

spec/mspec/lib/mspec/utils/script.rb
256:    [Etc.nprocessors, max].min

https://github.com/ruby/ruby/tree/v2_7_0/ext/etc に転がっているものを眺めてみます。

parallel_test は Linux な CodeBuild で動かしているので、 Linux に関心を絞ります。

sched_getaffinity

これは Linux のシステムコールで、C関数として利用できるよう定義されています。テキトーなLinuxディストリビューションで man sched_getaffinity するとカーネルのマニュアルが出てきます。

たいへんありがたいことに解説記事があります。

まとめ

  • parallel_test は、単独では実行環境のCPU数を取得していない。 Parallel.processor_count から取得している
  • Parallel.processor_count は Ruby 標準ライブラリ etc の Etc.nprocessors から取得している
  • Etc.nprocessors は、Linux ならシステムコール sched_getaffinity() が使えなければ sysconf(_SC_NPROCESSORS_ONLN) から取得している
6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5