101

サーバー上のOSをUbuntu10.04LTSからUbuntu12.04LTSにアップグレードすることを検討しています。残念ながら、実行可能になったスレッドを実行するための待ち時間は、2.6カーネルから3.2カーネルに大幅に増加したようです。実際、私たちが得ているレイテンシーの数値は信じがたいものです。

テストについて具体的に説明します。2つのスレッドを実行するプログラムがあります。最初のスレッドは現在の時刻(RDTSCを使用してティック単位)を取得し、1秒に1回条件変数を通知します。2番目のスレッドは、条件変数を待機し、シグナルが送信されるとウェイクアップします。次に、現在の時刻を取得します(RDTSCを使用してティック単位で)。2番目のスレッドの時間と最初のスレッドの時間の差が計算され、コンソールに表示されます。この後、2番目のスレッドは条件変数をもう一度待機します。約2秒経過すると、最初のスレッドによって再度通知されます。

つまり、一言で言えば、結果として1秒に1回、条件可変レイテンシー測定を介してスレッド間通信を取得します。

カーネル2.6.32では、このレイテンシーは2.8〜3.5 usのオーダーであり、これは妥当です。カーネル3.2.0では、このレイテンシーは40〜100usのオーダーに増加しました。2つのホスト間のハードウェアの違いを除外しました。これらは同一のハードウェアで実行されます(ハイパースレッディング、スピードステップ、およびすべてのC状態がオフになっている3.6 GHzで実行されるデュアルソケットX5687{Westmere-EP}プロセッサ)。テストアプリは、スレッドのアフィニティを変更して、同じソケットの独立した物理コアで実行します(つまり、最初のスレッドはコア0で実行され、2番目のスレッドはコア1で実行されます)。したがって、スレッドのバウンスはありません。コアまたはソケット間のバウンス/通信。

2つのホストの唯一の違いは、一方がカーネル2.6.32-28(高速コンテキストスイッチボックス)でUbuntu 10.04 LTSを実行し、もう一方がカーネル3.2.0-23(低速コンテキスト)で最新のUbuntu12.04LTSを実行していることです。スイッチボックス)。すべてのBIOS設定とハードウェアは同じです。

スレッドの実行がスケジュールされるのにかかる時間のこのばかげた速度低下を説明できるカーネルの変更はありますか?

更新: ホストとLinuxビルドでテストを実行したい場合は、コードをpastebinに投稿して閲覧してください。コンパイル:

g++ -O3 -o test_latency test_latency.cpp -lpthread

一緒に実行します(少なくともデュアルコアボックスがあると仮定します):

./test_latency 0 1 # Thread 1 on Core 0 and Thread 2 on Core 1

更新2:カーネルのパラメーター、カーネルの変更に関する投稿、および個人的な調査を何度も検索した後、問題が何であるかを理解し、この質問に対する回答として解決策を投稿しました。

4

3 に答える 3

96

最近のカーネルのスレッド ウェイクアップ パフォーマンスの問題に対する解決策は、古いカーネルで使用されていたintel_idleドライバーから cpuidle ドライバーに切り替えることです。acpi_idle残念なことに、intel_idleドライバーは C ステートに対するユーザーの BIOS 構成を無視し、独自の調子で踊ってしまいます。つまり、PC (またはサーバー) の BIOS ですべての C 状態を完全に無効にしても、このドライバーは、すべてのコアを消費する合成ベンチマーク (たとえば、ストレス) が走っています。ほとんどの互換性のあるハードウェアで素晴らしい Google i7z ツールを使用して、プロセッサ周波数に関連する他の有用な情報と共に C 状態遷移を監視できます。

セットアップで現在アクティブな cpuidle ドライバーを確認するには、次のように のセクションでcurrent_driverファイルをcat します。cpuidle/sys/devices/system/cpu

cat /sys/devices/system/cpu/cpuidle/current_driver

最新の Linux OS でコンテキスト スイッチのレイテンシを可能な限り低くしたい場合は、次のカーネル ブート パラメーターを追加して、これらの省電力機能をすべて無効にします。

