web-dev-qa-db-ja.com

新しいLinuxカーネルではコンテキストの切り替えがはるかに遅い

サーバーのOSをUbuntu 10.04 LTSからUbuntu 12.04 LTSにアップグレードすることを検討しています。残念ながら、実行可能になったスレッドを実行する遅延は、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〜100 usのオーダーまで増加しました。 2つのホスト間のハードウェアの違いを除外しました。これらは同一のハードウェアで実行されます(ハイパースレッディング、スピードステップ、すべてのC状態がオフの3.6 GHzで実行されるデュアルソケットX5687 {Westmere-EP}プロセッサ)。テストアプリは、同じソケットの独立した物理コアでスレッドを実行するようにスレッドのアフィニティを変更します(つまり、最初のスレッドはCore 0で実行され、2番目のスレッドはCore 1で実行されます)コア間またはソケット間のバウンス/通信。

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

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

更新:ホストとLinuxビルドでテストを実行したい場合は、 コードをPastebinに投稿 forあなたの閲覧。コンパイル:

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:カーネルパラメーターの詳細な検索、カーネルの変更に関する投稿、個人的な調査の後、問題が何であるかを把握し、ソリューションを次のように投稿しました。この質問に対する答え。

96

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

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

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は不要です。ただし、ドキュメントには、intel_idleドライバーのmax_cstateをゼロに設定すると、OSがacpi_idleドライバーに戻されることが記載されています。したがって、念のため2番目のブートオプションを追加しました。

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

最後に、3つのパラメータの最後のidle=pollは、本当のパワーを独り占めしています。 C1/C1Eを無効にします。これにより、消費電力が大幅に増えますが、レイテンシの最後の残りのビットが削除されるので、本当に必要な場合にのみ使用してください。 C1 *レイテンシはそれほど大きくないため、ほとんどの場合これは過剰になります。元の質問で説明したハードウェアで実行されているテストアプリケーションを使用すると、遅延は9 usから3 usになりました。これは、レイテンシーの影響を受けやすいアプリケーション(金融取引、高精度のテレメトリ/追跡、高頻度のデータ取得など)にとっては確かに大幅な削減ですが、大部分の人が被る電力に見合う価値はないかもしれませんデスクトップアプリ。確実に知る唯一の方法は、ハードウェアの消費電力/熱の実際の増加に対するパフォーマンスのアプリケーションの改善をプロファイルし、トレードオフを比較検討することです。

更新:

さまざまなidle=*パラメータを使用した追加テストの後、ハードウェアでサポートされている場合はidlemwaitに設定する方がはるかに良いアイデアであることがわかりました。 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を使用すると、Turbo Boostの開始(CPUがTDP [Thermal Design Power]を下回るようにすることによる)およびハイパースレッディング(MWAITが理想的なメカニズムです)物理コア全体を消費せず、同時にCの高い状態を回避します)。ただし、これはテストでまだ証明されていないので、引き続き行います。

更新2:

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

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

そして

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

93

おそらく遅くなったのは、条件変数の構成要素である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.
$ 
8
amdn

また、c-stateとは別のpstateドライバーが原因で、より最近のプロセスやLinuxカーネルでプロセッサーがクリックダウンすることもあります。さらに、これを無効にするには、次のカーネルパラメーターを使用します。

intel_pstate=disable

0
Kyle Brandt