Ruby:同時/マルチスレッドタスクのCPU負荷の低下?

Programmierus

前文:truecryptコンテナを復元するプロジェクトに取り組んでいます。おそらくランダムな順序で300万を超える小さなファイルにカットされました。目標は、暗号化キーを含むコンテナーの先頭または末尾を見つけることです。

そのために、メインのマウントまたはバックアップヘッダーの復元を同時に試みる多くのtruecryptプロセスを開始する小さなrubyスクリプトを作成しました。truecryptとの相互作用は、生成されたPTYを介して発生します。

  PTY.spawn(@cmd) do |stdout, stdin, pid|
    @spawn = {stdout: stdout, stdin: stdin, pid: pid}

    if test_type == :forward
      process_truecrypt_forward
    else
      process_truecrypt_backward
    end

    stdin.puts
    pty_expect('Incorrect password')

    Process.kill('INT', pid)
    stdin.close
    stdout.close
    Process.wait(pid)
  end

これはすべて正常に機能し、テストコンテナの必要な部分を正常に見つけることができます。物事をスピードアップするために(そして私は300万個以上を処理する必要があります)、私は最初にRuby MRIマルチスレッドを使用し、問題について読んだ後、concurent-rubyに切り替えました

私の実装は非常に簡単です。

log 'Starting DB test'
concurrent_db = Concurrent::Array.new(@db)

futures = []

progress_bar = initialize_progress_bar('Running DB test', concurrent_db.size)

MAXIMUM_FUTURES.times do
  log "Started new future, total #{futures.size} futures"

  futures << Concurrent::Future.execute do
    my_piece = nil

    run = 1

    until concurrent_db.empty?
      my_piece = concurrent_db.slice!(0, SLICE_PER_FUTURE)
      break unless my_piece
      log "Run #{run}, sliced #{my_piece.size} pieces, #{concurrent_db.size} left"

      my_piece.each {|a| run_single_test(a)}
      progress_bar.progress += my_piece.size
      run += 1
    end

    log 'Future finished'
  end
end

74個のCPUコアを備えた大規模なAWSインスタンスをレンタルして、「これで高速に処理する」と考えたよりも。しかし、問題は、同時に起動するフューチャー/スレッドの数(つまり、20または1000)に関係なく、1秒あたり最大50チェックに達しないことです。

1000スレッドを起動すると、CPU負荷は20〜30分間だけ100%に保たれ、その後、15%に達するまで低下し、そのままになります。そのような実行内の典型的なCPU負荷のグラフディスクの負荷は問題ではありません。AmazonEBSストレージを使用して最大3MiB /秒に達しています。

何が足りないのですか?100%CPUを使用して、パフォーマンスを向上させることができないのはなぜですか?

マックス

マルチスレッドのメリットが見られない理由を正確に言うのは難しいです。しかし、これが私の推測です。

と呼ばれる実行に10秒かかる非常に集中的なRubyメソッドがあるとしましょうdo_workさらに悪いことに、このメソッドを100回実行する必要があります。1000秒待つのではなく、マルチスレッド化を試みることができます。これにより、CPUコア間で作業が分割され、ランタイムが半分になるか、4分の1になる可能性があります。

Array.new(100) { Thread.new { do_work } }.each(&:join)

しかし、いいえ、これはおそらくまだ1000秒で終了します。どうして?

グローバルVMロック

この例を考えてみましょう。

thread1 = Thread.new { class Foo; end; Foo.new }
thread2 = Thread.new { class Foo; end; Foo.new }

Rubyでクラスを作成すると、内部で多くのことが行われます。たとえば、実際のクラスオブジェクトを作成し、そのオブジェクトのポインタをグローバル定数に(ある順序で)割り当てる必要があります。thread1がそのグローバル定数を登録し、実際のクラスオブジェクトの作成の途中で、thread2が実行を開始すると、「ああ、Fooすでに存在します。先に進んで実行しましょう」と言いFoo.newます。クラスが完全に定義されていない場合はどうなりますか?または、thread1とthread2の両方が新しいクラスオブジェクトを作成し、両方がクラスをとして登録しようとした場合はFooどうなりますか?どちらが勝ちますか?作成され、現在登録されていないクラスオブジェクトはどうですか?