Ubuntu 12.04 では、これらを のGRUB_CMDLINE_LINUX_DEFAULTエントリに追加してから/etc/default/grub実行することでこれを行うことができますupdate-grub。追加する起動パラメータは次のとおりです。

intel_idle.max_cstate=0 processor.max_cstate=0 idle=poll

以下は、3 つの起動オプションが何をするかについての詳細です。

ゼロに設定intel_idle.max_cstateすると、cpuidle ドライバーがacpi_idle(少なくともオプションのドキュメントに従って) に戻るか、完全に無効になります。私のボックスでは完全に無効になっています (つまり、current_driverファイルを表示/sys/devices/system/cpu/cpuidleすると の出力が生成されますnone)。この場合、2 番目のブート オプションprocessor.max_cstate=0は不要です。ただし、ドキュメントには、ドライバーの max_cstate をゼロに設定するとintel_idle、OS がドライバーに戻されると記載されていacpi_idleます。したがって、念のため、2 番目のブート オプションを入れます。

このprocessor.max_cstateオプションは、acpi_idleドライバーの最大 C 状態をゼロに設定し、うまくいけばそれも無効にします。intel_idle.max_cstate=0利用可能なすべてのハードウェアで cpuidle ドライバーを完全にノックアウトするため、これをテストできるシステムがありません。ただし、最初の起動オプションだけでインストールが元に戻った場合intel_idleは、この回答を更新できるように、2 番目のオプションがコメントに記載されていることを行ったかどうacpi_idleかをお知らせください。processor.max_cstate

最後に、3 つのパラメーターの最後の、idle=poll本当のパワーホッグです。C1/C1E を無効にします。これにより、消費電力が大幅に増加しますが、レイテンシの最後の残りのビットが削除されるため、本当に必要な場合にのみ使用してください。ほとんどの場合、C1* レイテンシはそれほど大きくないため、これはやり過ぎです。最初の質問で説明したハードウェアでテスト アプリケーションを実行すると、レイテンシが 9 us から 3 us になりました。これは確かに、レイテンシーの影響を受けやすいアプリケーション (金融取引、高精度のテレメトリー/トラッキング、高周波データの取得など) にとっては大幅な削減ですが、大部分のアプリケーションで発生する電力ヒットに見合う価値はないかもしれません。デスクトップ アプリ。確実に知る唯一の方法は、アプリケーションのパフォーマンスの向上とパフォーマンスの向上をプロファイリングすることです。

アップデート:

idle=*さまざまなパラメーターを使用して追加のテストを行った結果、ハードウェアでサポートされている場合に設定idleするmwait方がはるかに優れていることがわかりました。命令を使用するMWAIT/MONITORと、スレッドのウェイクアップ時間に顕著な遅延が追加されることなく、CPU が C1E に入ることができるようです。を使用すると、 idle=mwait( と比較して) CPU 温度がidle=poll低くなり、消費電力が少なくなり、ポーリング アイドル ループの優れた低遅延が維持されます。したがって、これらの調査結果に基づいて、CPU スレッドのウェイクアップ レイテンシを低くするための更新された推奨ブート パラメーターのセットは次のとおりです。

intel_idle.max_cstate=0 processor.max_cstate=0 idle=mwait

idle=mwaitの代わりに を使用するidle=pollと、ターボ ブースト (CPU を TDP [熱設計電力] 未満に保つのを助けることによって) とハイパースレッディング (物理コア全体を同時に消費しないための理想的なメカニズムである MWAIT) の開始にも役立つ場合があります。より高い C 状態を回避する時間)。ただし、これはまだテストで証明されていません。

更新 2:

mwaitアイドル オプションは、新しい 3.x カーネルから削除されました (更新のユーザー ck_ に感謝します)。これにより、次の 2 つのオプションが残ります。

