独占线程太多怎么办_【X86处理器功能详解】 英特尔 超线程技术

超线程技术允许CPU执行多个控制流,将物理核心虚拟为逻辑核心,提高处理器效率。通过乱序执行、分支预测等机制减少流水线堵塞,多线程可以更好地利用资源。然而,超线程也可能导致流水线争用和缓存冲突,影响性能。在实际应用中,应结合性能测试和监控工具来决定是否开启超线程,以实现最佳性能。
摘要由CSDN通过智能技术生成

793aa94330e8b5985c5a4086d6721184.png


今天分享一下超线程技术,以及在现实工作负载中为了达到最大性能,如何选择是否开启HT。

超线程是什么?

超线程(Hyper Threading),是允许一个CPU执行多个控制流的技术,就是将一颗具有超线程功能的物理CPU核心变成两颗逻辑CPU核心,而逻辑CPU核心对操作系统来说,跟物理CPU核心并没有什么区别。因此,操作系统会把工作线程分派给这两颗(逻辑)CPU核心上去执行,让(多个或单个)应用程序的多个线程,能够同时在同一颗CPU上被执行。Intel的Hyper Threading (HT)技术就是多线程的一种形式。HT技术提升了处理器的效率,因为其允许在给定的时间内并行允许更多的任务。

此时两颗逻辑CPU核心共享这一颗物理CPU核心的所有执行资源,使用同一条流水线、同一套数据寄存器,但是每个线程各自对应一套上下文状态寄存器,然后利用不同的交织力度同时运行多个线程指令的设计。可以理解为超线程技术就是对CPU物理核心的虚拟化。

现代处理器都包含许多的执行单元,以Intel处理器为例,有许多EU(Execution Units),每一个时钟周期都可以派发许多的微指令。有些执行单元非常的高效,比如AVX-512的EU开源在每个时钟周期执行32位浮点数的操作,如果不把这些功能强大的执行单元用起来简直是太亏了。要让计算能力都用起来,其中一个很好的方法就是使用多线程,同时开启多条流水线,尽可能多的让执行单元都忙起来。


但是开启多条流水线也引入了新的问题,有很多常见的原因会阻塞流水线:

读后写(Write-after-read,WAR):前面的指令需要读取一个结果,后面的指令需要写进度需要读取的结果。

写后读(Read-after-write,RAW):对于某个寄存器,前面的指令要将结果写进去,后面的指令需要读取它。

写后写(Write-after-write,WAW):前面的指令需要向某个寄存器写入一个结果,后续的指令也需要向同一个寄存器写入结果。

内存访问:访存的时钟周期比访问寄存器或Cache的时钟周期多多了。

多时钟周期指令:有些指令执行需要比较多的时钟周期,比如除法指令。

分支:程序有多个条件判断(分支),或者跳转。

资源争用:比如不同地址数据由于映射到同一个Cache line,对Cacheline的争用。


由于上面这些原因,让提升CPU的效率 更加复杂,所以也有一些方法来避免流水线堵塞:

寄存器别名:用来解决读后写(WAR),写后写(WAW)数据依赖,WAR和WAW相关可以在重命名了相关寄存器后任意排序。

乱序执行:将后续指令超越之前指令先执行从而降低流水线部件闲置的指令调度方式。

前递(Forwarding):有些真正的数据依赖无法避免,比如将数据写回内存的指令发生在数据计算完成,怎么办?这时候可以在Load a(访存地址)指令的写回这一步的控制信号把数据寄存器的WE信号封闭,从而做好到保持数据寄存器的上一个状态不变从而解决数据依赖。

缓存和预取:利用缓存的时间一致性和空间一致性的特性,充分利用缓存来避免访存等带来的流水线停顿。以Intel CPU为例,如果各级Cachemiss,访存时间大概在60-80ns之间,而L1 cache基本1ns就可以做到(大约3-4个时钟周期),L2Cache基本3ns左右(大约10个时钟周期),L3共享缓存长一些,大概在15ns左右(大约40个时钟周期)。完全可以充分利用数据和指令预取来提前把数据和指令准备好。

