说一说超线程/同步多线程(HT/SMT)技术那些事儿

x86开启超线程bios bios中的超线程_Hyper-Threading

MebiuW


虽然现在超线程(Hyper-Threading)被大家广泛接受,并把所有一个物理核心上有多个虚拟核心的技术都叫做超线程,但这其实是Intel的一个营销名称。而实际上这一类技术的(学术/技术)通行名称是同步多线程(SMT,Simultaneous Multithreading)技术。SMT技术初衷是通过提升CPU核心后端执行单元的利用率,来提升整体的并行性能。

x86开启超线程bios bios中的超线程_Hyper-Threading_02

Intel的SMT技术是我们认知最广泛的,早在2002年的Pentium 4上(应该是Pentium 4的E)和Xeon上,Intel就把SMT技术包装成Hyper Threading,并推向市场了。之后因为架构切换,在酷睿诞生初期暂停过一段时间,而自从Core i7 960这个划时代的酷睿后,就一直是Intel中高端CPU的标配了。 Intel的超线程一直都是SMT2,也就是一个物理核心虚拟出两个核心,也就是逻辑核心。 AMD最新的Zen系列CPU,也同样加入了SMT2的超线程,现在超线程技术可以说是PC和服务器CPU的标配了。

想和大家分享几个关于超线程的常见问题:

1、SMT为什么能提升并行性能(多线程性能)?

SMT技术虽然是提升多线程下表现的性能,但SMT实际上是提升CPU单核性能大背景下的“副产品”。 一个单核单线程任务,在CPU的视角上,可以被看作是执行一连串连续的指令。比如说是下面这个例子里,假设执行的就是一个包含10个指令的任务:

任务1 = A1 A2 B1 C1 D1 A3 A4 D2 C2 B2

我们先看最简单的场景,CPU执行这些任务最直接的办法就是一个指令一个指令的执行,假设这些指令的执行周期都是N,那么执行完这些指令,就需要10N个周期。那么CPU的速度就只和CPU的频率相关了,显然这也不符合我们的认知,不同CPU在相同频率下的性能显然不一样。

除了频率以外,要提升CPU的单核性能,第一个常见手段就是尽可能的缩短每个指令执行的周期,不过在我们假设的这个场景中和SMT关系不大,这里就不说了。第二个常见手段就是指令级并行(ILP)。虽然说这个任务1理论上是得一个指令接着一个指令的执行,但实际上这些指令并一定只能这么顺序执行,只要两个指令之间没有相互依赖和冲突,那么就可以并发执行(一起执行),从而缩短总的执行时间。 例如在上面这个例子中,我将指令分组成A B C D四组,组内必须顺序执行,组间的指令完全没有依赖(彼此不依赖对方执行后的数据)和冲突(不共享资源,不是一类操作),那么我们就可以并发执行这些指令。

任务1A:A1 A2 A3 A4
任务1B:B1 B2
任务1C:C1 C2
任务1D:D1 D2

那么我们还是假设每个任务的执行周期是N,可以看到只要我们按照上述分组执行代码,那么整体的执行时间就只取决于最长的一组任务1A,也就是执行时间可以缩短到4N,速度提升到2.5倍。

显然,如果说要在一个CPU核心里同时执行上述几组任务,那么CPU自然得具备至少4组执行端口,这里我们也简化成PA、PB、PC和PD,分别执行上述的1A 1B 1C 1D。所以现代的CPU要提升单核性能,都会尽可能的把后端的执行端口数目变多,并且尽可能的在单位时间内读入更多指令,从而促进指令间的并发。

但是,实际的任务里,指令之间的依赖冲突关系是错综复杂的,不可能完美的将指令均匀的分组到每一个端口上。 就比如说我们上面这个例子里,在并发执行后虽然时间缩短为了4N个周期,但是实际上只有端口PA是一直在工作的,而PB PC PD都会在中途闲下来。

x86开启超线程bios bios中的超线程_同时多线程_03

以Skylake为例子,后端执行端口EU不只一个

