マルチコアとは何か
シングルコアCPUの性能向上が限界(消費電力の壁、クロック周波数の壁、パイプライン複雑性の壁)に直面した後、技術者たちは「単純で乱暴」だが効果的な方法を思いつきました。それは1つのチップに複数のCPUコアを載せることです。この講義では、マルチコアの原理、利点、そしてアムダールの法則による並列高速化の制限について紹介します。
身近な例え:ファストフード店の厨房
ランチタイムのピーク時にあるファストフード店を想像してください。
シングルコアモード:シェフは1人だけ。彼は必死に速度を上げ(周波数を上げ)、調理の手順を最適化する(パイプライン)が、どんなに最適化しても同時に作れるのは1品だけ。客は長蛇の列に並び、待ちくたびれている。
マルチコアモード:店主は4人のシェフを雇い、一人に1つのコンロを与えます。4人のシェフは同時に4つの異なる料理を作れます。各シェフがより速くなる必要はありませんが、全体の提供量は数倍になります。
これがマルチコアの設計哲学です:シングルコアで速度を上げるのが難しくなったら、いくつかのコアを追加して並行して動作させるほうがよいでしょう。
マルチコアは「すべてを4つに複製する」という意味ではありません。4つのコアは通常、同じメモリ、同じディスクインターフェース、同じオペレーティングシステムを共有しており、「計算ユニット」のレイヤーだけが複製されています。まるで4人のシェフが同じ冷蔵庫と食材倉庫を共有しているようなものです。
シングルコア vs マルチコア:アーキテクチャ比較
シングルコアCPUの限界
2000年代初期、CPU業界はクロック周波数を絶えず上げることで性能を向上させていました——1 GHzから2 GHz、そして3 GHzへ。しかしこの道はすぐに終わりを迎えました。
シングルコアの周波数における3大ボトルネック:
- 消費電力の壁:周波数が高くなるほど消費電力は指数関数的に増加し、放熱が物理的な限界となる
- メモリの壁:CPUが速すぎてメモリが追いつかず、ほとんどの時間データを待っている
- ILPの壁:命令レベル並列性(ILP)は限界まで引き出されており、単一の命令ストリーム内で並列実行できる命令はますます少なくなっている
2005年前後、Intel と AMD は単純な「周波数競争」を次々と放棄し、マルチコア路線へと転換しました。
マルチコアCPUの構造
典型的なマルチコアCPUの内部構造は次のとおりです:
+-----------------------------------------------------------+ | CPU 芯片 | | +-------------+ +-------------+ +-------------+ +-----+ | | 核心 1 | | 核心 2 | | 核心 3 | |核 4 | | | L1 Cache | | L1 Cache | | L1 Cache | | ... | | | L2 Cache | | L2 Cache | | L2 Cache | | | | +------+------+ +------+------+ +------+------+ +--+--+ | | | | | | +----------------+----------------+--------------+ | | | +-----+------+ | | 共享 L3 Cache | | +-----+------+ | | | +-----+------+ | | 内存控制器 | ← 连接主内存 (RAM) | +------------+ +-----------------------------------------------------------+
重要な設計:
- 各コアは独自のL1およびL2キャッシュを持つ(プライベート)
- 複数のコアが1つのL3キャッシュを共有する
- すべてのコアはメモリコントローラを介して同じメインメモリにアクセスする
- オペレーティングシステムがタスクを異なるコアに割り当てる役割を担う
アムダールの法則:マルチコア高速化の上限
「コアが多ければ速い」という直感は必ずしも正しくありません。1967年、コンピュータ科学者のジーン・アムダールは有名な法則を提唱しました。
アムダールの法則:タスク全体の高速化比は、タスク内で直列に実行しなければならない部分に制限される。
公式は次のとおりです:
加速比 = 1 / (S + P/N) 其中: S = 任务中必须串行执行的比例(0~1) P = 任务中可以并行执行的比例(0~1),且 S + P = 1 N = 并行处理单元的数量(核心数)
この公式は残酷な事実を明らかにしています:
たとえ無限に多くのコアがあっても、タスクの50%しか並列化できない場合、高速化比は最大でも2倍にしかなりません。
導出:Nが無限大に近づくと、P/Nは0に近づき、高速化比は1/Sに近づきます。
| 並列化可能な割合 | 1コア | 2コア | 4コア | 8コア | 16コア | 無限のコア数(上限) |
|---|---|---|---|---|---|---|
| 50% | 1.00x | 1.33x | 1.60x | 1.78x | 1.88x | 2.00x |
| 75% | 1.00x | 1.60x | 2.29x | 2.91x | 3.37x | 4.00x |
| 90% | 1.00x | 1.82x | 3.08x | 4.71x | 6.40x | 10.00x |
| 95% | 1.00x | 1.90x | 3.48x | 5.93x | 9.14x | 20.00x |
| 99% | 1.00x | 1.98x | 3.88x | 7.48x | 13.91x | 100.00x |
この表の傾向を観察しましょう:
- 並列化可能な割合が高いほど、マルチコア高速化は顕著になる。99%並列化可能なタスクは16コアで約14倍高速化できる
- コアが増えるほど、限界利益は小さくなる。2コアから4コアへの高速化比の向上は、16コアから32コアへの向上よりはるかに大きい
- 直列部分はハードな上限である。50%しか並列化できない場合、1万個のコアを使っても最大でも2倍にしかならない
アムダールの法則は、なぜ「コアが多くても必ずしも速くない」のかを説明しています。プログラムが主に直列ロジックで構成されている場合——たとえば、ある計算ステップが前のステップの結果を待たなければならない場合——どれだけ多くのコアがあっても、ただ見ているだけで役に立ちません。
対話デモ:アムダールの法則計算機
以下のPythonコードは、アムダールの法則の高速化比の比較表を計算して表示し、同時にコア数に応じた高速化比の変化の傾向を描画します。「直列ボトルネック」の影響を直感的に感じられます。
実例
アムダールの法則計算機 (サンプル デモ)
さまざまな並列化割合とコア数での高速化比を計算する
比較表と傾向分析を表示する
"""
def amdahl_speedup(parallel_fraction, num_cores):
"""
アムダールの法則の核心的な公式
parallel_fraction: 並列実行可能な割合 (0.0 ~ 1.0)
num_cores: コア数
高速化比を返す
"""
if num_cores == 1:
return 1.0
serial_fraction = 1.0 - parallel_fraction
speedup = 1.0 / (serial_fraction + parallel_fraction / num_cores)
return speedup
def amdahl_limit(parallel_fraction):
"""
アムダールの法則の理論上の高速化上限を計算する(コア数が無限大に近づくとき)
N→∞ のとき、P/N→0、高速化比 → 1/S
"""
serial_fraction = 1.0 - parallel_fraction
if serial_fraction == 0:
return float('inf') # 完全に並列化可能、理論上は無限に高速化できる
return 1.0 / serial_fraction
def print_comparison_table(parallel_fractions, core_counts):
"""完全な高速化比の比較表を表示する"""
# 表の見出し
header = f{'コア数':>6} |
for pf in parallel_fractions:
header += f{pf*100:>5.0f}%並列可能 |
print("=" * (12 + 12 * len(parallel_fractions)))
print(アムダールの法則 - マルチコア高速化比比較表)
print("=" * (12 + 12 * len(parallel_fractions)))
print(header)
print("-" * (12 + 12 * len(parallel_fractions)))
for cores in core_counts:
row = f" {cores:>5} |"
for pf in parallel_fractions:
speedup = amdahl_speedup(pf, cores)
row += f" {speedup:>6.2f}x |"
print(row)
# 理論上の上限
print("-" * (12 + 12 * len(parallel_fractions)))
limit_row = f{'上限':>5} |
for pf in parallel_fractions:
limit = amdahl_limit(pf)
if limit == float('inf'):
limit_row += f上限なし |
else:
limit_row += f" {limit:>6.2f}x |"
print(limit_row)
print("=" * (12 + 12 * len(parallel_fractions)))
def analyze_diminishing_returns(parallel_fraction, max_cores=64):
"""マルチコア高速化の限界利益逓減を分析する"""
print(f"\n限界利益分析(並列化割合 {parallel_fraction*100:.0f}%):)
print(f{'コア数':>6} | {'高速化比':>8} | {'今回の向上':>10} | {'効率':>8})
print("-" * 42)
prev_speedup = 1.0
cores = 1
while cores <= max_cores:
speedup = amdahl_speedup(parallel_fraction, cores)
improvement = speedup - prev_speedup
# 効率 = 実際の高速化比 / コア数(完全な線形高速化の場合は100%)
efficiency = (speedup / cores) * 100
print(f" {cores:>6} | {speedup:>8.2f}x | +{improvement:>8.3f}x | {efficiency:>7.1f}%")
prev_speedup = speedup
if cores == 1:
cores = 2
else:
cores *= 2
def compare_serial_vs_parallel(serial_work_ms, parallel_work_ms, num_cores):
"""
具体タスクの所要時間を用いてアムダールの法則を実演する
serial_work_ms: 直列部分の所要時間(ミリ秒)
parallel_work_ms: 並列部分の所要時間(シングルコア時、ミリ秒)
num_cores: 使用可能なコア数
"""
total_single_ms = serial_work_ms + parallel_work_ms
parallel_fraction = parallel_work_ms / total_single_ms
parallel_time_multi = parallel_work_ms / num_cores
total_multi_ms = serial_work_ms + parallel_time_multi
speedup = total_single_ms / total_multi_ms
print(f"\n具体タスクの所要時間シミュレーション:)
print(f直列部分の所要時間: {serial_work_ms} ms(高速化不可))
print(f並列部分の所要時間: {parallel_work_ms} ms(シングルコア))
print(f並列化割合: {parallel_fraction*100:.0f}%)
print(f"")
print(fシングルコア合計所要時間: {total_single_ms} ms)
print(f{num_cores} コア合計所要時間: {total_multi_ms:.1f} ms(直列 {serial_work_ms} + 並列 {parallel_work_ms}/{num_cores} = {parallel_time_multi:.1f}))
print(f実際の高速化比: {speedup:.2f}x)
print(fアムダール上限: {amdahl_limit(parallel_fraction):.2f}x)
return speedup
# ===== メインプログラム =====
print("=" * 60)
print(アムダールの法則計算機 (サンプル))
print("=" * 60)
# 1. 基本比較表
parallel_levels = [0.50, 0.75, 0.90, 0.95, 0.99]
core_list = [1, 2, 4, 8, 16, 32, 64, 128, 256]
print_comparison_table(parallel_levels, core_list)
# 2. 限界利益分析(75%並列化可能な場合)
analyze_diminishing_returns(0.75, max_cores=64)
# 3. 具体タスクのシミュレーション
print("\n" + "=" * 60)
print(実際のシナリオシミュレーション)
print("=" * 60)
# シナリオ1:ビデオレンダリング(高い並列化、95%)
print("\n[シナリオ1] ビデオレンダリングタスク)
print(直列部分: ファイル読み込み、エンコーダ初期化 = 10ms)
print(並列部分: フレームごとのレンダリング = 200ms(シングルコア))
compare_serial_vs_parallel(10, 200, 16)
# シナリオ2:データベーストランザクション(中程度の並列化、50%)
print("\n[シナリオ2] データベーストランザクション処理)
print(直列部分: ログ書き込み、ロック管理 = 50ms)
print(並列部分: クエリ実行 = 50ms(シングルコア))
compare_serial_vs_parallel(50, 50, 16)
# シナリオ3:純粋な計算(高度な並列化、99%)
print("\n[シナリオ3] 科学計算/行列演算)
print(" 逐次部分: 結果の集約 = 1ms")
print(" 並列部分: 行列乗算 = 1000ms(シングルコア)")
compare_serial_vs_parallel(1, 1000, 16)
# 4. 最適なコア数の提案
print("\n" + "=" * 60)
print(実用のなアドバイス:より多くのコアが必要かどうかを判断するには?)
print("=" * 60)
# 異なる並列比率における「コストパフォーマンス」の転換点を計算する
print(f" {'並列比率':>10} | {'2コア向上':>10} | {'4コア向上':>10} | {'8コア向上':>10} | {'16コア向上':>9} | {'推奨コア数':>10}")
print("-" * 70)
for pf in [0.50, 0.60, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 0.99]:
ratios = []
prev = 1.0
recommendations = []
for cores in [2, 4, 8, 16]:
sp = amdahl_speedup(pf, cores)
gain = (sp / prev - 1) * 100 # 前の段階に対する向上率
ratios.append(gain)
prev = sp
if gain < 10:
recommendations.append(cores)
# 推奨:向上率が10%未満ならアップグレードする価値なし
suggested = ">=16コア" if not recommendations else f"{recommendations
print(f" {pf*100:>9.0f}% | {ratios[0]:>9.1f}% | {ratios[1]:>9.1f}% | {ratios[2]:>9.1f}% | {ratios[3]:>8.1f}% | {suggested:>10}")
print()
print(" 経験則:")
print(" - 日常業務、Webブラウジング(並列比率が低い): 4-6 コアで十分")
print(" - プログラミングのコンパイル、軽いゲーム: 6-8 コア")
print(" - 動画レンダリング、3Dモデリング: 8-16 コア")
print(" - 科学計算、AIトレーニング: コアは多ければ多いほど良い(ただし、VRAMやメモリ帯域幅も考慮する必要がある)")
上記のコードを実行すると、いくつかの重要な現象が確認できます:
- 並列比率がすべてを決める:50% 並列化可能なタスクは、256コアを使っても加速比は2倍未満。一方、99% 並列化可能なタスクは16コアで約14倍の高速化が可能。
- 収穫逓減:1コアから2コアへの向上が最大で、その後コア数を2倍にするたびに追加の高速化は小さくなっていく。
- 効率の低下:コア数を2倍にすると、効率(実際の加速比 / コア数)は低下し続ける。これが、むやみにコア数を増やしても割に合わない理由です。
対話デモ:マルチコアタスク割り当てアニメーション
以下では、12個の計算タスクをそれぞれ1つのコアと4つのコアに割り当てて処理します。1コアモードではタスクが直列にキューで実行され、4コアモードでは4つのタスクが同時に並列処理されます。2つのモードでのプログレスバーの進み速度と完了時間の違いを観察してください。
マルチコアタスク割り当てアニメーションデモ (サンプル)
タスクキュー1コア(逐次処理)
4コア(並列処理)
マルチコアの現実での応用
オペレーティングシステムはマルチコアをどうスケジューリングするか
オペレーティングシステムのスケジューラー(Scheduler)は、異なるプロセスやスレッドを異なるコアに割り当てる役割を担います。
それは次のことを考慮します:
- 負荷分散:各コアをできるだけ忙しくさせ、「1つのコアだけが忙しく、他のコアは傍観」といった状況を避ける
- キャッシュアフィニティ:同じスレッドをできるだけ同じコアで実行させ、そのコアの専用キャッシュを利用する
- 消費電力管理:負荷が低いときは一部のコアを停止して電力を節約する
マルチコア vs ハイパースレッディング
物理マルチコアの他に、ハイパースレッディング(Hyper-Threading / SMT)と呼ばれる技術があります。
ハイパースレッディングは、1つの物理コアを2つの「論理コア」のように見せます。あるスレッドがデータを待っている間(キャッシュミス)、コアは別のスレッドに切り替えて処理を続けることができます。
| 方式 | ハードウェアコスト | 性能向上 | 適用シナリオ |
|---|---|---|---|
| 物理マルチコア | 高い(実行ユニット一式を複製する必要がある) | ほぼ2倍(理想的な場合) | 継続的な高負荷タスク |
| ハイパースレッディング (SMT) | 低い(追加のレジスタと制御ロジックだけでよい) | 約10%〜30% | 待ち時間の有効活用 |
多くの現代のCPUは両方の技術を同時に使用しています。例えば、Intel Core i7 は「4物理コア + ハイパースレッディング = 8論理コア」の場合があります。
まとめと確認
一言でまとめると、マルチコアは単一のチップ上に複数のCPUコアを配置し、異なるタスクを並列実行します。しかし、アムダールの法則によれば、実際の加速比はタスク内の並列化可能な割合に依存します。逐次部分が高速化の上限を決定し、コアが増えるほど限界的な利益は小さくなります。
自己チェック問題
- あるタスクの80%は並列実行できます。4コアCPUでは、アムダールの法則による加速比はいくつですか?
答え:S=0.2, P=0.8, N=4。加速比 = 1/(0.2 + 0.8/4) = 1/(0.2 + 0.2) = 1/0.4 = 2.5倍。つまり4コアでも2.5倍の高速化しか得られず、効率はわずか62.5%です。
- タスクがほぼ100%並列実行できる場合、理論上の上限はいくらですか?なぜ現実には達成できないのですか?
答え:理論上の上限は無限大(加速比 = コア数)。しかし現実には、(1) タスクの分割と集約にオーバーヘッドがある、(2) コア間の同期と通信に遅延がある、(3) 共有リソース(メモリ帯域幅など)の競合が存在する、といった制約があります。
- なぜ現代のサーバーCPUには64個や128個ものコアがあるのに、パソコンは通常4〜16個のコアしかないのですか?
答え:サーバーが処理するリクエスト(Webリクエスト、データベースクエリなど)は本質的に高度に並列です。各ユーザーリクエストは相互に独立しており、並列化可能な割合はほぼ100%に近くなります。一方、パソコンの日常的なタスク(ブラウザのレンダリング、Office文書処理など)は並列度が限られており、コアが多すぎても使い切れず、むしろ消費電力とコストが増加します。