这是以前研究生的时候写的一篇文章
今天看了一篇文章关于cpu乱序执行的讲解,主题思想是cpu能并行的处理指令,这里的并行不是多核并行的处理,而是在某种情况下,上下2条指令可以被一个核一起送行,还有可能在下面的指令先运行,称为乱序执行,out of order,这也带来了超标量,1个时钟周期可以运行大于1条指令。这在以前是不可想象的,在理想情况下,一个时钟最多执行1条,但是创新是无界限的。
一个简单的实验
过程为对eax执行3次加法,循环差不多4G次,差一点点,无关紧要,产生执行文件反汇编如下
循环内部一共五条指令,4g次循环,就是20g个指令需要执行,使用time 计算运行时间10次测试值为
7.12 7.23 7.24 7.07 7.14 7.17 7.21 7.30 7.16 6.99这里是user时间,用户态时间,忽略其他函数包括main之前的函数执行时间
就是在这20g次得指令执行上花了大约7s时间,本机的配置为酷睿的2.1Ghz,7s的时间最多也只能有14G的周期,按照一般理论,执行不了20g次指令,这还是在每个指令都是单周期的理想情况下
这就是乱序执行带来的超标量性能。
对于此现象的稍微详细一点的解释可以是这样,
5条指令,每一条都是对cpu状态机的一个改变,对于上下没有依赖的指令可以一起执行
比如
这2条可以并行运行,但是
这2条就不可以并行运行,对于这5条指令可以理解为这样的一个过程
每一行代表一个机器周期,对eax的三次相加,必须等到前一次运算完成以后才能进行下一次的相加,但是对于edx的减法就可以和第一次eax的加法一起运行,其实edx就是c代码的那个i,这里就是乱序的体现,因为edx的减法在后面,但是先于2个eax的加法运行,没有按照固定顺序。但是不管怎么乱序,指令执行完以后的结果需要和顺序的一样。jne 80483a8这个指令要等到对edx减法完成是才能运行,因为要根据减法运算的结果的判断。
所以这5条指令 4G次循环其实是3个周期 4G次循环为12g个周期,7s的时间2GHZ 处理器可以有14G个周期。所以加上main之前的库代码为2G个周期,就差不多7s 的运行时间。
以下是对代码的另一种实验。对eax的3次加法改成分别对eax ebx ecx的3次加法,这个看上去是差不多,都是对寄存器加法运算,但是对于现代的处理器有很大的差别。
c代码改为
对于的a.out执行文件反汇编为
循环还是5条指令和原来的一样,只是原来是eax3次加法变成现在的eax ebx ecx的三次加法,测试的运行时间为
4.31 4.48 4.28 4.33 4.28 4.25 4.19 4.34 4.34 4.33
这是绝对的减少,这就很奇怪,只是对寄存器不一样,按照上面的方法可以画一下cpu运算数据流向图
因为对eax ebx ecx 3条指令没有上下依赖关系,目前intel处理器每个核有三个独立的加法器(可以测试具体有几个加法器),所以其实这3条指令是一起运行的,估计cpu数据流,一次循环其实只需要2个周期就可以了,4G次循环只需要8G个周期,对于一个2GHZ的处理器来说时间也刚好是4s差不多,和测试结果完全匹配。这就是cpu的乱序执行的能力。对于性能优化,在c的体现中,在循环中,在连续的几次运算中数据没有依赖性是重点。把运算过程的中间结果都放在同一个变量中会比较慢。
就算再增加一个加法运算把原来的3次改为4次,
运算时间还是和上一次是一样的。
因为cpu数据流图
在一个循环的运行周期上还是一样的,4.22 4.44 4.31 4.30 4.23 4.21 4.22 4.24 4.22 4.23
如果增加到5个的话,数据流回变成
在2个时钟周期中三个加法器会满负载运行,这样就会影响到jne 80483a8的运算,
其实这条指令也是需要加法器支持的,从上面可以看到这条指令的机器码是 75 f2效果是向前跳12个偏移,这是一个相对偏移跳转指令,运算过程为下一条指令地址0xbc+2=0xbe 加上0xf2
加上f2就是减14 等于0xb0,所以需要加法器计算最终的地址。但是加法器在前面已经满负荷运行,所以势必会增加每个循环的周期数。测试结果为
5.17 5.52 5.05 5.31 5.09 5.15 5.30 5.31 5.39 5.04
明显的比前面的运算时间要增加。