随着单核性能的不断提升,后端执行资源也越来越丰富,这种执行端口闲置的情况就会越来越明显,造成资源浪费。这时候,为了将这些资源物尽其用,同步多线程SMT就应运而生了。SMT的思路是这样的,既然一个任务填不满后端的资源,那么我们就找不只一个任务来填就好了,不同任务之间的相互依赖和冲突情况很低,放到一起来执行正合适去填满后端资源。

我们接着举例子,假设现在有一个新的任务2,同样是10个指令,同样按照端口分组:

任务2 = B‘1 A’1 B‘2 C’1 C‘2 D'1 D’2 D‘3 C’3 A‘2
任务2A = A’1 A’2
任务2B = B’1 B’2
任务2C = C’1 C’2 C’3
任务2D = D'1 D’2 D‘3

那么在指令级并发的情况下,这个任务的执行时间就是3N。

x86开启超线程bios bios中的超线程_x86开启超线程bios_04

那么如果把任务1和任务2在单核CPU上分别执行,它们的执行时间就是4N+3N=7N。这时,如果我们引入SMT技术,虚拟出两个核心来,让他们同样在一个核心内执行,依然使用ABCD来表示互相不冲突的指令:

PA:A1 A2 A3 A4 A’1 A’2
PB:B1 B2 B’1 B’2
PC:C1 C2 C’1 C’2 C’3
PD:D1 D2 D'1 D’2 D‘3

那么这时候整个执行时间就变成了6N,比起之前的7N又提升16.7%的速度。这就是SMT技术为什么能提升多任务并行效率的原因。通过引入SMT,可以更为有效的利用后端的资源。在我们这个例子中,两个任务并发后,只有PB端口空闲了1N个周期。另外SMT技术虚拟出来的核心不一定是2,例如在IBM的Power,Oracle的SPARC,上就SMT8了,我们可以通过引入更多的SMT虚拟核心,来进一步压榨利用率,只不过随着SMT虚拟的核心增多,提升的比率是在下降的。例如在我们上面这个例子中,在6N个周期内,PB只空出了2个周期,PC PD只空了1个周期,这时候再引入一个SMT核心,提升并不会很大了。

x86开启超线程bios bios中的超线程_x86开启超线程bios_05

当然实际的指令执行情况远比我给的例子复杂,每种指令的执行周期不一样,指令执行中间可能会有IO等待,执行暂停等情况,但是万变不离其宗,SMT的思想就是用不同的任务尽可能填满后端资源,懂了这个例子再考虑这些因素也不难。

2、相比物理多核心,SMT有什么优势?

x86开启超线程bios bios中的超线程_超线程_06

SMT2只需要在前端有两组维护不同任务上下文的单元就可以

增加物理核心,以及加入SMT都是提升多任务性能的方式。但为什么现在核心那么多了,还依旧会有SMT? 因为相比于增加物理核心,使用SMT来的更加廉价。因为SMT提供的是虚拟核心,所有虚拟核心共享很大一部分的资源,通常加入SMT技术只需要在前端额外增加一部分资源(毕竟两个任务是两个上下文)就可以。例如Intel曾经就披露过,奔腾4增加HT技术只需要多花费5%的核心面积,就可以增加15-30%的多线程性能,而如果增加物理核心,增加多少性能,就至少要增加多少比例的核心数量,性价比显然不如SMT。

3、SMT有什么代价?

