📗

コンピュータ・システム ~プログラマの視点から~ を読みました

2021/12/01に公開約5,500字

こんにちは、42tokyo Advent Calendar 2021 の3日目を担当する、在校生のbayamasaです。

42Tokyoでの活動にてこちらの本を読んだので、この本のまとめ及び感想などを記事にさせていただきたいと思います。

本について

こちらの本は主にカーネギーメロン大学で使われている教科書であり
英タイトルは、「Computer Systems: A Programmer's Perspective」 なので
通称CS:APPと呼ばれたりします。

内容

内容としてはいわゆるコンピュータサイエンスの基礎です。
この本は現代でもっとも多く使われているCPUの命令アーキテクチャ x86-64がプログラミングの視点から、どのように動いているのかということをメインに解説してあります。
なので、この本を全部読めば現代普及しているPCが、どのように動いてプログラムを動かしているのかということがわかります。

価格とか量とか

amazonのリンクはこちら。

https://www.amazon.co.jp/コンピュータ・システム-五島-正裕/dp/4621302019

特徴としては

  1. 高い
  2. 分厚い

自分が購入した本の中では、おそらく一番高い本で一番分厚い本でした。
上のリンクを見ればわかると思うですが、大体18kくらいでページ数も928ページあるそうです。
なのでこれを読むだけで、それなりの達成感が得られることでしょう。

ちなみに英語版はなんと無料で読めます。

https://csapp.cs.cmu.edu/

ここからは各章がどういう章立てになっているのかを詳しく見ていきます。

第1章 コンピュータ・システム・ツアー

この章は序章です。いわゆる全体の構成などを話しています。
この章を見て、「面白そう!」と思う人はこの本が向いている気がします。

第2章 情報の表現と操作

この章は主にC言語で使われているの話です。
基本的にこの本ではC言語がメイン言語として話の展開をします。
ですので、C言語の基本仕様の型の仕様がどうなっているのか、という話がされています。

  • ビット演算/シフト演算
  • 各型における最大値/最小値
  • オーバーフローの仕組み
  • 浮動小数点の仕組み

個人的にこの辺は面白かったです。というのも42Tokyoの課題の最序盤で、この辺の知識が必要になってくるからです。

  • 2進数における数値の表現方法
  • size_tとはなにか
  • unsigned charとcharの違いはなにか
  • INT_MINはなぜ -INT_MAX-1と書いてあるのか
  • IEEE754はなぜ生まれ、どういう特徴があって、どう便利なのか

この辺がクリアになると、現実で使っている数字とコンピュータ上の数値の違いなどが明確になります。
勉強するまではふわっとしていた、コンピュータ上で数値を表す方法について不明瞭な部分がなくなります。
個人的に42生がもっとも必要になる章であり、ここだけは誰もが必読であるべき。だと思っています。
この章がわかるだけで0と1で表現されるbitに対して苦手意識がなくなるのはこの本のとても良いところだと思います。
他の章が課題で活躍するケースはだいぶ先になってしまったり、使わなかったりするのですが...

第3章 プログラムのマシン・レベルの表現

この章で得られる知識はアセンブリ言語です。
アセンブラの命令や仕組みについて勉強します。

これによりC言語が吐き出すアセンブリ言語について少しだけ理解できるようになります。
アセンブリ言語とは機械語に一対一に対応した言語です。ようするにアセンブリが分かれば、プログラミング言語というジャンルに置いて知らないものはなくなります。
CもRubyもGoも結局アセンブリ言語に変換されるので、アセンブリ言語が読めれば全ての言語が使えるといっても過言ではありません(過言であり、暴論です)

ただ、アセンブラという言葉を使われた時点でアレルギー反応を示していた自分にとっては、「その中で何が行われているか」ということが分かり、拒否反応が出なくなっただけでも収穫であると思います。

個人的にはポインタという概念が完全に掴みきれたと思えたのも、アセンブリを読める様になったからだと思います。

簡単に説明すると、アセンブリ命令はデータアクセスにおいて、レジスタとしかやり取りをしません。
なのでメモリ上の値を見たいときは、レジスタにメモリの値書き込み、それを参照することでメモリの値を見に行く事ができます。
これがポインタと同じ概念です。ある値を見に行きたいが直接見に行くのではなく、何かしらのフィルターを通して値を見る。ということです。
もしこの辺を詳しく知りたいという方はぜひこちらの本を。

第4章 プロセッサ・アーキテクチャ

この章から一点変わってCPUの話に移ります。
CPUは3章でやったアセンブリを一行ずつ実行していくことでプログラムを動かします。
この一行ずつ実行していくというプロセスをもう少し詳細に見ていくのが、このパートです。

具体的には以下です。

  1. フェッチ: コードを取ってくる
  2. デコード: コードの解読
  3. 実行: コードの演算処理
  4. メモリ: メモリからの読み出し/書き込み
  5. ライトバック: レジスタ/キャッシュ/メモリへの演算結果の書き込み

CPUはこの順番で全ての命令を実行していきます。
しかし、CPUは常に性能向上を求められています。毎回順番に実行していたのでは速度効率が悪いです。
そのため、パイプライン処理と呼ばれる並列実行を詳しく見ていきます。

第5章 プログラム性能の最適化

この章は現代のPCがプログラムを早く動かしている様々な工夫について記載されています。
第4章でやったパイプライン処理はシングルコアにおける最適化の話です。
つまり現代で使われているマルチコアプロセッサ(いわゆるCore i7など)ではまだまだ最大性能を引き出すことができていません。
そのため、現代のPCのアーキテクチャでいうマルチコアプロセッサ/多段キャッシュ/DRAMなどを駆使してどうやってプログラム速度を早くしているのかということを学習します。

第6章 メモリ階層

