【发布时间】:2014-12-23 19:34:48
【问题描述】:
我正在开发一个经常需要将 6 到 8 个有符号 32 位整数转换为 32 位实数的应用程序。我用自定义汇编代码替换了 delphi 代码,令我惊讶的是,FPU 转换总是一样快,并且在某些计算机上比 SSE 转换快很多。下面是一些说明代码:
program Project1;
{$R *.res}
uses
windows,dialogs,sysutils;
type
piiii=^tiiii;
tiiii=record i1,i2,i3,i4:longint; end;
pssss=^tssss;
tssss=record s1,s2,s3,s4:single; end;
var
convert_value:single=13579.02468;
function convert_x87(adata:longint):single;
asm
mov [esp-4],eax
fild longint([esp-4])
fmul [convert_value]
end;
procedure convert_sse(afrom,ato,aconv:pointer);
asm
CVTDQ2PS xmm0,[eax]
mulps xmm0,[ecx]
movaps [edx],xmm0
end;
procedure get_mem(var p1,p2:pointer);
begin
getmem(p1,31);
p2:=pointer((longint(p1)+15) and (not 15));
end;
var
a,b,c,d:cardinal;
z:single;
i:piiii;
s1,s2:pssss;
w1,w2,w3:pointer;
begin
b:=gettickcount;
a:=0;
repeat
z:=convert_x87(a);
inc(a);
until a=0;
c:=gettickcount-b;
get_mem(pointer(w1),pointer(i));
get_mem(pointer(w2),pointer(s1));
get_mem(pointer(w3),pointer(s2));
s1.s1:=convert_value;
s1.s2:=convert_value;
s1.s3:=convert_value;
s1.s4:=convert_value;
b:=gettickcount;
i.i1:=0;
i.i2:=1;
i.i3:=2;
i.i4:=3;
repeat
convert_sse(i,s2,s1);
inc(i.i1,4);
inc(i.i2,4);
inc(i.i3,4);
inc(i.i4,4);
until i.i1=0;
d:=gettickcount-b;
freemem(w1);
freemem(w2);
freemem(w3);
showmessage('FPU:'+inttostr(c)+'/SSE:'+inttostr(d));
end.
在转换过程中需要重新缩放(所以是乘法),这就是为什么里面有一个。使用的值只是我选择的一个随机值,但无论我使用什么值,结果都是一样的。 FPU 和 SSE 之间的舍入也有非常小的差异,但在这种情况下并不重要。
但是,如果您运行该代码,您会发现 FPU 路径永远不会比 SSE 路径慢,而且它没有任何意义。有人知道发生了什么吗?
编辑:这是汇编程序中带有循环的不同源代码。结果真的很有趣。如果注释掉增量指令,SSE 版本比 FPU 版本快很多,但如果包含增量指令,则它们的速度大致相同:
program Project1;
{$R *.res}
uses
windows,dialogs,sysutils;
type
piiii=^tiiii;
tiiii=record i1,i2,i3,i4:longint; end;
pssss=^tssss;
tssss=record s1,s2,s3,s4:single; end;
var
convert_value:single=13579.02468;
procedure test_convert_x87;
asm
// init test data
push ebx
xor ebx,ebx
mov [esp-4],$98765432
// convert and multiply 1 int32 to 1 single
@next_loop:
// inc [esp-4]
fild longint([esp-4])
fmul [convert_value]
fstp single([esp-8])
// loop
dec ebx
jnz @next_loop
pop ebx
end;
procedure test_convert_sse(afrom,ato,aconv:pointer);
asm
// init test data
push ebx
xor ebx,ebx
mov [eax+0],$98765432
mov [eax+4],$98765432
mov [eax+8],$98765432
mov [eax+12],$98765432
// convert and multiply 4 int32 to 4 single
@next_loop:
// inc [eax+0]
// inc [eax+4]
// inc [eax+8]
// inc [eax+12]
cvtdq2ps xmm0,[eax]
mulps xmm0,[ecx]
movaps [edx],xmm0
// loop
sub ebx,4
jnz @next_loop
pop ebx
end;
procedure get_mem(var p1,p2:pointer);
begin
getmem(p1,31);
p2:=pointer((longint(p1)+15) and (not 15));
end;
var
b,c,d:cardinal;
i:piiii;
s1,s2:pssss;
w1,w2,w3:pointer;
begin
b:=gettickcount;
test_convert_x87;
c:=gettickcount-b;
get_mem(pointer(w1),pointer(i));
get_mem(pointer(w2),pointer(s1));
get_mem(pointer(w3),pointer(s2));
s1.s1:=convert_value;
s1.s2:=convert_value;
s1.s3:=convert_value;
s1.s4:=convert_value;
b:=gettickcount;
test_convert_sse(i,s2,s1);
d:=gettickcount-b;
freemem(w1);
freemem(w2);
freemem(w3);
showmessage('FPU:'+inttostr(c)+'/SSE:'+inttostr(d));
end.
【问题讨论】:
-
请贴出快慢版生成的汇编代码。这使得找到罪魁祸首变得更加容易,因为这里很少有人使用 pascal 并且可以轻松地重新创建您的情景。
-
您好,感谢您的关注。如果您检查名为 convert_x87 和 convert_sse 的函数的发布源,您会看到它们在汇编程序中,您应该能够复制粘贴它们。只有计时部分在delphi中。
-
名为 convert_sse 的函数一次执行 4 次转换 + 乘法运算,并且与一次执行 1 次的 convert_fpu 函数调用 4 次的速度更慢或相同。 convert_sse 函数中使用的指令是 CVTDQ2PS mulps movaps,它们不是适合此任务的 SIMD 指令吗?
-
@Marladu 周围的代码(循环)非常重要。无论编译器做什么,简单的三行汇编代码都可能完全被破坏。我很难学到这一点。
-
在第一个程序中,注释掉对
convert_sse和convert_x87的调用,您会发现x87 变体要快得多。所有这些代码所做的就是计数到2**32。实际上,sse 变体花费了 25% 的时间来计算。比 x87 版本大得多的百分比。我对此的看法是,您代码的 FP 部分是微不足道的。您是否 100% 确定这是您的瓶颈?只有当您在程序中除了从整数转换为浮点数之外什么都不做时,您才能期望提高性能。实际程序中进行转换所花费的时间百分比是多少?