天下没有免费的午餐,SMT技术带来多线程性能进步的同时,势必也会引入一些负面的影响。大体有如下一些:

  1. 多线程维护开销:我们上面的例子中比较理想的展示了SMT的效果,但也没展现出SMT的一些代价。一个物理核心如果引入多个线程,那么是要协调、隔离多个线程的,这会引入额外的开销。所以最理想的情况下,如果一个核心有两个线程,那么两个线程的总执行时间会更快,但是如果细分到每一个线程的执行时间,会比分别执行来的慢一些。
  2. 资源冲突:此外,在SMT核心中,因为多个逻辑核心会共享很多资源,如果两个线程的性质比较接近,总是在使用类似的资源,那么它们会遭遇资源冲突。程度轻一点的情况下,互相等待一下就好,多牺牲一点单线程性能,还能保证多线程效率。而差一点的情况就是资源冲突反而导致性能下降,最典型的冲突就是缓存的冲突,一个线程可以用100%的缓存,而超过一个线程使用同一个缓存,可用缓存就不是100%,会导致大量开销极大的缓存-内存换入换出。只要有一个线程是非常吃缓存的,那么加入SMT不但不会提升总的执行效率,反而会降低整体的效率。SMT非常忌讳不同线程的资源冲突,一但冲突SMT就很容易引入反面效果。比如在很多云服务器、HPC服务器上,SMT通常是关闭的,就是因为资源冲突。
  3. 线程安全问题:两个线程在同一个核心内执行,是需要严格隔离它们的上下文的,线程A不能访问修改其他线程的资源。线程隔离是一个非常复杂和繁琐的过程,如果隔离不彻底,那么会导致执行错误、以及隐私泄漏的问题。Intel前两年Skylake爆发的若干安全漏洞,就是因为线程隔离不到位造成的。
  4. 导致功耗增加:SMT整体的思路是略微牺牲单核性能/能耗比,换取大多数情况下的多线程时的单核性能和能耗比,那么对应的加入SMT后单核的能耗比会有些许倒退。由于引入SMT会导致核心设计更加复杂,静态功耗、漏电会更难控制,这对于移动设备是致命的。这也是为什么SMT在PC和服务器上大行其道这么多年,手机上几乎看不到的原因。

4、为什么有的核心没有SMT,有的有SMT2,有的有SMT4等?

相信到这里,大家已经明白,SMT是有利有弊的,SMT不是绝对的好,也不是绝对的坏,只有最合适的SMT配置。如果一个核心的后端资源空闲并不多,或者通常的多任务场景任务间冲突比较大,那么SMT就不应该配置或者应该少配置。比如Intel的Atom线最开始在非乱序核心中是有HT的,后面因为引入了乱序执行后,后端已经被填的很慢了,引入SMT的开销会大于其收益,自Silvermont后就没有SMT了。比如说在我们桌面环境下,其实SMT2已经差不多够用了(架构层面&任务层面),再多的SMT只会反向引入开销。

因为现在的多核大战,无论是PC还是手机,多线程性能绝大部分时候都是多余的,除了跑分和极端应用,很少能用到多核性能,要指望它们做更多的虚拟核心,估计近些年是看不到的。

5、你需要关闭SMT吗?

这应该是一个经常被讨论的问题了,我个人的建议是,对于一般消费者,除非你追求极致的单核表现,不然没必要关闭SMT,多数时候打开SMT并不会带来负面的影响。

现代的操作系统在调度上都考虑到了SMT的特性,比如你有8个核心16线程,那么在多任务要求不是很大的情况下,操作系统会尽量避免让一个核心同时执行两个线程。那么那些单核倒退、资源冲突的情况就基本不会发生了。而当多任务要求很高的时候,操作系统让一个核心跑多个线程,这时候虽然可能会发生冲突导致效率反向降低,但是更多的时候是增加效率,所以从期望上来说,是没必要的。

如果追求极致性能且自己确切不需要那么多多线程性能,自然可以关掉。反正也用不到那么多线程,享受不到SMT的好处,还不如索性关了,杜绝一切可能的开销,也更容易超频了。所以其实核心越多的,越适合关闭SMT,至于还在用4核心之类的就别关了。

6、如何辨别哪个核心是虚拟核心,哪个核心是物理核心?

很多人经常问,在Windows的任务管理器里,到底哪个核心是真的,哪个是虚拟的? 这样可以设置任务相关性,绑定核心。

但其实没有真假之别,一个核心的两个逻辑核心情况下,两个逻辑核心是完全对等的,没有真假之分。无论你使用A逻辑核心还是B逻辑核心都一样,无论你绑定哪个逻辑核心,只要不同时用,那么哪个逻辑核心都约等于是完整占用的物理核心。