将 C++ 代码转换为 x87 内联汇编代码
Convert C++ code to x87 inline assembly code
我正在尝试将 C++ 代码转换为 x87 样式的内联汇编代码。
C++代码:
double a = 0.0, b = 0.0, norm2 = 0.0;
int n;
for (n = 0; norm2 < 4.0 && n < N; ++n) {
double c = a*a - b*b + x;
b = 2.0*a*b + y;
a = c;
norm2 = a*a + b*b;
}
内联汇编代码:
double a = 0.0, b = 0.0, norm2 = 0.0;
int n;
for (n = 0; norm2 < 4.0 && n < N; ++n) {
// double c = a * a - b * b + x;
__asm fld a
__asm fmul st(0), st(0)
__asm fld b
__asm fmul st(0), st(0)
__asm fsubp st(1), st(0)
__asm fld x
__asm faddp st(1), st(0)
__asm fstp c
// b = 2.0 * a * b + y;
__asm fld two
__asm fld b
__asm fld a
__asm fmulp st(2), st(0)
__asm fmulp st(1), st(0)
__asm fld y
__asm faddp st(1), st(0)
__asm fstp b
// a = c
__asm fld c
__asm fstp a
//norm2 = a * a + b * b;
__asm fld a
__asm fmul st(0), st(0)
__asm fld b
__asm fmul st(0), st(0)
__asm faddp st(1), st(0)
__asm fstp norm2
}
虽然我的汇编代码可以运行,但速度很慢。我怎样才能加快速度?
这个有很多需要改进的地方。以下是一些要点:
不要在 MSVC 风格的内联汇编中编程
MSVC 风格的内联汇编可能很容易编程,但它也强制所有变量驻留在内存中。每次您读取或分配一个变量时,都会执行一次缓慢的内存访问。这对性能有很大影响。
相反,将整个程序集函数写在一个单独的程序集文件中。如果那不可能,至少开始你的汇编代码时将所有变量加载到寄存器中,然后完全在这些寄存器上计算并通过将寄存器写回变量来结束汇编部分。这样,无用的数据移动量就被最小化了。
执行此操作时,在汇编中实现 for
循环本身,这样您就不必在每次迭代时写出然后读回所有变量,而是整个循环只读一次。
在寄存器中保留尽可能多的值
如前所述,所有这些 fld
和 fstp
指令都需要时间。将数字保存在寄存器中,这样您就不必经常重新加载它们。此外,如果这不可能,至少将加载和存储合并到以下指令中。例如,而不是
__asm fld x
__asm faddp st(1), st(0)
你可以
__asm fadd x
但是将所有内容都保存在寄存器中要好得多。例如,只需将 c
变量保留在堆栈中即可轻松删除它。
不要重复工作
您的代码计算 a*a
和 b*b
两次:一次在上一次迭代中计算 norm2
,一次在下一次迭代中计算 c
。将这些乘积计算一次并保留它们以节省两次乘法。
使用更便宜的指令而不是更昂贵的指令。
回想一下 2x = x + x 并用加法代替昂贵的常数负载和乘法。
还记得 a² - b² = (a + b)(a - b) 用加法代替乘法。请注意,这可能会改变四舍五入,并且与“不要执行两次工作”的建议不兼容。但也许它可以用于初始迭代。
我正在尝试将 C++ 代码转换为 x87 样式的内联汇编代码。
C++代码:
double a = 0.0, b = 0.0, norm2 = 0.0;
int n;
for (n = 0; norm2 < 4.0 && n < N; ++n) {
double c = a*a - b*b + x;
b = 2.0*a*b + y;
a = c;
norm2 = a*a + b*b;
}
内联汇编代码:
double a = 0.0, b = 0.0, norm2 = 0.0;
int n;
for (n = 0; norm2 < 4.0 && n < N; ++n) {
// double c = a * a - b * b + x;
__asm fld a
__asm fmul st(0), st(0)
__asm fld b
__asm fmul st(0), st(0)
__asm fsubp st(1), st(0)
__asm fld x
__asm faddp st(1), st(0)
__asm fstp c
// b = 2.0 * a * b + y;
__asm fld two
__asm fld b
__asm fld a
__asm fmulp st(2), st(0)
__asm fmulp st(1), st(0)
__asm fld y
__asm faddp st(1), st(0)
__asm fstp b
// a = c
__asm fld c
__asm fstp a
//norm2 = a * a + b * b;
__asm fld a
__asm fmul st(0), st(0)
__asm fld b
__asm fmul st(0), st(0)
__asm faddp st(1), st(0)
__asm fstp norm2
}
虽然我的汇编代码可以运行,但速度很慢。我怎样才能加快速度?
这个有很多需要改进的地方。以下是一些要点:
不要在 MSVC 风格的内联汇编中编程
MSVC 风格的内联汇编可能很容易编程,但它也强制所有变量驻留在内存中。每次您读取或分配一个变量时,都会执行一次缓慢的内存访问。这对性能有很大影响。
相反,将整个程序集函数写在一个单独的程序集文件中。如果那不可能,至少开始你的汇编代码时将所有变量加载到寄存器中,然后完全在这些寄存器上计算并通过将寄存器写回变量来结束汇编部分。这样,无用的数据移动量就被最小化了。
执行此操作时,在汇编中实现 for
循环本身,这样您就不必在每次迭代时写出然后读回所有变量,而是整个循环只读一次。
在寄存器中保留尽可能多的值
如前所述,所有这些 fld
和 fstp
指令都需要时间。将数字保存在寄存器中,这样您就不必经常重新加载它们。此外,如果这不可能,至少将加载和存储合并到以下指令中。例如,而不是
__asm fld x
__asm faddp st(1), st(0)
你可以
__asm fadd x
但是将所有内容都保存在寄存器中要好得多。例如,只需将 c
变量保留在堆栈中即可轻松删除它。
不要重复工作
您的代码计算 a*a
和 b*b
两次:一次在上一次迭代中计算 norm2
,一次在下一次迭代中计算 c
。将这些乘积计算一次并保留它们以节省两次乘法。
使用更便宜的指令而不是更昂贵的指令。
回想一下 2x = x + x 并用加法代替昂贵的常数负载和乘法。
还记得 a² - b² = (a + b)(a - b) 用加法代替乘法。请注意,这可能会改变四舍五入,并且与“不要执行两次工作”的建议不兼容。但也许它可以用于初始迭代。