原文はこちら。
https://blogs.oracle.com/linuxkernel/accelerating-linux-boot-time
ここでは、3064個のCPUと32TBのメモリを搭載した最大規模のSPARCマシンのLinuxの再起動時間を示します(ほとんどの修正は汎用コードに含まれていますが、大規模な構成の他のアーキテクチャでも同様の改善が見込まれます)。
Note: 上記の結果に比べ、素のハードウェア上ではファームウェアの初期化は何倍も時間がかかります。このデータは仮想化I/Oを備えたLDomゲストで測定したものです。
https://blogs.oracle.com/linuxkernel/accelerating-linux-boot-time
このエントリはOracle LinuxカーネルチームのPasha Tatashinによるものです。
Linux Reboot
マシンが速やかに再起動することが重要な理由はたくさんあります。いくつか列挙してみましょう。- 稼働時間と可用性の向上
- 顧客体験の向上:ブート/リブートプロセスが速いとマシン全体が速くなったように感じる
- ダウンタイムが短くなれば、重要なパッチを迅速に適用してセキュリティを向上させることが可能
Linux Early Boot Improvements
今年初めから、Linuxの起動と再起動のパフォーマンスを向上させる方法を検討してきました。これまでのところ、私は初期起動時のみを見ていて、いくつかの問題を特定しました。それらのうちのいくつかはすでにメインラインのLinuxにプッシュされており、一部は統合されている途中です。ここでは、3064個のCPUと32TBのメモリを搭載した最大規模のSPARCマシンのLinuxの再起動時間を示します(ほとんどの修正は汎用コードに含まれていますが、大規模な構成の他のアーキテクチャでも同様の改善が見込まれます)。
Linux1 (before) | Linux2 (after) | |
---|---|---|
Shutdown | 4m20s | 4m20s |
Firmware | 1m13s | 1m13s |
Kernel | 14m32s | 0m58s |
Services | 8m44s | 4m13s |
Total | 28m49s | 10m44s |
Note: 上記の結果に比べ、素のハードウェア上ではファームウェアの初期化は何倍も時間がかかります。このデータは仮想化I/Oを備えたLDomゲストで測定したものです。
- Linux1は手を入れる前のメインラインカーネルです。
- Linux2には以下の変更を追加しています。
- add_node_ranges() が遅い
32TBのシステムでは、この関数は82秒を要しています。その理由は、このページが属するNUMAノードを決定するために1ページずつ辿るためです。この関数を最適化すると、1秒未満で完了するようになりました。この成果はすでにメインラインのLinuxに統合されています。 - より高速に起動するためにOpenFirmware CPUノードのキャッシュをスキップLinuxはOpenBootからすべてのCPUに関する情報をキャッシュします。そのため、3064個のCPUがあると75.56秒かかります。しかし、ハイパーバイザのマシン記述子にすべて含まれているため、sun4vのCPUノードに関する情報は実際には必要ありません。この修正を適用すると、ブート中にOpenBootからデータをキャッシュするのに1秒未満ですみます。この変更はまだメインラインに提出されていません。
- page構造体の並列ゼロ初期化
起動中にmemblockアロケータが1スレッドだけを使いpage構造体に割り当てられたメモリをゼロ初期化します。この修正では、page構造体の遅延初期化機能を使用して、page構造体のゼロ初期化を並列処理します。この修正により、32Tマシンでは約73秒起動時間を短縮できます。パッチはアップストリームのコミュニティに提出され、現在レビュー中です。 - page構造体の遅延初期化の有効化(詳細は後述)
Linuxには、ブート時にpage構造体の並列初期化オプションがあります。この修正により、起動時間が171秒短縮されます。現在のところ、SPARCではいくつかの依存作業(例えば、メモリのホットプラグ)が完了するまで、このオプションは無効になっています。 - 低速ハッシュテーブルの初期化 inode_cachep、dentry_hashtableといった、alloc_large_system_hash()を使って割り当てられたハッシュテーブルは、メモリサイズが増加すると大きくなる可能性があります。これらの多くは単純にmemset()でゼロ埋めすることで初期化できます。また、一部は現在の増大速度ほど速く成長しません。今回、この問題に取り組むために新しい適応型スケーリングアルゴリズム(adaptive scaling algorithm)を追加しました。この修正により、起動時間が58秒短縮します。パッチはメインラインにあります。
- 初期ブートタイムスタンプ
この作業ではパフォーマンスは向上しませんが、上記で見つかった多くの問題を検出できます。
Complete Deferred Page Initialization
システム内のすべての物理ページフレーム(physical page frame)には、関連するpage構造体があり、これを使ってページのステータスを記述します。起動時に、カーネルはすべてのpage構造体にメモリを割り当て、初期化します。従来、初期化はスレーブCPUがオンライン化される前に起動の早い段階で行われるため、大規模なマシンではかなりの時間がかかります。この問題は、Mel GormanがParallel struct page initialisation機能を追加した2015年に部分的に解決されました。Parallel struct page initialisation v4Melのプロジェクトでは、起動時にカーネルはpageの小さな部分集合のみを初期化し、残りを他のCPUが利用可能なときに初期化していました。ただし、page構造体の初期化時間をさらに短縮するには、次の2つの方法があります。
http://linux-kernel.2935.n7.nabble.com/PATCH-0-13-Parallel-struct-page-initialisation-v4-tt1094819.html
- 初期化を並列実行していたとしても、page構造体が格納されるメモリのゼロ埋めは(これまで同様)1個のスレッドだけを使って実施します。これはLinuxの初期ブートメモリアロケータ(memblock)が割り当て対象のメモリを常にゼロ埋めするためです。この問題を解決するためには、起動時のゼロ埋めを他のCPUが使用可能になった時点で実施するようにすることが必要です。しかし、カーネルのある部分がpage構造体のメモリを初期化またはゼロ埋めすることを期待しているため、複雑な課題に対応する必要があります。そのため、メモリがゼロ埋めされる前に、page構造体にアクセスさせないようにする必要があります。このプロジェクトは現在レビュー中であり、1TBのメモリを搭載するOracle X5-8では起動が22秒早くなります。
complete deferred page initialization
時間短縮は搭載メモリサイズに比例し、X5-8は最大6TBまで搭載可能なので、フル構成のX5-8ではブート時間は約132秒短縮できます。
https://lwn.net/Articles/734374/Oracle Server X5-8—Features
このプロジェクトでは、既存のブート時のバグを修正し、今後新しいバグが入り込まないようにするため、memblockアロケータにデバッグチェックを追加しています。
https://www.oracle.com/servers/x86/x5-8/features.html - もう一つの、page構造体の初期化を改善するための最適化は、ノードごとに複数のスレッドを使用して構造体ページを初期化することです。1つのメモリノードには、数ギガバイトのメモリを含めることができます。一例として、6TBのメモリを備えたX5-8はノードあたり0.75TBのメモリを持ち、この値は新しいシステムで増加し続けます。新しいXeonプロセッサはノードあたり最大1.5TBをサポートし、AMDのEPYCプロセッサはノードあたり最大2TBをサポートします。
Intel® Xeon® Platinum 8180M Processor 38.5M Cache, 2.50 GHz
Daniel Jordanは、現在レビュー中の彼のktaskの作業にこの最適化を含めました。
Product Specifications
https://ark.intel.com/products/120498/Intel-Xeon-Platinum-8180M-Processor-38_5M-Cache-2_50-GHz
EPYC™ 7000 Series
http://www.amd.com/en/products/epyc-7000-series
EPYC™ 7000 Series Data Sheet
http://www.amd.com/system/files/2017-06/AMD-EPYC-Data-Sheet.pdfktask: multithread cpu-intensive kernel work
ktaskについては以下のエントリをご覧ください。
https://lwn.net/Articles/731925/ktask: A Generic Framework for Parallelizing CPU-Intensive Work
https://blogs.oracle.com/linuxkernel/ktask%3A-a-generic-framework-for-parallelizing-cpu-intensive-work
https://orablogs-jp.blogspot.jp/2017/11/ktask-generic-framework-for.html
Early Boot Time Stamps
Linuxには、コンソールに表示される各行の先頭にタイムスタンプを入れるのに便利な機能があります。CONFIG_PRINTK_TIMEはデフォルトで多くのディストリビューションで有効になっています。CONFIG_PRINTK_TIME: Show timing information on printksこれを使えば、dmesg -dコマンド(dmesgの新しいバージョンでのみ-dフラグを利用可)を使用して、起動時および実行時のregressionをすばやく検出できます。
https://cateee.net/lkddb/web-lkddb/PRINTK_TIME.html
dmesg - print or control the kernel ring bufferしかし、タイムスタンプはブート時の最終段階で利用できるため、ブートにかなりの時間を費やすと、その時間が報告されません。実際、このエントリで説明しているすべての改善は、printkタイムスタンプが利用可能になる前に発生します。したがって、起動時にタイムスタンプを利用できるようにするために、SPARC用とx86用の2つのプロジェクトで作業が行われました。 SPARCはすでにメインライン上にありますが、x86は現在レビュー中です。
http://man7.org/linux/man-pages/man1/dmesg.1.html
[v3 0/8] Early boot timestamp
https://www.spinics.net/lists/sparclinux/msg18063.html
[PATCH v6 0/4] Early boot time stamps for x86
https://lkml.org/lkml/2017/8/30/574