「プログラムって、コンピュータの中でどうやって動いているの?」そんな疑問を抱いたことはありませんか?
アプリを起動すると瞬時に画面が表示される、ゲームをプレイすると滑らかに動く、計算機で数字を入力すると即座に答えが出る。こうした当たり前の現象の裏側で、コンピュータの中では一体何が起きているのでしょうか?
前回まで、コンピュータが電気で計算し、メモリにデータを記憶する仕組みを見てきました。今回は、その基盤の上でプログラムがどのように「実行」されるのか、その驚くべき仕組みを解き明かしていきます!
📌 忙しい人はここだけ読めばOK!
プログラム実行の本質は以下の通りです:
- 命令の読み込み → CPUがメモリからプログラムの命令を一つずつ取得
- 命令の解釈 → 機械語命令を解析して何をすべきかを判断
- 命令の実行 → 実際の処理(計算、データ移動、判断など)を実行
- 次の命令へ → 1秒間に数十億回この繰り返し
身近な例:料理レシピを一行ずつ読んで実行する料理人のように、CPUもプログラムを一命令ずつ読んで実行しています
今すぐできること:スマホでアプリを起動して「今、CPUが猛スピードで命令を読み込んでいる」と意識してみる
プログラム実行の詳細な仕組みと、なぜこの方式が採用されたのかを知りたい方は、以下をお読みください。
なぜこのような実行方式が生まれたのか?
人間の作業との根本的な違い
人間が何かの作業をする時を想像してみてください。例えば、朝の支度をする場合:
「歯を磨いて、顔を洗って、着替えて、朝食を食べて…」
人間は複数のことを同時に考えながら作業できます。歯を磨きながら今日の予定を考えたり、朝食を食べながらニュースを聞いたりできますよね。
しかし、初期のコンピュータ設計者たちが直面した問題は、電子回路は本質的に「一度に一つのことしかできない」ということでした。
電子回路の物理的制約
コンピュータの中核であるCPUは、トランジスタという小さなスイッチが数十億個集まった電子回路です。このトランジスタは、電気信号によって「ON(1)」または「OFF(0)」の状態を瞬時に切り替えることができます。
しかし、どんなに高速でも、電子回路は物理的に「順番に」処理せざるを得ないのです。なぜなら:
- 電気信号の伝播:どんなに小さくても、電気信号がトランジスタ間を移動するには時間がかかる
- 計算回路の制約:足し算をする回路と掛け算をする回路を同時に使うことはできない
- メモリアクセスの競合:同じメモリ領域に同時にアクセスすると衝突が起きる
この物理的制約から、「一つの命令を完全に処理してから、次の命令に移る」という順次実行方式が採用されることになったのです!
1940年代の大発見:プログラム内蔵方式
実は、プログラム実行の仕組みには革命的なアイデアが隠されています。それが「プログラム内蔵方式」です。
従来の機械(1940年代以前):
- 機械の動作は物理的な配線や歯車で決まっていた
- 違う計算をするには、機械そのものを作り直す必要があった
- 例:そろばんは足し算引き算しかできない、計算機は掛け算割り算専用
コンピュータの革新(1940年代後半):
- プログラムもデータとしてメモリに保存
- 同じ機械で、保存するプログラムを変えるだけで全く違う処理が可能
- ゲーム、計算、文書作成、インターネット…すべて同じCPUで実行
これは「図面(プログラム)を変えるだけで、同じ工場(CPU)が全く違う製品を作れる」ような画期的な発明だったのです!
CPUがプログラムを実行する4つのステップ
現代のCPUは、毎秒数十億回という驚異的な速度で、以下の4つのステップを繰り返しています:
ステップ1:フェッチ(Fetch)- 命令の取得
具体的な動作:
CPUは「プログラムカウンタ」(次に実行する命令の住所を記録している小さなメモリ)という特別なレジスタ(CPUが高速でアクセスできる作業用の記憶領域)に保存されているアドレスを見て、メモリから次に実行すべき命令を読み込みます。
身近な例で理解:
料理をする時、レシピ本の「手順3」を見て「玉ねぎを炒める」という指示を読むのと同じです。CPUは「メモリのアドレス1000番地」を見て「ADD命令」を読み込みます。
実際のメモリの様子:
メモリアドレス 保存されている内容(機械語)
1000番地: 10110000 01000001 (ADD命令)
1001番地: 10110001 01000010 (SUB命令)
1002番地: 11000000 00000001 (JUMP命令)
ステップ2:デコード(Decode)- 命令の解釈
具体的な動作:
読み込んだ機械語命令を解析して、「何をすべきか」「どのデータを使うか」「結果をどこに保存するか」を判断します。
なぜこのステップが必要なのか:
機械語はすべて0と1の組み合わせなので、CPUは「この0と1の並びは足し算命令だ」「この部分はデータのアドレスだ」と解釈する必要があります。
デコードの実例:
機械語: 10110000 01000001
デコード結果:
- 命令種類: ADD(足し算)
- 対象データ1: レジスタA
- 対象データ2: レジスタB
- 結果保存先: レジスタC
これは、レシピの「玉ねぎを炒める」を読んで「玉ねぎ(材料)をフライパン(道具)で炒める(動作)」と理解するのと同じプロセスです!
ステップ3:エクゼキュート(Execute)- 命令の実行
具体的な動作:
解釈した命令に基づいて、実際の処理を行います。計算、データの移動、条件判断など、CPUが持つ様々な機能を使って作業を実行します。
CPUの実行ユニット:
- ALU(算術論理演算装置):加算、減算、論理演算(AND、ORなどの論理判断)を担当する専用回路
- FPU(浮動小数点演算装置):小数の複雑な計算(3.14 × 2.5 など)を担当する専用回路
- ロード/ストアユニット:メモリとCPUの間でデータを出し入れする専用回路
- 分岐予測ユニット:プログラムの「もし〜なら」の判断でどちらに進むかを予測する専用回路
実行の実例:
命令: 「5 + 3 を計算しなさい」
実行プロセス:
1. レジスタAから「5」を取得
2. レジスタBから「3」を取得
3. ALUで加算処理実行
4. 結果「8」をレジスタCに保存
ステップ4:ライトバック(Write Back)- 結果の保存と次へ
具体的な動作:
実行結果をメモリやレジスタに保存し、プログラムカウンタを次の命令のアドレスに更新します。そして再びステップ1に戻ります。
継続的な実行:
このサイクルは、プログラムが終了するか、エラーが発生するまで永続的に続きます。現代のCPUでは、1秒間に30億〜50億回もこの4ステップを繰り返しています!
プログラムカウンタの更新:
実行前: プログラムカウンタ = 1000番地
実行後: プログラムカウンタ = 1001番地(次の命令)
特殊ケース(ジャンプ命令の場合):
実行前: プログラムカウンタ = 1002番地
実行後: プログラムカウンタ = 2050番地(指定されたアドレス)
実際のプログラムではどうなっているのか?
Javaプログラムが動く様子を追跡してみよう
以下の簡単なJavaプログラムが実際にどのように実行されるかを見てみましょう:
public class Calculator {
public static void main(String[] args) {
int a = 5;
int b = 3;
int result = a + b;
System.out.println(result);
}
}
段階1:コンパイル時
- Javaコンパイラ(人間の書いたプログラムをコンピュータが理解できる形に翻訳するソフトウェア)が人間の書いたコードを「バイトコード」(Java専用の中間的な機械語)に変換
- バイトコードはJava仮想マシン(JVM:Javaプログラムを動かすための専用ソフトウェア)が理解できる中間言語
- 「int a = 5」→「istore_1」のような命令に変換
段階2:実行時
- JVMがバイトコードを機械語に変換(Just-In-Time コンパイル:プログラム実行の直前に、バイトコードをそのコンピュータ専用の機械語に翻訳する方式)
- 変換された機械語がCPUで実行開始
- CPUが4ステップサイクルで処理:
- 「istore_1」命令をフェッチ
- 「整数5をローカル変数1に保存」とデコード
- 実際にメモリに5を書き込み
- 次の命令に進む
現代のCPUが実現している驚異的な性能
実行速度の実感:
- 現代のスマートフォン:1秒間に約30億命令を実行
- 高性能ノートPC:1秒間に約50億命令を実行
- スーパーコンピュータ:1秒間に数兆命令を実行
これがどれほど高速かというと、1秒間に地球を7周半できる光の速度で情報処理をしているようなものです!
並列実行の工夫:
現代のCPUは、基本的には順次実行ですが、以下のような高度な技術で実質的な並列処理を実現しています:
- パイプライン処理:工場の流れ作業のように、ある命令を実行している間に次の命令の準備を同時進行で行う技術
- スーパースカラ:CPUの中に複数の作業場所(実行ユニット)を作り、関係のない複数の命令を本当に同時実行する技術
- 分岐予測:プログラムが「もしAならBを実行、そうでなければCを実行」という分かれ道で、どちらに進むかを予測して先に準備する技術
- マルチコア:CPUを丸ごと複数個搭載して、真の意味での並列実行を可能にする技術
プログラマーが知っておくべき実行の仕組み
なぜこの知識がプログラミングに役立つのか?
1. パフォーマンスの理解
「なぜこのコードは遅いのか?」という疑問に対して、CPU実行の観点から原因を推測できるようになります。
// 遅い例:同じ重い計算を何度も繰り返している
for (int i = 0; i < 1000000; i++) {
// heavyCalculation()は時間のかかる計算(例:複雑な数学計算)
// 毎回同じ計算を2回実行するので、CPUに無駄な負荷をかけている
int result = heavyCalculation() + heavyCalculation();
}
// 速い例:計算結果を一度だけ求めて、それを再利用
int cached = heavyCalculation(); // 重い計算は一度だけ
for (int i = 0; i < 1000000; i++) {
// 既に計算済みの値を足すだけなので、CPUの負荷が軽い
int result = cached + cached;
}
2. メモリ使用量の意識
CPUとメモリの関係を理解することで、メモリ効率の良いプログラムが書けるようになります。
3. エラーの理解
「NullPointerException」(存在しないデータを使おうとした時のエラー)「StackOverflowError」(メモリの積み重ね領域があふれた時のエラー)などのエラーが、実際にCPUとメモリでどのような問題を引き起こしているかを理解できます。
プログラミング初心者へのアドバイス
まずは「動く」ことを目指そう
プログラム実行の仕組みを理解することは重要ですが、最初は「とりあえず動くプログラム」を作ることに集中しましょう。仕組みの理解は、プログラミングに慣れてから深めていけば十分です!
「なぜ?」を大切にしよう
プログラムが期待通りに動かない時、「なぜこうなるのか?」という疑問を持つことで、CPUとメモリの動作原理への理解が自然に深まります。
小さなプログラムから始めよう
複雑なプログラムよりも、「変数に値を入れて表示する」「簡単な計算をする」といった基本的なプログラムから始めることで、実行の流れを実感できます。
次回予告:プログラミング言語の世界へ
今回は、コンピュータがプログラムを実行する基本的な仕組みを見てきました。CPUが機械語命令を一つずつ読み込んで処理する様子、そしてその驚異的な速度を理解できたでしょうか?
次回は、いよいよ「プログラミング言語」の世界に入ります。人間が書いたJavaコードが、どのようにしてCPUが理解できる機械語に変換されるのか、その翻訳の仕組みを詳しく解説していきます!
まとめ
プログラム実行の本質は:
- 順次実行の原理 - CPUは物理的制約により一命令ずつ順番に処理
- 4つのステップサイクル - フェッチ→デコード→実行→ライトバックの繰り返し
- プログラム内蔵方式 - 同じCPUで異なるプログラムを実行可能な革新的仕組み
- 驚異的な実行速度 - 毎秒数十億回の命令実行で滑らかな動作を実現
- 実践的な価値 - パフォーマンスやエラーの理解に役立つ基礎知識
コンピュータの中で起きている「プログラム実行」は、決して魔法ではありません。電気回路の物理的な制約から生まれた合理的で効率的な仕組みなのです。
あなたが書くJavaプログラムも、この基本原理の上で動いています。今度プログラムを実行する時は、「今、CPUが猛スピードで命令を読み込んで処理しているんだ」と想像してみてください。きっと、プログラミングがより身近で親しみやすいものに感じられるはずです!
📚 他の学習課題も解決しませんか?
この記事は技術的理解カテゴリーの内容でした。プログラミング学習には他にも様々な課題があります:
- 心理的障壁 - 挫折感やモチベーション管理
- 学習プロセス - 効率的な学習方法や継続のコツ
- 実践応用 - より良いコードを書くためのスキル
詳しくはプログラミング学習サポートをご覧ください。
📖 このシリーズの続きを読む
次の記事: 【ゼロから理解するコンピュータ 第5回】プログラミング言語とは何か?人間の言葉をコンピュータ語に翻訳する驚異の仕組み
コメント