超线程

概述:把一个物理层面 CPU 核心,“伪装”成两个逻辑层面的 CPU 核心。这个 CPU,会在硬件层面增加很多电路,使得我们可以在一个 CPU 核心内部,维护两个不同线程的指令的状态信息。

比如,在一个物理 CPU 核心内部,会有双份的 PC 寄存器、指令寄存器乃至条件码寄存器。这样,这个 CPU 核心就可以维护两条并行的指令的状态。在外面看起来,似乎有两个逻辑层面的 CPU 在同时运行。所以,超线程技术一般也被叫作同时多线程(Simultaneous Multi-Threading,简称 SMT)技术。96aa1220ff27776f55091c55c2eddbc8.webp 不过,在 CPU 的其他功能组件上,Intel 可不会提供双份。无论是指令译码器还是 ALU,一个 CPU 核心仍然只有一份。因为超线程并不是真的去同时运行两个指令,那就真的变成物理多核了。超线程的目的,是在一个线程 A 的指令,在流水线里停顿的时候,让另外一个线程去执行指令。因为这个时候,CPU 的译码器和 ALU 就空出来了,那么另外一个线程 B,就可以拿来干自己需要的事情。线程 B 没有对于线程 A 里面指令的关联和依赖。

超线程只在特定的应用场景下效果比较好。一般是在那些各个线程“等待”时间比较长的应用场景下。比如,应对很多请求的数据库应用,就很适合使用超线程。各个指令都要等待访问内存数据,但是并不需要做太多计算。

于是,我们就可以利用好超线程。我们的 CPU 计算并没有跑满,但是往往当前的指令要停顿在流水线上,等待内存里面的数据返回。这个时候,让 CPU 里的各个功能单元,去处理另外一个数据库连接的查询请求就是一个很好的应用案例。

SIMD 加速矩阵乘法

SIMD,中文叫作单指令多数据流(Single Instruction Multiple Data)。
image.png
中间有一组信息叫作 Instructions,里面写了有 MMX、SSE 等等。这些信息就是这个 CPU 所支持的指令集,也就是上面提到的单指令多数据中的 instruction。

下面是两段示例程序,一段呢,是通过循环的方式,给一个 list 里面的每一个数加 1。另一段,是实现相同的功能,但是直接调用 NumPy 这个库的 add 方法。

  1. $ python
  2. >>> import numpy as np
  3. >>> import timeit
  4. >>> a = list(range(1000))
  5. >>> b = np.array(range(1000))
  6. >>> timeit.timeit("[i + 1 for i in a]", setup="from __main__ import a", number=1000000)
  7. 32.82800309999993
  8. >>> timeit.timeit("np.add(1, b)", setup="from __main__ import np, b", number=1000000)
  9. 0.9787889999997788
  10. >>>

前面使用循环来一步一步计算的算法,一般被称为 SISD,也就是单指令单数据(Single Instruction Single Data)的处理方式。多核 CPU 呢同时处理多个指令的方式可以叫作 MIMD,也就是多指令多数据(Multiple Instruction Multiple Data)。

为什么 SIMD 指令能快那么多呢?这是因为,SIMD 在获取数据和执行指令的时候,都做到了并行。一方面,在从内存里面读取数据的时候,SIMD 是一次性读取多个数据。

Intel 在引入 SSE 指令集的时候,在 CPU 里面添上了 8 个 128 Bits 的寄存器。128 Bits 也就是 16 Bytes ,也就是说,一个寄存器一次性可以加载 4 个整数。比起循环分别读取 4 次对应的数据,时间就省下来了。image.png
在数据读取到了之后,在指令的执行层面,SIMD 也是可以并行进行的。4 个整数各自加 1,互相之前完全没有依赖,也就没有冒险问题需要处理。只要 CPU 里有足够多的功能单元,能够同时进行这些计算,这个加法就是 4 路同时并行的,自然也省下了时间。

总结

超线程,其实是一个“线程级并行”的解决方案。它通过让一个物理 CPU 核心,“装作”两个逻辑层面的 CPU 核心,使得 CPU 可以同时运行两个不同线程的指令。虽然,这样的运行仍然有着种种的限制,很多场景下超线程并不一定能带来 CPU 的性能提升。但是 Intel 通过超线程,让使用者有了“占到便宜”的感觉。同样的 4 核心的 CPU,在有些情况下能够发挥出 8 核心 CPU 的作用。而超线程在今天,也已经成为 Intel CPU 的标配了。

而 SIMD 技术,则是一种“指令级并行”的加速方案,或者我们可以说,它是一种“数据并行”的加速方案。在处理向量计算的情况下,同一个向量的不同维度之间的计算是相互独立的。而我们的 CPU 里的寄存器,又能放得下多条数据。于是,我们可以一次性取出多条数据,交给 CPU 并行计算。

扩展阅读

指令级并行是一种隐式并行,也就是写程序的人不需要关注,通过流水线和超标量,使得一个程序的指令序列中有多条同时乱序运行,顺序提交。这依赖寄存器重命名,多个执行单元,重排序缓冲和指令预测技术。

线程级并行时一种显式并行,也就是程序员要写多线程程序。线程级并行主要指同时多线程(SMT)/超线程(HT)以及多核和多处理器。SMT 是在指令级并行的基础上的扩展,可以在一个核上运行多个线程,多个线程共享执行单元,以便提高部件的利用率,提高吞吐量。SMT 需要为每个线程单独保持状态,如程序计数器(PC),寄存器堆,重排序缓冲等。

数据级并行是一种显式并行,主要指单指令多数据(SIMD),比如 a,b 和 c 都是相同大小的数组,要进行的计算是 a 的每一个元素与 b 的响应元素进行运算,结果放入 c 的对应元素中。如果没有 SIMD,就需要写一个循环执行多遍来完成,而 SIMD 中 一条指令就可以并行地执行运算。