ウェブエンジニア問題集

コンピュータの仕組み — CPU・メモリ・プロセスの基本

Webアプリケーションを書いているとき、普段は「コードを書けば動く」という感覚で十分です。しかし、メモリ不足でサーバーが落ちる処理が重い原因を調べるマルチスレッドでバグが出るといった場面では、コンピュータの内部で何が起きているのかを知らないと手が止まります。

この章では、プログラムが動く土台となる CPU・メモリ・プロセス・仮想メモリ を、Web開発で必要になる粒度で押さえます。

CPUとメモリは常にペアで動いている

コンピュータの中核は CPU(Central Processing Unit) ですが、CPU単体では何もできません。命令とデータを置いておく場所、つまり メモリ(RAM) と常にペアで動いています。

プログラムが動くときの流れは、おおまかに次のサイクルを高速に繰り返しているだけです。

この1サイクルをCPUは1秒間に何十億回も回しています。だから「重い処理」とは、このサイクルを膨大な回数回さないと終わらない処理だ、と理解できます。

記憶装置は「速度と容量のトレードオフ」の階層になっている

コンピュータの記憶装置は1種類ではありません。速くて小さいもの遅くて大きいもの を組み合わせた階層構造になっています。

階層ごとの速度差は桁違いです。ざっくりとしたイメージを押さえておきましょう。

階層アクセス時間のイメージ人間のスケールに例えると
レジスタ / L11ナノ秒未満目の前の紙に書く
メインメモリ数十〜100ナノ秒本棚から本を取る
SSD0.1ミリ秒前後隣の建物まで行って取る
HDD10ミリ秒前後都内を電車で移動
ネットワーク数十〜数百ミリ秒海外に郵送して取り寄せる

Web開発の文脈では、DBクエリやAPIアクセスは「ネットワーク越し」 なので、メモリ上の変数を触るのに比べて100万倍オーダーで遅い、という感覚が役立ちます。「N+1問題」のような不要なクエリを嫌う理由はここにあります。

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

OSはプログラムの実行を プロセススレッド という単位で管理しています。この2つの違いは、並行処理やマルチスレッドのバグを調べるときに必ず必要になります。

プロセス は1つの独立したプログラムの実行単位で、それぞれが独立したメモリ空間 を持ちます。ブラウザがクラッシュしてもエディタは生きている、というのはプロセスが分離されているからです。

スレッド はプロセスの内側で動く処理の流れで、同じプロセス内のスレッドは同じメモリを共有 します。共有しているぶん軽量で、複数のスレッドで並列処理ができる一方、同じ変数を同時に書き換えるとバグる(競合状態)という難しさがあります。

プロセススレッド
メモリ空間独立共有
生成コスト高い低い
1つが落ちたとき他のプロセスは生き残る同じプロセス内は全滅することが多い
通信プロセス間通信 (IPC) が必要メモリを直接読み書きできる

Node.jsのようにシングルスレッドで動くランタイムもあれば、GoやJavaのようにマルチスレッドを前提にした言語もあります。マルチスレッドの方が速そうに見えますが、共有メモリの同期で苦労するぶん、設計難度は跳ね上がります。

仮想メモリ — なぜ8GBのPCで12GB分のアプリが動くのか

物理的なRAMの容量には限りがあります。しかし、8GBのRAMしかないPCで、合計12GB必要になりそうなアプリを同時に起動しても、一応動いたりします。これは 仮想メモリ の仕組みのおかげです。

OSは各プロセスに「自分だけの広大なメモリ空間」という幻想を見せています。実際には必要な部分だけを物理メモリに載せ、あふれた分はSSD/HDD上の「スワップ領域」に退避させています。

使っていないページをディスクに追い出す(スワップアウト)、必要になったらメモリに戻す(スワップイン)、という動きを透過的にやってくれます。

ただし、ディスクへのアクセスはメモリより1万倍以上遅い ので、スワップが頻発するとCPUがほぼ待ち時間で埋まって極端に遅くなる 現象(スラッシング)が起きます。本番サーバーで「急に重い」となったとき、まずは freetop でスワップ使用量を見るのがお決まりです。

よくあるハマりどころ

1. Node.jsでOOM(Out of Memory)で死ぬ

Node.jsのデフォルトのヒープ上限は1.5GB前後です。大きなJSONをそのままメモリに載せると簡単に超えます。ストリーム処理にするか、--max-old-space-size で上限を増やす、あるいは処理をバッチ分割する必要があります。

2. Dockerコンテナがメモリ上限で勝手に kill される

コンテナは ホスト全体のメモリではなく、コンテナに割り当てられたメモリ上限 で動きます。ローカルでは動くのに本番で落ちる場合、コンテナのメモリ上限を疑ってみましょう。

3. マルチスレッドで「たまに値がおかしい」

複数のスレッドが同じ変数を同時に書き換えている可能性が高いです。ロック(Mutex)やアトミック操作で同期するか、そもそも共有しない設計に寄せるのが基本です。

ちゃんと使うためのポイント

  • CPUは「フェッチ → デコード → 実行」のサイクルをひたすら回しているだけ
  • 記憶階層は桁違いの速度差。DB・APIアクセスはメモリより100万倍オーダーで遅い
  • プロセスはメモリ独立、スレッドはメモリ共有。共有するぶんバグの難度が上がる
  • 仮想メモリ のおかげで物理RAMより大きなアプリが動く。ただしスワップが頻発すると一気に重くなる

次の章では、CPUが扱うデータをどう並べるかという話、データ構造 に入ります。配列・スタック・キュー・ハッシュテーブルなど、どの言語を使ってもついて回る基本を整理します。