この章で第一部が修了です。第1章~第6章までではいわゆるハードウェアとソフトウェアの関連性みたいなものが書いてあります。第二部ではよりソフトウェアのみの話になっていたりします。

メモリ、つまりはデータを保存しておく場所は複数あります。
なぜ複数あるかというと、データ移動速度とデータの保存量はトレードオフだからです。
データの移動速度、つまりデータの読み込み/取り出し速度が早くなればなるほど保存できるデータの量は少なくなります。逆に大量に保存できるメモリの場合、read/writeの速度は遅くなります。
つまり時間と空間がトレードオフになるのです。(かっこいい)

そのためPCでは複数の保存箇所を用意しておき、毎回読み出し/書き込みをするメモリは爆速にしておき、たまに取り出したりする箇所は低速かつ大容量のメモリを配置しています。

現代では多くの場合、以下のような構成になっています。
高速/低容量

  1. レジスタ
  2. L1キャッシュ
  3. L2キャッシュ
  4. L3キャッシュ
  5. DRAM
  6. SSD/HDD
    低速/大容量

プログラムはデータを探しに行くとき、基本的には高速な容量をからデータを持って来れることが望ましいので、レジスタから順番にデータを探しに行きます。レジスタからL1キャッシュを参照し、もしL1キャッシュにデータが存在しなかったら、L2/L3とデータを順番に参照しにいきます。
どのようにデータが存在しないということを確認しているのかというと、これもまたアルゴリズムが存在します。この辺が面白いです。

また表紙のカラフルな謎の画像はメモリマウンテンと呼ばれるもので、各メモリ階層速度と容量の関係性を表しています。

第7章 リンク

C言語のコンパイルは複数のことを行っています。
その辺は第1章に書いてあるのですが、やっていることの一つに「リンク」というものが存在します。
これは、めちゃめちゃ簡単に言うと複数のファイルで書いたコードをつなげるという役割です。

つまりこの機能が存在しないと、一つの動くプログラムは一つのテキストファイルからしか生成できないということになってしまいます。
複数のファイルにコードを細分化できるからこそ、現代の複数人での開発などが成立しているということです。なので結構大事です。

この辺の詳しい話は、こちらの以前42Tokyoで公演してくださったアーカイブが参考になるかと思います。

https://www.youtube.com/watch?v=alDvi0vdb0Q
https://www.youtube.com/watch?v=tAMruxkwVf0

第8章 例外的な制御フロー

プログラムは基本的に上から順番に命令を実行していきます。これを制御フローといいます。
a.outを実行することにより、この制御フローに従ってプログラムを実行します。

しかしa.outというのは、そのPCで動いているプログラムの一部であって、他にも複数のプログラムが動いています。discordを開いているかも知れないし、ChromeでYouTubeを見ているかもしれません。
もし、その他で動いているプログラムが、a.outに影響を及ぼしたとしたら?
それは制御フローの管轄外(制御フローとはあくまでa.outというプログラムの中の話なので)からの処理になります。

これを例外的な制御フローと呼びます。
この章ではこの例外的な制御フローを紐解いていくことで、実行プログラムの外側の部分である、プロセス/並行処理/シグナルなどの仕組みを理解していきます。
この辺も低レイヤーのよくわからない箇所なので個人的にはとても良かったです。

第9章 仮想メモリ

DRAMの話です。前の章でやっていたメモリの部分からDRAMをより詳しく掘り下げます。
この章は4章から始まったハードウェアに関する説明の集大成です。要するにムズいです。
6章のキャッシュ機構や8章のプロセス関連の話の応用編で、例としてIntel Core i7/Linux メモリシステムにおける実際の現代のPCにおけるデータ取得の仕組みが細かく記載されています。

この本の目標をこのページを理解することに絞って読み進めても良さそうな感じがします。
それくらいボリューミーで深い章になっていると思います。

後半でmallocとfreeの仕組みについても記載があるので、面白いです。

第10章 システムレベルI/O

第11章 ネットワーク・プログラミング

第12章 並行プログラミング

第10章からは第三部になり、応用編です。
ここからの章は正直他に詳しい本があります。
「詳解UNIX」、「Linuxプログラミングインターフェース」など
要するに、今までの知識をふまえて実際に動くコードを書いてみよう、のフェーズです。

この辺は42Tokyoの課題との親和性が高く、各課題をやる前によむことでより知識を広く深くする事ができます。が、個人的には流し読みで大丈夫な気がします。
メインは9章までだと思っています。

感想

めちゃめちゃ高い買い物だったけど、だからこそ全部読み切れたような気がします。
後は読書会があったおかげで読み切ることができました。感謝。
元々未経験でエンジニアになったので、低レイヤーのことはさっぱり分からずAWSを触っているときや、Railsを使っているときには表面上の知識だけで仕事をしていたので、ずっとモヤモヤしていました。
今回この本を読んだことでそういう部分を解消できたのは本当に大きかったと思います。
ただこの本を全部読んでわかったことは、この本で書かれていることはあくまで基礎であり、各章の専門性はより膨大に広がっているということです。研究とかをしていくとなると、まだまだ知識が足りません。

またこの本だとOSの部分などはカバーしきれていないです。今後はこの辺にも突っ込むために、勉強していければと思います。

この本はこういう方におすすめです。

  • アセンブリ/プロセス/例外などに出くわしたとき、モヤモヤする。
  • 自分が書いたコードがどのように機械語に変換されるか知りたい。
  • 現代のノイマン型コンピューターがどのような仕組みになっているか隅々まで知りたい。

最後に

明日は、tayamamoさんが 「GitHub Actions でリークテスト:)」 について書いてくれる予定ですので、そちらの記事もお楽しみに!

Discussion

ログインするとコメントできます