我正在编写一个执行数百万个模块化添加的程序。为了提高效率,我开始思考如何使用机器级指令来实现模块化加法。
设 w 为机器的字长(通常为 32 或 64 位)。如果取模数为 2^w,那么模加法可以非常快速地执行:只需简单地将加数相加,并丢弃进位即可。
我使用以下 C 代码测试了我的想法:
#include <stdio.h>
#include <time.h>
int main()
{
unsigned int x, y, z, i;
clock_t t1, t2;
x = y = 0x90000000;
t1 = clock();
for(i = 0; i <20000000 ; i++)
z = (x + y) % 0x100000000ULL;
t2 = clock();
printf("%x\n", z);
printf("%u\n", (int)(t2-t1));
return 0;
}
使用带有以下选项的 GCC 进行编译(我曾经-O0
阻止 GCC 展开循环):
-S -masm=intel -O0
生成的汇编代码的相关部分是:
mov DWORD PTR [esp+36], -1879048192
mov eax, DWORD PTR [esp+36]
mov DWORD PTR [esp+32], eax
call _clock
mov DWORD PTR [esp+28], eax
mov DWORD PTR [esp+40], 0
jmp L2
L3:
mov eax, DWORD PTR [esp+36]
mov edx, DWORD PTR [esp+32]
add eax, edx
mov DWORD PTR [esp+44], eax
inc DWORD PTR [esp+40]
L2:
cmp DWORD PTR [esp+40], 19999999
jbe L3
call _clock
很明显,不涉及任何模运算。
现在,如果我们将 C 代码的模块化添加行更改为:
z = (x + y) % 0x0F0000000ULL;
汇编代码更改为(仅显示相关部分):
mov DWORD PTR [esp+36], -1879048192
mov eax, DWORD PTR [esp+36]
mov DWORD PTR [esp+32], eax
call _clock
mov DWORD PTR [esp+28], eax
mov DWORD PTR [esp+40], 0
jmp L2
L3:
mov eax, DWORD PTR [esp+36]
mov edx, DWORD PTR [esp+32]
add edx, eax
cmp edx, -268435456
setae al
movzx eax, al
mov DWORD PTR [esp+44], eax
mov ecx, DWORD PTR [esp+44]
mov eax, 0
sub eax, ecx
sal eax, 28
mov ecx, edx
sub ecx, eax
mov eax, ecx
mov DWORD PTR [esp+44], eax
inc DWORD PTR [esp+40]
L2:
cmp DWORD PTR [esp+40], 19999999
jbe L3
call _clock
显然,在两次调用_clock
.
考虑到汇编指令数量的增加,我预计通过正确选择模数可以获得至少 100% 的性能增益。但是,在运行输出时,我注意到速度仅提高了 10%。我怀疑操作系统正在使用多核 CPU 来并行运行代码,但即使将进程的 CPU 亲和性设置为 1 也没有改变任何东西。
你能给我一个解释吗?
编辑:使用 VC++ 2010 运行示例,我得到了我的预期:第二个代码比第一个示例慢 12 倍!