分支预测:举个例子,当程序遇到跳转指令的时候,这时候一般来说没有执行到这里,后续程序并不知道此时是否真正需要跳转。跳转指令是两种可能,那可以猜一下啊,猜测正确就可以不堵塞流水线,猜测错误的代价也不大。所以遇到条件跳转指令,可以设计为直接把排在条件跳转后面的指令进行译码执行,此时就是预测不会跳转,流水线不停顿;如果程序的执行结果是跳转,此时将错误进入流水线指令的执行结果清除直接执行后续跳转的目标地址的就可以了。

多线程的基本思想是一次运行多个线程。如果一个线程遇到暂停,则可以执行另一个线程。通过增加从中挑选指令的线程池,就更有可能保持CPU的使用率,而更高的利用率意味着更高的效率。

6ff81203ab05b23da5a064e76b659976.png

在单个核心中保留超线程的原因是因为单个线程无法100%利用流水线,比如当有大范围连续发生的RAW相关时,或者缓存不命中导致花费大量时间去访问RAW时,会导致流水线大范围空泡,此时如果有另一个线路中的指令能够顶上来占用这些时隙,就可以充分利用资源。超线程既充分利用了流水线资源,又能让多线程以更好的时间窗并行执行,提升用户体验。

现代CPU允许多个新CPU同时运行多个线程,其中有一般数量的线程并行地不是那么充分,因为他们使用的是公用设施。可以用特殊指令来关闭超线程,这样CPU内部就不会使能其他线程的状态寄存器了,只能保留一套。


HT在BIOS中的设置:

以Intel DSG 服务器为例,超线程的开关在BIOS菜单中显示如下:

da01093d8470fc5f779b742764d0887b.png


为什么有些性能调优中需要关闭超线程?

超线程无疑是提升效率的一种方法,但是在实际过程中也要结合客户的实际应用来考虑是否要开启超线程应用。原因之一是因为超线程对流水线上的执行部件都是多线程争用的。现在实际的产品中普遍都在核心中保留了超线程,比如Intel X86 CPU每个核心支持2个超线程。每个逻辑CPU每时每刻可以执行一个线程,如果这两个逻辑CPU所执行的两个线程会共用流水线。

有些实际做性能测试的小伙伴会在某些应用上关闭超线程,那么既然超线程能提高线程数,从而提升并行粒度,那为什么要关闭超线程呢?

主要是在现代CPU和现代的多数程序场景下,超线程对性能的提升不一定很大,因为现代CPU在乱序执行、分支预测等方面的设计优化几乎做到了极致,流水线空闲的概率较低,在某些场景下多个线程就不是共同利用流水线了,而是共同争抢流水线,反而可能产生一些开销,比如第一个线程运行得好好的,没有空泡,结果非要调度第二个线程乱入第一个线程,这样的话,配套的分支预测、乱序执行顺序提交等模块就要切换各自的上下文状态到第二个线程。为什么非要调度第二个线程呢?如果CPU看到第一个线程充分利用了流水线,就让它一直执行下去不就好了么?不可以,那此时其他线程就被饿死了,永远得不到执行。既然选择了超线程,就会有线程被调度上去,又不让执行,最后那些线程就会卡住,影响用户体验。

还有一种可能是缓存争用带来的性能损失,当然这都可以通过性能监控工具来查看程序热点,查看瓶颈在哪里。比如现在的设计是同一个物理核心上的逻辑核心是共享L1、L2缓存(也叫私有缓存)的,不同物理核心上的逻辑核心是共享L3缓存的,在设计上L3缓存挂接到一个共享总线供所有的核心存取。

理论上所有的核心都可以公平的使用Cache,通常情况下如果一个线程不使用太多内存,那么让另一个线程使用整个缓存是合理的。但是,如果两个线程都大量使用缓存,而且通过缓存映射的方式正好需要映射到同一个缓存行,它们会不断地相互逐出,从而减慢系统的运行速度。这种缓存争用降低了效率,甚至可能消除引入超线程带来的好处。

以上底层发生的事件是对客户不透明的,对客户的建议就是,对于客户的工作负载,默认开始超线程,如果性能到不了预期或者期望能优化,建议使用Vtune等工具查看性能瓶颈,客观评估是否需要打开超线程。当然编译器也有一些编译选项可以帮助自动优化,也有一些基本的调优建议帮助提升实际性能,比如Cache对齐(比如将多个线程所操作的变量数据分散到多个不会引发冲突的Cache行存放),减少分支等(例减少if语句),程序员也可以手动加入一些编译制导语句让其代码对硬件执行更加友好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值