内联汇编器中的sqrtsd可以比sqrt()更快吗?

Can sqrtsd in inline assembler be faster than sqrt()?

本文关键字:sqrt 汇编器 sqrtsd      更新时间:2023-10-16

我正在创建一个需要大量使用 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 $1"
    DEFAULT_ITERATIONS=$1
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指令只需要 3802 个时钟,计算 16 的平方根,而sqrt()需要 5501 个时钟?它与某些指令的硬件实现有关吗?谢谢。

中央处理器:

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,因此它们具有执行平方根等运算的"理想"算法。然后,它们可以自由优化,但在所有情况下,它们都必须返回相同的结果,直到小数点后一位。所以他们的优化自由是不完全的,事实上是受到严重限制的。

您的算法可能在部分时间偏离了一位或两位数。对于某些用户来说,这可能完全可以忽略不计,但也可能导致其他一些用户出现令人讨厌的错误,因此默认情况下不允许这样做。

如果您更关心速度而不是标准合规性,请尝试使用编译器的选项。例如,在GCC中,我首先尝试的是-funsafe-math-optimizations,它应该可以无视严格的标准合规性进行优化。一旦你对它进行了足够的调整,你应该更接近并可能超过你手工实现的速度。

忽略其他问题,除非使用特定标志编译,否则sqrt()仍然会比sqrtsd慢一点。

sqrt()必须潜在地设置errno,它必须检查它是否在这种情况下。它仍然会归结为任何合理的编译器上的本机平方根指令,但它会有一点开销。没有像您的缺陷测试显示的那样有很多开销,但仍然有一些。

你可以在这里看到它的实际效果。

某些编译标志禁止此测试。例如,对于 GCC、fno-math-errnoffinite-math-only