多核心是怎么回事
当单核 CPU 的性能提升遇到瓶颈(功耗墙、频率墙、流水线复杂度墙)后,工程师想出了一个「简单粗暴」但有效的办法:在一个芯片上放多个 CPU 核心。本讲将介绍多核心的原理、优势以及阿姆达尔定律对并行加速的限制。
生活中的类比:快餐店厨房
想象一家快餐店在午餐高峰期的情况。
单核模式:只有一个厨师。他在拼命加快速度(提高频率),优化做菜流程(流水线),但再怎么优化,同时只能做一道菜。客人排长队,等得不耐烦。
多核模式:老板雇了 4 个厨师,一人一个灶台。4 个厨师可以同时做 4 道不同的菜。不需要每个厨师变得更快,但整体的出餐量翻了好几倍。
这就是多核心的设计哲学:当单个核心很难再提速时,不如多放几个核心,让它们并行工作。
多核心不等于「把所有东西都做 4 份」。4 个核心通常共享同一块内存、同一个硬盘接口和同一个操作系统,它们只是在「计算单元」层面做了复制。就像 4 个厨师共享同一个冰箱和食材仓库。
单核 vs 多核:架构对比
单核 CPU 的困境
2000 年代初期,CPU 行业靠不断提高频率来提升性能——从 1 GHz 到 2 GHz 再到 3 GHz。但这条路很快走到了尽头。
单核频率的三大瓶颈:
- 功耗墙:频率越高,功耗指数级增长,散热成为物理极限
- 内存墙: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 缓存(私有)
- 多个核心共享一个 L3 缓存
- 所有核心通过内存控制器访问同一块主内存
- 操作系统负责将任务分配到不同的核心上
阿姆达尔定律:多核加速的天花板
「核心越多越快」这个直觉并不总是对的。1967 年,计算机科学家 Gene Amdahl 提出了一个著名的定律。
阿姆达尔定律:一个任务的整体加速比,受限于任务中必须串行执行的部分。
公式如下:
加速比 = 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% 可并行时,即使用一万个核心,最多也只能快 2 倍
阿姆达尔定律解释了为什么「核心多不代表一定快」。如果你的程序主要是串行逻辑——比如一个计算步骤必须等待上一个步骤的结果——那么再多的核心也只能围观,帮不上忙。
交互演示:阿姆达尔定律计算器
下面的 Python 代码计算并打印阿姆达尔定律的加速比对比表格,同时绘制加速比随核心数变化的趋势,让你直观感受「串行瓶颈」的影响。
实例
阿姆达尔定律计算器 (runoob 演示)
计算不同并行比例和核心数下的加速比
打印对比表格和趋势分析
"""
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(" 阿姆达尔定律计算器 (runoob)")
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[0]}核够用"
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(" - 日常办公、网页浏览(并行比例低): 4-6 核足够")
print(" - 编程编译、轻度游戏: 6-8 核")
print(" - 视频渲染、3D 建模: 8-16 核")
print(" - 科学计算、AI 训练: 核心多多益善(但还需考虑显存、内存带宽)")
运行上面的代码,你会看到几个关键现象:
- 并行比例决定一切:50% 可并行的任务,即使用 256 核,加速比也只有不到 2 倍。而 99% 可并行的任务在 16 核上就能加速近 14 倍。
- 边际收益递减:从 1 核到 2 核的提升最大,之后每次翻倍核心数,带来的额外加速越来越小。
- 效率下降:核心数翻倍,效率(实际加速比 / 核心数)持续下降。这就是为什么盲目堆核心数并不划算。
交互演示:多核任务分配动画
下面演示将 12 个计算任务分别分配给 1 个核心 和 4 个核心 处理。1 核心模式下任务串行排队执行,4 核心模式下 4 个任务同时并行处理。观察两种模式下进度条的填充速度和完成时间差异。
多核任务分配动画演示 (runoob)
1 核心(串行处理)
4 核心(并行处理)
多核心在现实中的应用
操作系统如何调度多核
操作系统中的调度器(Scheduler)负责将不同的进程和线程分配到不同的核心上。
它会考虑:
- 负载均衡:尽量让每个核心都忙碌,避免「一核有难、多核围观」
- 缓存亲和性:尽量让同一个线程在同一个核心上运行,利用该核心的私有缓存
- 功耗管理:在负载低时关闭部分核心,节省电量
多核心 vs 超线程
除了物理多核,还有一种技术叫超线程(Hyper-Threading / SMT)。
超线程让一个物理核心看起来像两个「逻辑核心」。当一个线程在等待数据(缓存未命中)时,核心可以切换到另一个线程继续工作。
| 方案 | 硬件成本 | 性能提升 | 适用场景 |
|---|---|---|---|
| 物理多核 | 高(需要复制整套执行单元) | 接近翻倍(理想情况) | 持续高负载任务 |
| 超线程 (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.5x。也就是说 4 核只带来了 2.5 倍加速,效率仅 62.5%。
- 如果一个任务几乎 100% 都可以并行执行,理论上限是多少?为什么现实中还是达不到?
答案:理论上限是无穷大(加速比 = 核心数)。但现实中受限于:(1) 任务拆分和汇总有开销;(2) 核心间同步和通信有延迟;(3) 共享资源(如内存带宽)的竞争。
- 为什么现代服务器 CPU 可以有 64 甚至 128 个核心,但个人电脑通常只有 4~16 个核心?
答案:服务器处理的请求(如网页请求、数据库查询)天然高度并行——每个用户请求相互独立,可并行比例接近 100%。而个人电脑的日常任务(如浏览器渲染、Office 文档处理)并行度有限,太多核心用不上,反而增加功耗和成本。
