超标量

最近在调一个性能问题,过程中惊讶的发现,cpu竟然能够在一个时钟周期里执行多条指令。

仔细google后发现还是我土了,这是cpu早就支持的一项技术,叫做superscalar,超标量。其实上学时计算机组成原理就翻来覆去的说这个概念,我一直理解为,超标量就是一条指令可以操作多条数据,进而达到一个时钟周期内达到几条指令的效果,而这其实是SIMD所实现的功能。

写了个测试程序仔细研究了下。一个for循环在编译器不开优化选项的情况下,大致翻译成六条指令
[code]
for(unsigned i=0; i<0xffffffff; ++i);
00FC1040 mov dword ptr [i],0
00FC1047 jmp test_cpu+42h (0FC1052h)
00FC1049 mov eax,dword ptr [i]
00FC104C add eax,1
00FC104F mov dword ptr [i],eax
00FC1052 cmp dword ptr [i],0FFFFFFFFh
00FC1056 jae test_cpu+4Ah (0FC105Ah)
00FC1058 jmp test_cpu+39h (0FC1049h)
[/code]
在我的机器,core i5-2400 @ 3.1GHz的cpu上,这个循环跑了8496ms,每秒钟执行的指令条数为0xffffffff*6/8.496=3033168993.644068,平均1.022个时钟周期执行一条指令,事实上还达不到,因为intel的core系列cpu支持Turbo Boost技术,会动态条整cpu的频率,i5-2400可以达到3.4 GHz,一般来讲,差不多也就是1.2个时钟周期执行一条指令。

如果for循环里做几个操作,看下面代码
[c]
int a=0,b=0,c=0;
for(unsigned i=0; i<0xffffffff; ++i){
++a;
++b;
++c;
}
[/c]
翻译成下面的机器代码
[code]
int a=0,b=0,c=0;
0104A0E0 mov dword ptr [a],0
0104A0E7 mov dword ptr [b],0
0104A0EE mov dword ptr [c],0
for(unsigned i=0; i<0xffffffff; ++i){
0104A0F5 mov dword ptr [i],0
0104A0FC jmp test_cpu+57h (104A107h)
0104A0FE mov eax,dword ptr [i]
0104A101 add eax,1
0104A104 mov dword ptr [i],eax
0104A107 cmp dword ptr [i],0FFFFFFFFh
0104A10B jae test_cpu+7Ah (104A12Ah)
++a;
0104A10D mov eax,dword ptr [a]
0104A110 add eax,1
0104A113 mov dword ptr [a],eax
++b;
0104A116 mov eax,dword ptr [b]
0104A119 add eax,1
0104A11C mov dword ptr [b],eax
++c;
0104A11F mov eax,dword ptr [c]
0104A122 add eax,1
0104A125 mov dword ptr [c],eax
}
0104A128 jmp test_cpu+4Eh (104A0FEh)
[/code]
一共是9+6=15条指令,执行时间为9795ms,一秒内总共执行0xffffffff*15/9.795=6577285290.964778条指令,3.1GHz的cpu来看,平均一个时钟周期执行两条指令。

可以接着扩展,比如
[cpp]
int a=0,b=0,c=0,e=0,f=0,g=0;
for(unsigned i=0; i<0xffffffff; ++i){
++a; ++b; ++c; ++e; ++f; ++g;
}
[/cpp]

被翻译成24条指令,10611ms,总条数为0xffffffff*24/10.611=9714373299.406277,平均一个时钟周期执行3条指令。

指令在执行阶段,会被分析数据依赖关系,可以并行的就直接被dispatch到ALU乱序执行。上面的a,b,c三个变量之间没有以来关系,于是就可以发挥superscalar的功效了。

但是如果a,b,c三个变量之间产生关联,就会变成顺序执行了,比如
[cpp]
int a=0,b=0,c=0;
for(unsigned i=0; i<0xffffffff; ++i){
a = b+c;
b = a+c;
c = a+b;
}
[/cpp]

同样编译成15条指令
[code]
for(unsigned i=0; i<0xffffffff; ++i){
00A4A0F5 mov dword ptr [i],0
00A4A0FC jmp test_cpu+57h (0A4A107h)
00A4A0FE mov eax,dword ptr [i]
00A4A101 add eax,1
00A4A104 mov dword ptr [i],eax
00A4A107 cmp dword ptr [i],0FFFFFFFFh
00A4A10B jae test_cpu+7Ah (0A4A12Ah)
a = b+c;
00A4A10D mov eax,dword ptr [b]
00A4A110 add eax,dword ptr [c]
00A4A113 mov dword ptr [a],eax
b = a+c;
00A4A116 mov eax,dword ptr [a]
00A4A119 add eax,dword ptr [c]
00A4A11C mov dword ptr [b],eax
c = a+b;
00A4A11F mov eax,dword ptr [a]
00A4A122 add eax,dword ptr [b]
00A4A125 mov dword ptr [c],eax
}
00A4A128 jmp test_cpu+4Eh (0A4A0FEh)
[/code]
但是却花了28410ms才执行完,0xffffffff*15/28.410=2267670166.3146777,执行一条指令需要1.367个时钟周期。

查了下intel的Software Developer’s Manual关于superscalar的说明

2.2.3.2 Execution Core
The execution core of the Intel Core microarchitecture is superscalar and can process instructions out of order to increase the overall rate of instructions executed per cycle (IPC). The execution core employs the following feature to improve execution throughput and efficiency:

• Up to six micro-ops can be dispatched to execute per cycle
• Up to four instructions can be retired per cycle
• Three full arithmetic logical units
• SIMD instructions can be dispatched through three issue ports
• Most SIMD instructions have 1-cycle throughput (including 128-bit SIMD instructions)
• Up to eight floating-point operation per cycle
• Many long-latency computation operation are pipelined in hardware to increase overall throughput
• Reduced exposure to data access delays using Intel Smart Memory Access

core架构的cpu一共有3个算术逻辑单元ALU,每个cycle最多可以并发6条微指令。

wikipedia上的介绍讲1998年以后的通用cpu,除了低功耗和嵌入式的以外,基本都支持superscalar技术。

其实现在一些高性能的嵌入式cpu也支持超标量了,比如iphone 4使用的A4处理器,包含一个ARM Cortex-A8内核,是一个dual-issue superscalar design,每个周期可以并发执行两条指令。