2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > 计算机组成原理:超线程与单指令多数据流

计算机组成原理:超线程与单指令多数据流

时间:2019-12-21 09:46:39

相关推荐

计算机组成原理:超线程与单指令多数据流

超线程

想要提升CPU的性能,无论是流水线架构、分支预测、还是乱序执行,其本质上都是一种指令级并行技术,也就是想同一时间执行多条指令,来提升CPU的吞吐率

但是这些方法可能会因为流水线太深而起不到效果。因为更深的流水线里面的指令就更多,相互的依赖关系就更多。于是,很多时候不得不把流水线停顿下来,插入很多NOP操作,来解决这些依赖带来的“冒险问题”。

但是有更好的方法,那就是超线程技术。

那么什么是超线程技术?

既然CPU同时运行那些在代码层面有前后依赖关系的指令,会遇到各种冒险问题,我们不如去找一些和这些指令完全独立,没有依赖关系的指令来运行就好了。那么,这样的指令哪里来呢?自然同时运行在另一个程序里了现代计算机在同一个时间里可以运行很多个程序。各个程序之间,是完全相互独立的。它们的指令完全并行运行,而不会产生依赖问题带来的“冒险”

然而这个时候,你可能就会觉得奇怪了,这么做似乎不需要什么新技术呀。现在我们用的CPU 都是多核的,本来就可以用多个不同的 CPU 核心,去运行不同的任务。即使当时的Pentium 4 是单核的,我们的计算机本来也能同时运行多个进程,或者多个线程。这个超线程技术有什么特别的用处呢?

无论是上面说的多个CPU核心运行不同的程序,还是在单个CPU核心里面切换运行不同线程的程序,在同一时间点上,一个物理的CPU核心只会运行一个线程的指令,所以其他我们并没有真正做到指令的并行运行。而超线程不是这样。超线程的CPU,其实是把一个物理层面CPU核心,“伪装”成两个逻辑层面的CPU核心。这个CPU,会在硬件层面增加很多电路,使得我们可以在一个CPU核心内部,维护两个不同线程的指令的状态信息 比如,一个物理CPU核心内部,会有双份的PC寄存器、指令寄存器、条件寄存器。这样,这个CPU核心就可以维护两条并行的指令的状态。在外面看来,似乎有两个逻辑层面的CPU在同时运行。所以,超线程技术一般也被叫作同时多线程(Simultaneous Multi-Threading,简称 SMT)技术不过,在CPU的其他功能组件上却没有提供双份。无论是指令译码器还是ALU,一个CPU核心仍然只有一份。因为超线程并不是真的同时运行两个指令,那就真的变成物理多核了。超线程的目的,是在一个线程A的指令,在流水线里停顿的时候,让另外一个线程去执行指令。因为这个时候,CPU的译码器和ALU就空出来了,那么另外一个线程B,就可以拿来干自己的事情。这个线程B里面没有对线程A里面指令的关联和依赖这样,CPU 通过很小的代价,就能实现“同时”运行多个线程的效果。通常我们只要在CPU 核心的添加 10% 左右的逻辑功能,增加可以忽略不计的晶体管数量,就能做到这一点。

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

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

如果你用的是 Windows,可以去下载安装一

个CPU-Z来看看你手头的 CPU 里面对应的参数。

CPU 的Cores,被标明了是 4,而 Threads,则是 8。这说明这个 CPU,只有 4 个物理的 CPU 核心,也就是所谓的 4 核 CPU。但是在逻辑层面,它“装作”有 8 个 CPU 核心,可以利用超线程技术,来同时运行 8 条指令。

SIMD:如何加速矩阵乘法

从上面CPU的信息图里面,可以看到,中间有一组信息叫做Instructions,里面写了MMX、SSE 等等。这些信息就是这个CPU所支持的指令集。这里的MMX和SSE的指令集,也就引出了一个提升 CPU 性能的技术方案,SIMD,中文叫作单指令多数据流(Single Instruction Multiple Data)。

我们先来体会一下 SIMD 的性能到底怎么样。下面是两段示例程序,一段呢,是通过循环的方式,给一个 list 里面的每一个数加 1。另一段呢,是实现相同的功能,但是直接调用NumPy 这个库的 add 方法。在统计两段程序的性能的时候,我直接调用了 Python 里面的 timeit 的库。

$ python>>> import numpy as np>>> import timeit>>> a = list(range(1000))>>> b = np.array(range(1000))>>> timeit.timeit("[i + 1 for i in a]", setup="from __main__ import a", number=1000000)32.82800309999993>>> timeit.timeit("np.add(1, b)", setup="from __main__ import np, b", number=1000000)0.9787889999997788>>>

从两段程序的输出结果来看,你会发现,两个功能相同的代码性能有着巨大的差异,足足差出了 30 多倍。也难怪所有用 Python 讲解数据科学的教程里,往往在一开始就告诉你不要使用循环,而要把所有的计算都向量化(Vectorize)。

为什么他们之间的差异这么大呢?原因是,NumPy直接用到了SIMD指令,能够并行进行向量的操作。

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

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

就以我们上面的程序为例,数组里面的每一项都是一个 integer,也就是需要 4 Bytes 的内存空间。Intel 在引入 SSE 指令集的时候,在 CPU 里面添上了 8 个 128 Bits 的寄存器。

128 Bits 也就是 16 Bytes ,也就是说,一个寄存器一次性可以加载 4 个整数。比起循环分别读取 4 次对应的数据,时间就省下来了。

在数据读取到了之后,在指令的执行层面,SIMD 也是可以并行进行的。4 个整数各自加1,互相之前完全没有依赖,也就没有冒险问题需要处理。只要 CPU 里有足够多的功能单元,能够同时进行这些计算,这个加法就是 4 路同时并行的,自然也省下了时间。

所以,对于那些在计算层面存在大量“数据并行”(Data Parallelism)的计算中,使用SIMD 是一个很划算的办法。在这个大量的“数据并行”,其实通常就是实践当中的向量运算或者矩阵运算。在实际的程序开发过程中,过去通常是在进行图片、视频、音频的处理。最近几年则通常是在进行各种机器学习算法的计算。

而基于 SIMD 的向量计算指令,也正是在 Intel 发布 Pentium 处理器的时候,被引入的指令集。当时的指令集叫作MMX,也就是 Matrix Math eXtensions 的缩写,中文名字就是矩阵数学扩展。而 Pentium 处理器,也是 CPU 第一次有能力进行多媒体处理。这也正是拜 SIMD 和 MMX 所赐。

总结

超线程,其实是一个“线程级并行”的解决方案。它通过让一个物理CPU核心,“装作”两个逻辑层面的CPU核心,使得CPU可以同时运行两个不同线程的指令。虽然,这样的运行仍然有着种种限制,很多场景下超线程并不一定能够带来CPU的性能提升。但是同样的 4 核心的 CPU,在有些情况下能够发挥出 8 核心CPU 的作用。而超线程在今天,也已经成为 Intel CPU 的标配了SIMD技术,则是一种“指令级并行”/"数据并行"的加速方案。在处理向量计算的情况下,同一个向量的不同维度之间的计算是相互独立的。而我们的CPU里的寄存器,又能够放得下多条数据。于是,我们可以一次性取出多条数据,交给CPU并行运算。正是 SIMD 技术的出现,使得我们在 Pentium 时代的个人 PC,开始有了多媒体运算的能力。可以说,Intel 的 MMX、SSE 指令集,和微软的 Windows 95 这样的图形界面操作系统,推动了 PC 快速进入家庭的历史进程。

终于知道为什么挖矿烧显卡啦~

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。