我正在对科学应用进行一些数值优化.我注意到的一件事是GCC会pow(a,2)通过编译来优化调用a*a,但调用pow(a,6)没有优化,实际上会调用库函数pow,这会大大降低性能.(相比之下,英特尔C++编译器,可执行文件icc,将消除库调用pow(a,6).)
我很好奇的是,当我更换pow(a,6)与a*a*a*a*a*a使用GCC 4.5.1和选项" -O3 -lm -funroll-loops -msse4",它采用5分mulsd的说明:
movapd  %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
如果我写(a*a*a)*(a*a*a),它会产生
movapd  %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm13, %xmm13
这将乘法指令的数量减少到3. icc具有类似的行为.
为什么编译器不能识别这种优化技巧?
我正在开发一些工程模拟.这包括实现一些长方程,例如这个方程,以计算橡胶类材料中的应力:
T = (
    mu * (
            pow(l1 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a
            * (
                pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
                - l1 * l2 * l3 * pow(l1 * l2 * l3, -0.4e1 / 0.3e1) / 0.3e1
            ) * pow(l1 * l2 * l3, 0.1e1 / 0.3e1) / l1
            - pow(l2 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l1 / 0.3e1
            - pow(l3 * …我们N是一个编译时无符号整数.
GCC可以优化
unsigned sum = 0;
for(unsigned i=0; i<N; i++) sum += a; // a is an unsigned integer   
简单地说   a*N.这可以理解,因为模数运算说(a%k + b%k)%k = (a+b)%k.
但GCC不会优化
float sum = 0;
for(unsigned i=0; i<N; i++) sum += a;  // a is a float
到a*(float)N.
但是通过使用关联数学,-Ofast我发现GCC可以按顺序减少这一点log2(N).例如,因为N=8它可以在三个添加中进行总和.
sum = a + a
sum = sum + sum // (a + a) + (a + a)
sum = sum + sum …考虑使用以下 float 循环,使用 -O3 -mavx2 -mfma 编译
for (auto i = 0; i < a.size(); ++i) {
    a[i] = (b[i] > c[i]) ? (b[i] * c[i]) : 0;
}
Clang 在矢量化方面做得非常出色。它使用 256 位 ymm 寄存器,并了解 vblendps/vandps 之间的差异,以获得尽可能最佳的性能。
.LBB0_7:
        vcmpltps        ymm2, ymm1, ymm0
        vmulps  ymm0, ymm0, ymm1
        vandps  ymm0, ymm2, ymm0
然而,海湾合作委员会的情况要糟糕得多。由于某种原因,它并没有比 SSE 128 位向量更好(-mprefer-vector-width=256 不会改变任何东西)。
.L6:
        vcomiss xmm0, xmm1
        vmulss  xmm0, xmm0, xmm1
        vmovss  DWORD PTR [rcx+rax*4], xmm0
如果将其替换为普通数组(如指南中所示),gcc 会将其矢量化为 AVX ymm。
int a[256], b[256], c[256];
auto foo …我正在尝试使用以下简单的C代码来分析计算sqrt所需的时间,其中readTSC()是一个读取CPU循环计数器的函数.
double sum = 0.0;
int i;
tm = readTSC();
for ( i = 0; i < n; i++ )
   sum += sqrt((double) i);
tm = readTSC() - tm;
printf("%lld clocks in total\n",tm);
printf("%15.6e\n",sum);
但是,当我使用打印出汇编代码时
gcc -S timing.c -o timing.s
在英特尔机器上,结果(如下所示)令人惊讶?
为什么汇编代码中有两个sqrts,一个使用sqrtsd指令而另一个使用函数调用?它是否与循环展开和尝试在一次迭代中执行两个sqrts相关?
以及如何理解这条线
ucomisd %xmm0, %xmm0
为什么它与%xmm0自身相比?
//----------------start of for loop----------------
call    readTSC
movq    %rax, -32(%rbp)
movl    $0, -4(%rbp)
jmp .L4
.L6:
cvtsi2sd    -4(%rbp), %xmm1
// 1. use sqrtsd instruction
sqrtsd  %xmm1, %xmm0
ucomisd %xmm0, …在回答我建议的问题时-ffast-math,评论指出这是危险的.
我个人的感觉是,在科学计算之外,没关系.我还认为严肃的财务应用程序使用固定点而不是浮点数.
当然,如果你想在你的项目中使用它,最终的答案是在你的项目上测试它,看看它对它有多大影响.但我认为,尝试并具有此类优化经验的人可以给出一般答案:
可以ffast-math在正常项目中安全使用吗?
鉴于IEEE 754浮点具有舍入误差,假设您已经生活在不精确的计算中.
这个答案特别启发了这样一个事实:-ffast-math除了重新排序操作会导致稍微不同的结果(不检查NaN或零,禁用签名零只是为了说明一些),但我没有看到效果是什么其中最终将是一个真实的代码.
我试着想到浮点的典型用法,这就是我提出的:
和学校项目,但这些并不重要.
类似于问题gcc的ffast-math实际上做了什么?并且与Clang优化级别的SO问题有关,我想知道在实际条件下优化是做什么clang的-Ofast,以及它们是否与gcc完全不同,或者这是否依赖于编译器依赖于硬件.
根据clang优化级别的公认答案,-Ofast增加了-O3优化:-fno-signed-zeros -freciprocal-math -ffp-contract=fast -menable-unsafe-fp-math -menable-no-nans -menable-no-infs.这似乎完全与浮点数学相关.但是这些优化对于像C++这样的事物来说意味着什么呢?像英特尔酷睿i7这样的CPU上的浮点数通用数学函数以及这些差异有多可靠?
例如,实际上:
该代码std::isnan(std::numeric_limits<float>::infinity() * 0)返回真正的我-O3.我相信这是符合IEEE数学标准的结果.
随着-Ofast不过,我得到一个错误的返回值.此外,该操作(std::numeric_limits<float>::infinity() * 0) == 0.0f返回true.
我不知道这是否与gcc中看到的相同.我不清楚结果如何依赖于结构,也不清楚编译器如何依赖它们,也不清楚是否存在任何适用的标准-Ofast.
如果有人可能会产生类似于一组单元测试或代码公案来解决这个问题,那可能是理想的.我已经开始做这样的事情,但宁愿不重新发明轮子.
为什么必须使用-ffast-mathg ++来实现使用doubles 的循环向量化?我不喜欢-ffast-math因为我不想失去精确度.
是否可以在运行时选择性地打开/关闭-ffast-math?例如,使用公共基类Math创建类FastMath和AccurateMath,以便一个人能够在运行时使用这两个实现?将次正常闪烁归零等同样如此.
特别是,我不知道使用-ffast-math进行编译是否会发出一条指令,该指令一旦执行就会影响线程中的所有数值计算(例如,设置一个标志以将subnormals刷新为零).
我正在重写我上学期为大学开发的光线追踪器,但遇到了以下问题:当我在 Debug 中编译和运行我的代码时,输出符合预期
但是当我启用更高的优化级别(例如“-O2”)时,结果会完全不同:
我不确定为什么会发生这种情况。我追踪到球体相交代码
//#pragma GCC push_options
//#pragma GCC optimize("O0")
Intersection Sphere::intersect(const Ray& ray, const float previous) const
{
    const auto oc = ray.origin - center_;
    const auto lhv = -dot(ray.direction, oc);
    const auto discriminant = lhv * lhv - (oc.lensqr() - radius_ * radius_);
    if (discriminant < 0.0F)
    {
        return Intersection::failure();
    }
    float distance;
    const auto rhv = std::sqrt(discriminant);
    const auto r = std::minmax(lhv + rhv, lhv - rhv);
    if (r.first <= 0.0F)
    {
        if (r.second <= 0.0F) …