Assembly x86可以独立或并行执行FPU操作吗?

Assembly x86可以独立或并行执行FPU操作吗?,assembly,x86,floating-point,fpu,pipelining,Assembly,X86,Floating Point,Fpu,Pipelining,我的老师声称处理器有时可以并行执行FPU操作。像这样: float a = 3.14; float b = 5.12; float c; float d = 3.02; float e = 2.52; float f; c = a + b; f = e + d; 因此,正如我所听说的,上面的2个add操作的执行速度比: float a = 3.14; float b = 5.12; float c; float d = 3.02; float e = 2.52; float f; c = a

我的老师声称处理器有时可以并行执行FPU操作。像这样:

float a = 3.14;
float b = 5.12;
float c;
float d = 3.02;
float e = 2.52;
float f;
c = a + b;
f = e + d;
因此,正如我所听说的,上面的2个add操作的执行速度比:

float a = 3.14;
float b = 5.12;
float c;
float d = 3.02;
float e = 2.52;
float f;
c = a + b;
f = c + d;
因为处理器必须等待
c
计算完成

我想验证这一点,所以我编写了一个函数来完成第二件事,它通过检查时间戳计数器来测量时间:

flds    h # st(7)
flds    g # st(6)
flds    f # st(5)
flds    e # st(4)
flds    d # st(3)
flds    c # st(2)
flds    b # st(1)
flds    a # st(0)
fadd    %st, %st(1) # i = a + b
fmul    %st, %st(2) # j = i * c
fadd    %st, %st(3) # k = j + d
fmul    %st, %st(4) # l = k + e
fadd    %st, %st(5) # m = l + f
fmul    %st, %st(6) # n = m * g
fadd    %st, %st(7) # o = n + h
这些都不是独立的。现在,我正在尝试写独立的。但问题是,无论我实际做什么,值总是保存到
ST(0)
(无论我使用哪个指令),然后可以选择弹出它,但这仍然意味着我们必须等待计算

我查看了编译器生成的代码(
gcc-S
)。它在
st
寄存器上不是这样运行的。对于每个数字,它都会:

flds number
fstps -some_value(%ebp)
然后(例如,对于a和b,其中
-4(%ebp)
是a,
-8(%ebp)
是b):

因此,它首先加载到FPU,然后跳回正常堆栈。然后,它弹出一个值(到
st(0)
),将其添加到该值中,然后返回结果。所以它仍然不是独立的,因为我们必须等到
st(0)
被释放

我的老师说错了什么吗,或者有没有一种方法可以让它们独立起来,在我测量时,它们的执行时间会有明显的不同?

按照的风格,我将您的老师的说法“处理器有时可以并行执行FPU操作”评为“半正确”。在某些意义上和某些条件下,这是完全正确的;从其他意义上讲,这根本不是真的。因此,作出一般性的陈述是非常误导人的,而且很可能被误解

现在,很有可能,你的老师是在一个非常具体的背景下说这句话的,对他之前告诉你的内容做了一些假设,而你没有把所有这些都包括在问题中,所以我不会责怪他们故意误导。相反,我将试图澄清这一普遍主张,指出它在某些方面是正确的,在其他方面是错误的

最大的症结正是所谓的“FPU操作”。通常,x86处理器在一个单独的浮点协处理器(称为浮点单元,或FPU)x87上执行FPU操作。在80486处理器之前,这是一个安装在主板上的独立芯片。从80486DX开始,x87 FPU与主处理器直接集成在同一个硅上,因此可用于所有系统,而不仅仅是那些安装了专用x87 FPU的系统。今天,所有x86处理器都有内置的x87兼容FPU,这通常是人们在x86微体系结构中说“FPU”时所指的

然而,x87 FPU很少再用于浮点操作。尽管它仍然存在,但实际上已被SIMD单元所取代,SIMD单元更易于编程且(通常)更高效

AMD是第一个用3DNow推出这种专用的矢量单元的公司!K6-2微处理器中的技术(大约1998年)。由于各种技术和市场原因,除了在某些游戏和其他专业应用程序中,它并没有真正得到应用,也从未在业界流行(AMD已经在现代处理器上逐步淘汰了它),但它确实支持压缩单精度浮点值的算术运算

当英特尔发布带有奔腾III处理器的SSE扩展时,SIMD真正开始流行起来。SSE类似于3DNow!,因为它支持对单精度浮点值进行向量运算,但与之不兼容,并且支持稍大范围的运算。AMD也很快在处理器上增加了SSE支持。与3DNow相比,SSE真的很好!它使用了一组完全独立的寄存器,这使得编程更加容易。对于奔腾4,英特尔发布了SSE2,它是SSE的扩展,增加了对双精度浮点值的支持。支持64位长模式扩展(AMD64)的所有处理器都支持SSE2,这也是目前所有处理器的特点,因此64位代码实际上总是使用SSE2指令来操作浮点值,而不是x87指令。即使在32位代码中,SSE2指令今天也很常用,因为自奔腾4以来的所有处理器都支持它们

除了支持传统处理器外,今天使用x87指令的原因只有一个,那就是x87 FPU支持一种特殊的“长双精度”格式,精度为80位。SSE仅支持单精度(32位),而SSE2增加了对双精度(64位)值的支持。如果您绝对需要扩展精度,那么x87是您的最佳选择。(在单个指令的级别上,它的速度与在标量值上运行的SIMD单元相当。)否则,您更喜欢SSE/SSE2(以及后来的指令集SIMD扩展,如AVX等),当然,当我说“您”时,我不仅仅指汇编语言程序员;我也指编译器。例如,VisualStudio2010是最后一个默认为32位构建生成x87代码的主要版本。在所有更高版本中,除非您专门关闭SSE2指令(
/arch:IA32

使用这些SIMD指令,可以同时执行多个浮点操作,这是完全正确的,事实上,这就是整点操作。即使在处理标量(非压缩)浮点值时,如您所示的代码中所示,现代处理器通常有多个执行单元,允许同时执行多个操作(假设满足某些条件,如您所指出的缺少数据依赖性,则
flds    -4(%ebp)
fadds   -8(%ebp) # i = a + b
fstps   -32(%ebp)
fadd  st(1), st(0)    ; clock cycles 1 through 3
fadd  st(2), st(0)    ; clock cycles 2 through 4
fadd  st(3), st(0)    ; clock cycles 3 through 5
fadd  st(4), st(0)    ; clock cycles 4 through 6
fld  [a1]   ; cycle 1
fadd [a2]   ; cycles 2-4
fld  [b1]   ; cycle 3
fadd [b2]   ; cycles 4-6
fld  [c1]   ; cycle 5
fadd [c2]   ; cycles 6-8
fxch st(2)  ; cycle 6 (pairs with previous instruction)
fadd [a3]   ; cycles 7-9
fxch st(1)  ; cycle 7 (pairs with previous instruction)
fadd [b3]   ; cycles 8-10
fxch st(2)  ; cycle 8 (pairs with previous instruction)
fadd [c3]   ; cycles 9-11
fxch st(1)  ; cycle 9 (pairs with previous instruction)
fadd [a4]   ; cycles 10-12
fxch st(2)  ; cycle 10 (pairs with previous instruction)
fadd [b4]   ; cycles 11-13
fxch st(1)  ; cycle 11 (pairs with previous instruction)
fadd [c4]   ; cycles 12-14
fxch st(2)  ; cycle 12 (pairs with previous instruction)
flds    -4(%ebp)
fadds   -8(%ebp) # i = a + b
fstps   -32(%ebp)