詳解システム・パフォーマンス 3章「オペレーティングシステム」輪読メモ

詳解システム・パフォーマンス 第2章「メソドロジ」メモ - ゆううきメモ の続き。今回は第3章「オペレーティングシステム

システムパフォーマンス分析では、オペレーティングシステムとそのカーネルについての理解は必要不可欠だ。システムコールがどのように実行されるか、CPU がどのようにスレッドをスケ ジューリングするか、限られたメモリがパフォーマンスにどのような影響を及ぼすか、ファイルシステムは I/O をどのように処理するかなどのシステムのふるまいについて、あなたは頻繁に仮説を立て、それをテストすることになる。これらのふるまいを理解するためには、オペレーティングシステムとカーネルの知識を使わなければならない。

Brendan Gregg,西脇靖紘,長尾高弘「詳解システム・パフォーマンス」, オライリージャパン p.85

議論

章の内容をベースに議論したことは以下の9点である。議論した内容なので、事実でない可能性があることに注意。

  • カーネル空間とユーザー空間の違い
  • プロセスとスレッドの違い
  • selectとepollの違い
  • システムライブラリとはなにか
  • mallocは何をやっているのか
  • forkのCoWはなにをやっているのか
  • forkとcloneの違い
  • goroutineはスレッドか
  • キャッシング層が多すぎる

カーネル空間とユーザー空間の違い

カーネル空間といっても、プロセスとは独立したまとまった空間があるわけではおそらくない。 (図3-8プロセス環境を見ながら) 1つのプロセスの中にカーネル空間とユーザーアドレス空間がある。 カーネル空間では、メタデータとして、PIDなどをもつ。カーネルのコードでは、task_struct構造体として表現される。 (http://lxr.free-electrons.com/source/include/linux/sched.h#L1511) 他には、ファイルディスクリプタカーネル空間に確保される。ユーザー空間にはディスクリプタIDのみを確保する。 ユーザー空間は、実行ファイル、(共有)ライブラリ、ヒープ、スレッドのスタックなどをもつ。 softirqdみたいなカーネルスレッドだと、カーネル空間しかなさそう。

select/pollとepollの違い

select/poll はカーネル空間上のファイルディスクリプタをループで舐めるのでO(n)となり、監視するディスクリプタ数が多いと遅い。 一方で、epollはカーネル側でディスクリプタの状態をもつので、状態変化したものだけユーザー空間に返すことができる (エッジトリガ通知)。

詳細は、「Linuxプログラミングインタフェース」 63章 高度なI/Oモデル を参照。

システムライブラリとはなにか

glibcとか。glibcには例えば、mallocとかがある。mallocシステムコールではなく、ライブラリ関数。

MySQLで、jemallocを使うとスレッド数に対するスケーラビリティが向上する例がある。最近、社内でハマった。 MySQL performance: Impact of memory allocators (Part 2)

プロセスとスレッドの違い

カーネルはタスク (プロセス or スレッド or カーネルスレッド) という単位で、実行スケジューリングする。つまり、プロセスとスレッドは同等に扱われる。 では、プロセスとスレッドはカーネルコンテキストにおいてなにが異なるのか。 前述のように、タスクはtask_struct構造体で表現される。スレッドの場合は、あるスレッドを表現するtask_struct構造体の一部のメンバー変数が、スレッドの生成元であるプロセスと同じポインタを指しているだけではないか。この共有される一部のメンバー変数には、ヒープ領域などが含まれるはず?

mallocは何をやっているのか

ヒープメモリから要求するサイズの空き領域を探索して返す。(このときはシステムコールを実行しない?) 要求するサイズの空き領域がなければ、brk()により、ヒープメモリサイズを拡張する。

ヒープメモリの最初のアロケートはいつ行われる?forkされたときに親プロセスからコピーされるはず。原祖であるinitプロセスではどうしてるのか。

forkのCoW (Copy On Write) は何をやっているのか

fork直後に、新規の仮想メモリアドレス領域を子プロセスの領域として確保される。 しかし、子プロセスの仮想メモリアドレスのマッピング先は、親プロセスの物理メモリアドレスである。 というのがCoWの仕組みのはず?

ちなみに、vforkは、仮想メモリやページテーブルのコピーすら作らない。

forkとcloneの違い

forkは子プロセスを生成し、cloneはスレッドを生成するために使われる。cloneは関数を渡すインタフェース。cloneはスタックを別途割り当てる。cloneはflagsにより、親子で共有する属性を指定できる。 スレッドと一口に言っても、共有している属性が場合によって異なることがありそう。

  • CLONE_FILES: 親子プロセス間でファイルディスクリプタテーブルを共有する
  • CLONE_FS: 親子プロセス間でファイルシステム関連の属性を共有する
  • CLONE_IO: 親子プロセス間で I/O コンテキストを共有する
  • CLONE_VM: 親子プロセス間で仮想メモリを共有する

Linuxプログラミングインタフェース」 28章 プロセスの作成とプログラムの実行:詳細 より。

goroutine はスレッドか

goroutineはスレッド(ここではカーネルのtask_structで表現されるスレッド)と1対1対応するわけではない。「タスク」ではない。言語処理系側の実行コンテキスト(グリーンスレッド)である。ただし、M個のスレッド上で、N個のgoroutine (N > M) を動かすことはできる。このようなスレッドの多重化のために、言語処理系上でどのような実装が必要なのかはあまりわかっていない。

キャッシング層が多すぎる

  1. アプリケーションキャッシュ (アプリケーションプロセス上のメモリにキャッシュ?)
  2. Webサーバキャッシュ (nginxのキャッシュなど)
  3. キャッシングサーバー (memcachedなど)
  4. データベースキャッシュ (MySQLのバッファキャッシュなど)
  5. ディレクトリキャッシュ
  6. ファイルメタデータキャッシュ
  7. OSのバッファキャッシュ
  8. ブロックキャッシュ
  9. ディスクコントローラーキャッシュ
  10. ストレージアレイキャッシュ’
  11. オンディスクキャッシュ

とにかくディスクI/Oの遅さをなんとかするために、キャッシュをはさみまくっていることがわかる。

この章でのいくつかの疑問もしくは疑問に対する仮説は後の章で解説されるか、Linuxプログラミングインタフェース読めばわかりそう。 その場にいるオペレーションエンジニアと、我々は言語処理系の実装がわからないという会話をした。処理系もシステム系領域なので、やっていく必要がある。

詳解 システム・パフォーマンス

詳解 システム・パフォーマンス