内联汇编程序中的 sqrtsd 可以比 sqrt() 更快吗?
Can sqrtsd in inline assembler be faster than sqrt()?
我正在创建一个需要大量使用 sqrt() 函数的测试实用程序。在深入研究了可能的优化之后,我决定尝试使用 C++ 中的内联汇编程序。代码是:
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <ctime>
using namespace std;
volatile double normalSqrt(double a){
double b = 0;
for(int i = 0; i < ITERATIONS; i++){
b = sqrt(a);
}
return b;
}
volatile double asmSqrt(double a){
double b = 0;
for(int i = 0; i < ITERATIONS; i++){
asm volatile(
"movq %1, %%xmm0 \n"
"sqrtsd %%xmm0, %%xmm1 \n"
"movq %%xmm1, %0 \n"
: "=r"(b)
: "g"(a)
: "xmm0", "xmm1", "memory"
);
}
return b;
}
int main(int argc, char *argv[]){
double a = atoi(argv[1]);
double c;
std::clock_t start;
double duration;
start = std::clock();
c = asmSqrt(a);
duration = std::clock() - start;
cout << "asm sqrt: " << c << endl;
cout << duration << " clocks" <<endl;
cout << "Start: " << start << " end: " << start + duration << endl;
start = std::clock();
c = normalSqrt(a);
duration = std::clock() - start;
cout << endl << "builtin sqrt: " << c << endl;
cout << duration << " clocks" << endl;
cout << "Start: " << start << " end: " << start + duration << endl;
return 0;
}
我正在使用设置迭代次数、开始分析并在 VIM 中打开分析输出的脚本来编译此代码:
#!/bin/bash
DEFAULT_ITERATIONS=1000000
if [ $# -eq 1 ]; then
echo "Setting ITERATIONS to "
DEFAULT_ITERATIONS=
else
echo "Using default value: $DEFAULT_ITERATIONS"
fi
rm -rf asd
g++ -msse4 -std=c++11 -O0 -ggdb -pg -DITERATIONS=$DEFAULT_ITERATIONS test.cpp -o asd
./asd 16
gprof asd gmon.out > output.txt
vim -O output.txt
true
输出为:
Using default value: 1000000
asm sqrt: 4
3802 clocks
Start: 1532 end: 5334
builtin sqrt: 4
5501 clocks
Start: 5402 end: 10903
问题是为什么sqrtsd
指令计算16的平方根只需要3802个时钟,而sqrt()
需要5501个时钟?
它与某些指令的硬件实现有关吗?谢谢。
CPU:
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 2
Core(s) per socket: 2
Socket(s): 1
NUMA node(s): 1
Vendor ID: AuthenticAMD
CPU family: 21
Model: 48
Model name: AMD A8-7600 Radeon R7, 10 Compute Cores 4C+6G
Stepping: 1
CPU MHz: 3100.000
CPU max MHz: 3100,0000
CPU min MHz: 1400,0000
BogoMIPS: 6188.43
Virtualization: AMD-V
L1d cache: 16K
L1i cache: 96K
L2 cache: 2048K
NUMA node0 CPU(s): 0-3
浮点运算必须考虑舍入。大多数 C/C++ 编译器采用 IEEE 754,因此它们有一个 "ideal" 算法来执行诸如平方根之类的运算。然后他们可以自由地进行优化,但他们必须 return 在所有情况下都 直到最后一位小数的相同结果。所以他们优化的自由度并不完全,实际上是被严重限制了。
您的算法有时可能会出现一两位数的偏差。对于某些用户来说这可能完全可以忽略不计,但也可能对其他一些用户造成严重的错误,因此默认情况下不允许这样做。
如果您更关心速度而不是标准合规性,请尝试查看编译器的选项。例如,在 GCC 中,我首先尝试的是 -funsafe-math-optimizations
,它应该能够在不考虑严格的标准合规性的情况下进行优化。一旦你对其进行了足够的调整,你应该更接近并可能超过你手工实现的速度。
忽略其他问题,除非使用特定标志编译,否则 sqrt()
仍然会比 sqrtsd
慢一点。
sqrt()
必须潜在地设置 errno
,它必须检查它是否属于那种情况。在任何合理的编译器上,它仍然会归结为本机平方根指令,但会有一点开销。不像您的缺陷测试显示的那样有很多开销,但仍然有一些。
您可以在实际操作中看到 here。
一些编译标志抑制了这个测试。例如对于 GCC,fno-math-errno
和 ffinite-math-only
.
我正在创建一个需要大量使用 sqrt() 函数的测试实用程序。在深入研究了可能的优化之后,我决定尝试使用 C++ 中的内联汇编程序。代码是:
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <ctime>
using namespace std;
volatile double normalSqrt(double a){
double b = 0;
for(int i = 0; i < ITERATIONS; i++){
b = sqrt(a);
}
return b;
}
volatile double asmSqrt(double a){
double b = 0;
for(int i = 0; i < ITERATIONS; i++){
asm volatile(
"movq %1, %%xmm0 \n"
"sqrtsd %%xmm0, %%xmm1 \n"
"movq %%xmm1, %0 \n"
: "=r"(b)
: "g"(a)
: "xmm0", "xmm1", "memory"
);
}
return b;
}
int main(int argc, char *argv[]){
double a = atoi(argv[1]);
double c;
std::clock_t start;
double duration;
start = std::clock();
c = asmSqrt(a);
duration = std::clock() - start;
cout << "asm sqrt: " << c << endl;
cout << duration << " clocks" <<endl;
cout << "Start: " << start << " end: " << start + duration << endl;
start = std::clock();
c = normalSqrt(a);
duration = std::clock() - start;
cout << endl << "builtin sqrt: " << c << endl;
cout << duration << " clocks" << endl;
cout << "Start: " << start << " end: " << start + duration << endl;
return 0;
}
我正在使用设置迭代次数、开始分析并在 VIM 中打开分析输出的脚本来编译此代码:
#!/bin/bash
DEFAULT_ITERATIONS=1000000
if [ $# -eq 1 ]; then
echo "Setting ITERATIONS to "
DEFAULT_ITERATIONS=
else
echo "Using default value: $DEFAULT_ITERATIONS"
fi
rm -rf asd
g++ -msse4 -std=c++11 -O0 -ggdb -pg -DITERATIONS=$DEFAULT_ITERATIONS test.cpp -o asd
./asd 16
gprof asd gmon.out > output.txt
vim -O output.txt
true
输出为:
Using default value: 1000000
asm sqrt: 4
3802 clocks
Start: 1532 end: 5334
builtin sqrt: 4
5501 clocks
Start: 5402 end: 10903
问题是为什么sqrtsd
指令计算16的平方根只需要3802个时钟,而sqrt()
需要5501个时钟?
它与某些指令的硬件实现有关吗?谢谢。
CPU:
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 2
Core(s) per socket: 2
Socket(s): 1
NUMA node(s): 1
Vendor ID: AuthenticAMD
CPU family: 21
Model: 48
Model name: AMD A8-7600 Radeon R7, 10 Compute Cores 4C+6G
Stepping: 1
CPU MHz: 3100.000
CPU max MHz: 3100,0000
CPU min MHz: 1400,0000
BogoMIPS: 6188.43
Virtualization: AMD-V
L1d cache: 16K
L1i cache: 96K
L2 cache: 2048K
NUMA node0 CPU(s): 0-3
浮点运算必须考虑舍入。大多数 C/C++ 编译器采用 IEEE 754,因此它们有一个 "ideal" 算法来执行诸如平方根之类的运算。然后他们可以自由地进行优化,但他们必须 return 在所有情况下都 直到最后一位小数的相同结果。所以他们优化的自由度并不完全,实际上是被严重限制了。
您的算法有时可能会出现一两位数的偏差。对于某些用户来说这可能完全可以忽略不计,但也可能对其他一些用户造成严重的错误,因此默认情况下不允许这样做。
如果您更关心速度而不是标准合规性,请尝试查看编译器的选项。例如,在 GCC 中,我首先尝试的是 -funsafe-math-optimizations
,它应该能够在不考虑严格的标准合规性的情况下进行优化。一旦你对其进行了足够的调整,你应该更接近并可能超过你手工实现的速度。
忽略其他问题,除非使用特定标志编译,否则 sqrt()
仍然会比 sqrtsd
慢一点。
sqrt()
必须潜在地设置 errno
,它必须检查它是否属于那种情况。在任何合理的编译器上,它仍然会归结为本机平方根指令,但会有一点开销。不像您的缺陷测试显示的那样有很多开销,但仍然有一些。
您可以在实际操作中看到 here。
一些编译标志抑制了这个测试。例如对于 GCC,fno-math-errno
和 ffinite-math-only
.