これに対する公式のRubyソリューションは単純です。実際にこのコードを並行して実行しないでください。代わりに、「グローバルVMロック」と呼ばれる単一の大規模なミューテックスがあり、Ruby VMの状態を変更するもの(クラスの作成など)を保護します。したがって、上記の2つのスレッドはさまざまな方法でインターリーブされる可能性がありますが、各VM操作は基本的にアトミックであるため、VMが無効な状態になることはありません。

これは私のラップトップで実行するのに約6秒かかります:

def do_work
  Array.new(100000000) { |i| i * i }
end

これには約18秒かかります明らかに

3.times { do_work }

ただし、GVLはスレッドが実際に並行して実行されるのを防ぐため、これにも約18がかかります。

Array.new(3) { Thread.new { do_work } }.each(&:join)

これも実行に6秒かかります

def do_work2
  sleep 6
end

しかし、これ実行に約6秒かかります。

Array.new(3) { Thread.new { do_work2 } }.each(&:join)

どうして?Rubyのソースコードを掘り下げるsleepと、最終的にC関数が呼び出さnative_sleepれ、そこに次のように表示されます。

GVL_UNLOCK_BEGIN(th);
{
    //...
}
GVL_UNLOCK_END(th);

Ruby開発者はsleep、これがVMの状態に影響を与えないことを知っているため、GVLのロックを明示的に解除して、GVLを並行して実行できるようにしました。GVLをロック/ロック解除するものと、GVLのパフォーマンス上の利点をいつ確認するかを正確に把握するのは難しい場合があります。

コードを修正する方法

私の推測では、コード内の何かがGVLにヒットしているため、スレッドの一部が並行して実行されている間(通常、サブプロセス/ PTYのものはすべて実行されます)、Ruby VM内でそれらの間で競合が発生し、一部がシリアル化されます。

真に並列なRubyコードを取得するための最善の策は、次のように単純化することです。

Array.new(x) { Thread.new { do_work } }

これdo_workは、サブプロセスの生成など、GVLのロックを確実に解除する単純なものである確信している場合ですTruecryptコードを小さなシェルスクリプトに移動してみてください。そうすれば、Rubyが実行された後、Rubyがコードと対話する必要がなくなります。

いくつかのサブプロセスを開始するだけの小さなベンチマークから始めて、それらをシリアルに実行する時間を比較することによって、それらが実際に並列に実行されていることを確認することをお勧めします。

この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。

侵害の場合は、連絡してください[email protected]

編集
0

コメントを追加

0

関連記事

分類Dev

私のマルチスレッドゲームは常に100%CPUです。CPU負荷を減らすためにスレッドアクティビティを管理するにはどうすればよいですか?

分類Dev

負荷テストスクリプトのスレッド化またはマルチプロセッシング

分類Dev

最大3つのスレッドに同時にアクセスできるJavaマルチスレッド方式

分類Dev

アイドル時のシステムからの高いCPU負荷

分類Dev

同じプロセスの2つのユーザーレベルのスレッドをマルチコアCPUで同時に実行できますか?

分類Dev

同じプロセスの2つのユーザーレベルのスレッドをマルチコアCPUで同時に実行できますか?

分類Dev

マルチスレッドを使用したリストへの同時データ挿入

分類Dev

マルチコアマシン-CPU負荷メトリック

分類Dev

マルチスレッド非同期/待機なしの同時実行

分類Dev

CompletableFutureマルチスレッド、シングルスレッド同時、またはその両方?

分類Dev

マルチスレッドおよび高負荷のシナリオでJavaファイルioを使用していますか?

分類Dev

マルチスレッドを介して複数のSQLクエリを同時に実行する

分類Dev

