🐧 第六章 存储层次
📄 一句话概括

超线程不是【两个核】,是一个核的两份工作证:访存多的负载总吞吐能提升 10%~25%,纯计算几乎不提升——因为瓶颈在执行单元,不在线程数。

📝 本节和前面几节的关系

6.14 把存储层次收完,这一节回到 CPU 里:既然有那么多空闲周期,能不能让它同时干两件事。

一、先分清逻辑核与物理核

os.cpu_count() 给的是【逻辑核】——操作系统看到的数量。 开了超线程之后,一个物理核会报成两个。所以 8 个逻辑核的机器,可能只有 4 个物理核。 这在买机器时最容易搞混:卖家说【8 核 16 线程】,意思是 8 个物理核;而系统信息里写【16 个逻辑处理器】,也可能只有 8 个物理核。 Linux 上看:lscpu 的 Core(s) per socket 与 Thread(s) per core 两行相乘才是真相。

二、为什么能提速

一个线程很少能把执行单元用满:它经常在等内存(6.2 说过,一次内存访问等于几十个周期)。 那几十个周期里,执行单元是闲着的。超线程就是让第二个线程去用这些空闲周期。 所以它共享的是【执行单元与缓存】,各自独有的是【寄存器】——这就是它【便宜】的原因:不用复制整个核,只多一套寄存器。 也是它【提不了太多】的原因:两个线程抢同一份执行单元。

三、能提多少

访存多的负载:单线程只用掉一半左右的执行单元,两个线程各用 55%,总吞吐 110%——提升 10%。 混合负载:两个线程各用 65%,总吞吐 125%——提升 25%。 纯计算:执行单元本来就用满了,两个线程各分 50%,总吞吐还是 100%——一点没多,还让每个线程都慢了一半。 所以判断标准很简单:你的程序是【算得多】还是【等得多】?等得多(访存、IO)→ 超线程有用;算得多 → 加物理核才有用。 这也解释了【8 核 16 线程跑纯计算,比不上 16 个物理核】。

四、附带的一个副作用

两个线程共享 L1/L2,所以它们会互相挤掉对方的缓存行。 结果:单线程性能略微下降(通常几个百分点);两个线程跑同一份数据时更明显。 所以【超线程永远不是纯赚】:它拿单线程性能换多线程吞吐。 对延迟敏感的服务(高频交易、实时音视频),有时会显式关掉超线程——宁可少一半线程,也要稳定。

五、动手 Demo:超线程——一个核塞两个线程,能快多少

这一节先读本机有几个逻辑核,再把超线程的账算成一张表:不同类型的负载,两个线程各能拿多少、总吞吐是多少。

import os
print("逻辑核数", os.cpu_count())

for tag, each, tot in (("访存多", 0.55, 1.10),
                       ("混合负载", 0.65, 1.25),
                       ("纯计算", 0.50, 1.00)):
    print(tag, "两线程各", each, "总吞吐", tot)

实跑输出:

--------------------------------------------------------------------
超线程——一个核塞两个线程,能快多少
--------------------------------------------------------------------
① 先看本机有几个逻辑核

  指标               本机
  -----------------------------------------------------------
  逻辑核数           8
  到底几个物理核     要看 CPU 型号(逻辑核常是物理核的两倍)

  **逻辑核数是【操作系统看到的】数量**,**开了超线程之后,一个物理核会报成两个。**
  **所以 8 个逻辑核的机器,可能只有 4 个物理核。**

② 超线程为什么能提速,又提不了多少

  部件                     一个线程能用满吗     两个线程时
  -----------------------------------------------------------------
  执行单元(算数逻辑)     常常用不满           填进去,吞吐上去了
  寄存器                   每线程一套           要各存一份
  L1/L2 缓存              共享                 可能互相挤

  **超线程的思路**:**一个线程在等内存的时候,执行单元是闲着的**——
  **那就让第二个线程用它。**
  **所以它【不是两个核】,是【一个核的两份工作证】。**

③ 算一笔账:提速多少

  情况                     单线程能跑满吗     两线程各拿     总吞吐
  ------------------------------------------------------------------
  访存多(常等内存)       用不满             55%            110%
  混合负载                 用不满             65%            125%
  纯计算(一直有活干)     用不满             50%            100%

  **访存多的负载能提升 10%~25%;纯计算几乎不提升,还让每个线程都变慢。**
  **因为瓶颈在执行单元,不在线程数**——**超线程只是把空闲的周期填上。**
  **而且【单线程的性能会略微下降】**:**两个线程抢同一份缓存。**

  **所以判断标准很简单**:**你的程序是【算得多】还是【等得多】?**
  **等得多(访存、IO)→ 超线程有用;算得多 → 用处不大,加物理核才行。**

  **这也解释了为什么【8 核 16 线程】的机器跑纯计算任务,比【16 个物理核】慢一截。**

本机 8 个逻辑核——但物理核可能只有 4 个,查 CPU 型号才能确定。 访存多的负载总吞吐 110%,混合 125%,纯计算 100%。 所以【超线程的收益全看你的程序在等什么】——它不是免费的算力,是【把空闲周期捡起来】。 这也解释了一个常见困惑:为什么【核数翻倍,程序没快一倍】。一半原因在阿姆达尔定律(4.16),另一半就在这里。 买机器时的实用判断:跑 Web 服务、数据库(等 IO 多)→ 超线程有用;跑数值计算、视频编码(算得多)→ 看物理核数。

📋 Linux 上分清物理核与逻辑核:lscpu | grep -E 'Core|Thread|Socket'——Thread(s) per core 是 2 就说明开了超线程。

30 秒实验

  1. lscpu | grep -E 'Socket|Core|Thread'(或 Windows 的任务管理器→性能→CPU),算出物理核数。
  2. cat /proc/cpuinfo | grep 'cpu cores' | head -1,另一种读法。
  3. 用 multiprocessing 跑 N 个纯计算任务,N 从 1 加到逻辑核数,看加速比在哪一步停滞。

常见误区

  1. 【逻辑核数就是物理核数】。开了超线程就报成两倍,要看 Thread(s) per core。
  2. 【超线程等于两个核】。它共享执行单元与缓存,纯计算时几乎没有收益。
  3. 【超线程只有好处】。它会让单线程性能略微下降,所以延迟敏感的服务有时会关掉。
自测三问(点开对答案) 1. 怎么分清逻辑核与物理核? —— **看 `Thread(s) per core`**:**等于 2 就说明一个物理核报了俩**;**`Socket × Core(s) per socket` 才是物理核数。** 2. 超线程为什么能提速?又为什么提不了太多? —— **线程等内存时执行单元闲着,让第二个线程去用。****但两个线程抢同一份执行单元,纯计算时就没得抢了。** 3. 什么样的负载适合超线程? —— **等得多(访存、IO)的负载**;**纯计算的负载用处不大,那种要加物理核。**

相关笔记