idle=halt- と同様に動作するはずmwaitですが、お使いのハードウェアがそうであることを確認するためにテストしてください。このHLT命令はMWAIT、状態ヒント 0 を使用した場合とほぼ同じです。問題は、HLT 状態から抜け出すには割り込みが必要ですが、MWAIT 状態から抜け出すにはメモリ書き込み (または割り込み) を使用できるという事実にあります。Linux カーネルがアイドル ループで何を使用するかによっては、MWAIT がより効率的になる可能性があります。だから、私が言ったように、テスト/プロファイルして、それがあなたのレイテンシのニーズを満たしているかどうかを確認してください...

idle=poll- 電力と熱を犠牲にして、最高のパフォーマンス オプション。

于 2012-08-24T22:10:54.317 に答える
8

おそらく、速度が低下したのは、条件変数の構成要素である futex です。これはいくつかの光を当てます:

strace -r ./test_latency 0 1 &> test_latency_strace & sleep 8 && killall test_latency

それから

for i in futex nanosleep rt_sig;do echo $i;grep $i test_latency_strace | sort -rn;done

興味深いシステムコールにかかったマイクロ秒が時間でソートされて表示されます。

カーネル 2.6.32 の場合

$ for i in futex nanosleep rt_sig;do echo $i;grep $i test_latency_strace | sort -rn;done
futex
 1.000140 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000129 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000124 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000119 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000106 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000103 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000102 futex(0x601ac4, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601ac0, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 0.000125 futex(0x7f98ce4c0b88, FUTEX_WAKE_PRIVATE, 2147483647) = 0
 0.000042 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000038 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000037 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000030 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000029 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 0
 0.000028 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000027 futex(0x601b00, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000018 futex(0x7fff82f0ec3c, FUTEX_WAKE_PRIVATE, 1) = 0
nanosleep
 0.000027 nanosleep({1, 0}, {1, 0}) = 0
 0.000019 nanosleep({1, 0}, {1, 0}) = 0
 0.000019 nanosleep({1, 0}, {1, 0}) = 0
 0.000018 nanosleep({1, 0}, {1, 0}) = 0
 0.000018 nanosleep({1, 0}, {1, 0}) = 0
 0.000018 nanosleep({1, 0}, {1, 0}) = 0
 0.000018 nanosleep({1, 0}, 0x7fff82f0eb40) = ? ERESTART_RESTARTBLOCK (To be restarted)
 0.000017 nanosleep({1, 0}, {1, 0}) = 0
rt_sig
 0.000045 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000040 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000038 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000034 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000033 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000032 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000032 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000031 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000031 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000028 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000028 rt_sigaction(SIGRT_1, {0x37f8c052b0, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x37f8c0e4c0}, NULL, 8) = 0
 0.000027 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000027 rt_sigaction(SIGRTMIN, {0x37f8c05370, [], SA_RESTORER|SA_SIGINFO, 0x37f8c0e4c0}, NULL, 8) = 0
 0.000027 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000025 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000025 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000023 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000023 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000022 rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
 0.000022 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000021 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000021 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000021 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000021 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000021 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000019 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0

カーネル 3.1.9 の場合

$ for i in futex nanosleep rt_sig;do echo $i;grep $i test_latency_strace | sort -rn;done
futex
 1.000129 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000126 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000122 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000115 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000114 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000112 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 1.000109 futex(0x601764, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x601760, {FUTEX_OP_SET, 0, FUTEX_OP_CMP_GT, 1}) = 1
 0.000139 futex(0x3f8b8f2fb0, FUTEX_WAKE_PRIVATE, 2147483647) = 0
 0.000043 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000041 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000037 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000036 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000034 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
 0.000034 futex(0x601720, FUTEX_WAKE_PRIVATE, 1) = 1
nanosleep
 0.000025 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000022 nanosleep({1, 0}, {0, 3925413}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
 0.000021 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
 0.000017 nanosleep({1, 0}, 0x7fff70091d00) = 0
rt_sig
 0.000045 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000044 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000043 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000040 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000038 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000037 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000036 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000036 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000035 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000035 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000034 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000031 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000027 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000027 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000027 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000027 rt_sigaction(SIGRT_1, {0x3f892067b0, [], SA_RESTORER|SA_RESTART|SA_SIGINFO, 0x3f8920f500}, NULL, 8) = 0
 0.000026 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000026 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000025 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000024 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000023 rt_sigprocmask(SIG_UNBLOCK, [RTMIN RT_1], NULL, 8) = 0
 0.000023 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
 0.000022 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
 0.000021 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
 0.000019 rt_sigaction(SIGRTMIN, {0x3f89206720, [], SA_RESTORER|SA_SIGINFO, 0x3f8920f500}, NULL, 8) = 0

比較する「ピンポン」パフォーマンス テストを含むこの5 年前のバグ レポートを見つけました。

  1. シングルスレッド libpthread ミューテックス
  2. libpthread 条件変数
  3. 昔ながらの Unix シグナル

追加しなければなりませんでした

#include <stdint.h>

コンパイルするために、このコマンドで行いました

g++ -O3 -o condvar-perf condvar-perf.cpp -lpthread -lrt

カーネル 2.6.32 の場合

$ ./condvar-perf 1000000
NPTL
mutex                 elapsed:    29085 us; per iteration:   29 ns / 9.4e-05 context switches.
c.v. ping-pong test   elapsed:  4771993 us; per iteration: 4771 ns / 4.03 context switches.
signal ping-pong test elapsed:  8685423 us; per iteration: 8685 ns / 4.05 context switches.

カーネル 3.1.9 の場合

$ ./condvar-perf 1000000
NPTL
mutex                 elapsed:    26811 us; per iteration:   26 ns / 8e-06 context switches.
c.v. ping-pong test   elapsed: 10930794 us; per iteration: 10930 ns / 4.01 context switches.
signal ping-pong test elapsed: 10949670 us; per iteration: 10949 ns / 4.01 context switches.

カーネル 2.6.32 と 3.1.9 の間で、コンテキスト スイッチは確かに遅くなったと結論付けていますが、カーネル 3.2 で観察されたほどではありません。これはまだあなたの質問に答えていないことに気づきました。掘り続けます。

編集: プロセス (両方のスレッド) のリアルタイム優先度を変更すると、3.1.9 のパフォーマンスが 2.6.32 と一致するように改善されることがわかりました。ただし、2.6.32 に同じ優先度を設定すると速度が低下します... もう少し詳しく調べてみます。

ここに私の結果があります:

カーネル 2.6.32 の場合

$ ./condvar-perf 1000000
NPTL
mutex                 elapsed:    29629 us; per iteration:   29 ns / 0.000418 context switches.
c.v. ping-pong test   elapsed:  6225637 us; per iteration: 6225 ns / 4.1 context switches.
signal ping-pong test elapsed:  5602248 us; per iteration: 5602 ns / 4.09 context switches.
$ chrt -f 1 ./condvar-perf 1000000
NPTL
mutex                 elapsed:    29049 us; per iteration:   29 ns / 0.000407 context switches.
c.v. ping-pong test   elapsed: 16131360 us; per iteration: 16131 ns / 4.29 context switches.
signal ping-pong test elapsed: 11817819 us; per iteration: 11817 ns / 4.16 context switches.
$ 

カーネル 3.1.9 の場合

$ ./condvar-perf 1000000
NPTL
mutex                 elapsed:    26830 us; per iteration:   26 ns / 5.7e-05 context switches.
c.v. ping-pong test   elapsed: 12812788 us; per iteration: 12812 ns / 4.01 context switches.
signal ping-pong test elapsed: 13126865 us; per iteration: 13126 ns / 4.01 context switches.
$ chrt -f 1 ./condvar-perf 1000000
NPTL
mutex                 elapsed:    27025 us; per iteration:   27 ns / 3.7e-05 context switches.
c.v. ping-pong test   elapsed:  5099885 us; per iteration: 5099 ns / 4 context switches.
signal ping-pong test elapsed:  5508227 us; per iteration: 5508 ns / 4 context switches.
$ 
于 2012-08-24T16:43:25.817 に答える