Windowsでのlocaltime_s()のマルチスレッドパフォーマンスの低下の回避策

分類Dev

Windowsでのlocaltime_s()のマルチスレッドパフォーマンスの低下の回避策

分類Dev

スレッドロック内のマルチスレッド

分類Dev

マルチスレッドサービスでの同時実行の問題の処理

分類Dev

続編の熱心な負荷でスタックレベルが深すぎる

分類Dev

Whileループ用のARMベースのボードの巨大なCPU負荷

分類Dev

WebWorkerスレッドを使用した高負荷同時実行テスト中のセグメンテーション違反

分類Dev

JavaシングルスレッドCPUの使用とマルチスレッドCPUの使用

分類Dev

インスタンス制御のクラスとマルチスレッド

分類Dev

負荷がかかった状態でのApacheCamelルートのパフォーマンスの低下

分類Dev

負荷分散スレッド要求の割合

分類Dev

ワイヤレスルーターの負荷分散?

分類Dev

マルチスレッド/同時実行セレンWindows

分類Dev

ExecutorServiceがマルチスレッドのパフォーマンスを低下させる

分類Dev

複数のCPU、マルチスレッドパフォーマンス

分類Dev

構造体のフィールドに対する負荷値命令と負荷アドレス命令の効率

分類Dev

複数のスレッドでのデータ挿入の簡単な負荷テストの設計

Related 関連記事

  1. 1

    私のマルチスレッドゲームは常に100%CPUです。CPU負荷を減らすためにスレッドアクティビティを管理するにはどうすればよいですか?

  2. 2

    負荷テストスクリプトのスレッド化またはマルチプロセッシング

  3. 3

    最大3つのスレッドに同時にアクセスできるJavaマルチスレッド方式

  4. 4

    アイドル時のシステムからの高いCPU負荷

  5. 5

    同じプロセスの2つのユーザーレベルのスレッドをマルチコアCPUで同時に実行できますか?

  6. 6

    同じプロセスの2つのユーザーレベルのスレッドをマルチコアCPUで同時に実行できますか?

  7. 7

    マルチスレッドを使用したリストへの同時データ挿入

  8. 8

    マルチコアマシン-CPU負荷メトリック

  9. 9

    マルチスレッド非同期/待機なしの同時実行

  10. 10

    CompletableFutureマルチスレッド、シングルスレッド同時、またはその両方?

  11. 11

    マルチスレッドおよび高負荷のシナリオでJavaファイルioを使用していますか?

  12. 12

    マルチスレッドを介して複数のSQLクエリを同時に実行する

  13. 13

    Windowsでのlocaltime_s()のマルチスレッドパフォーマンスの低下の回避策

  14. 14

    Windowsでのlocaltime_s()のマルチスレッドパフォーマンスの低下の回避策

  15. 15

    スレッドロック内のマルチスレッド

  16. 16

    マルチスレッドサービスでの同時実行の問題の処理

  17. 17

    続編の熱心な負荷でスタックレベルが深すぎる

  18. 18

    Whileループ用のARMベースのボードの巨大なCPU負荷

  19. 19

    WebWorkerスレッドを使用した高負荷同時実行テスト中のセグメンテーション違反

  20. 20

    JavaシングルスレッドCPUの使用とマルチスレッドCPUの使用

  21. 21

    インスタンス制御のクラスとマルチスレッド

  22. 22

    負荷がかかった状態でのApacheCamelルートのパフォーマンスの低下

  23. 23

    負荷分散スレッド要求の割合

  24. 24

    ワイヤレスルーターの負荷分散?

  25. 25

    マルチスレッド/同時実行セレンWindows

  26. 26

    ExecutorServiceがマルチスレッドのパフォーマンスを低下させる

  27. 27

    複数のCPU、マルチスレッドパフォーマンス

  28. 28

    構造体のフィールドに対する負荷値命令と負荷アドレス命令の効率

  29. 29

    複数のスレッドでのデータ挿入の簡単な負荷テストの設計

ホットタグ

アーカイブ