22

Demystifying the Restrict Keyword的底部是这个奇怪的建议:

由于 GCC 中调度的顺序,简化表达式总是更好。不要将内存访问与计算混为一谈。代码可以重写如下:

然后有一个例子本质上改变了这个

velocity_x[i] += acceleration_x[i] * time_step;

进入这个

const float ax  = acceleration_x[i];       // Then the same follows for y, z
const float vx  = velocity_x[i];           // etc for y, z
const float nvx = vx + ( ax * time_step ); // etc
velocity_x[i]   = nvx;                     // ...

真的吗?与优化编译器必须做的其他事情(例如 lambda 参数等)相比,我会认为这种转换是微不足道std::foreach的。

这只是陈旧、愚蠢的建议吗?或者 GCC 不能或不会这样做有充分的理由吗?(这让我担心velocity += acceleration * time_step用我的Vector3f课写上面的内容!

4

2 回答 2

15

编辑:( 我正在删除有关的详细信息,restrict因为它偏离了所提出的实际问题并引起了混乱。假设restict使用了 OP。)

对于优化编译器而言,您问题中的转换确实微不足道,但这不是阿克顿的论文所建议的。

这是论文中所做的转换:

这段代码...

  for (size_t i=0;i<count*stride;i+=stride)
  {
    velocity_x[i] += acceleration_x[i] * time_step;
    velocity_y[i] += acceleration_y[i] * time_step;
    velocity_z[i] += acceleration_z[i] * time_step;
    position_x[i] += velocity_x[i]     * time_step;
    position_y[i] += velocity_y[i]     * time_step;
    position_z[i] += velocity_z[i]     * time_step;
  }

...被转换成这段代码:

  for (size_t i=0;i<count*stride;i+=stride)
  {
    const float ax  = acceleration_x[i];
    const float ay  = acceleration_y[i];
    const float az  = acceleration_z[i];
    const float vx  = velocity_x[i];
    const float vy  = velocity_y[i];
    const float vz  = velocity_z[i];
    const float px  = position_x[i];
    const float py  = position_y[i];
    const float pz  = position_z[i];

    const float nvx = vx + ( ax * time_step );
    const float nvy = vy + ( ay * time_step );
    const float nvz = vz + ( az * time_step );
    const float npx = px + ( vx * time_step );
    const float npy = py + ( vy * time_step );
    const float npz = pz + ( vz * time_step );

    velocity_x[i]   = nvx;
    velocity_y[i]   = nvy;
    velocity_z[i]   = nvz;
    position_x[i]   = npx;
    position_y[i]   = npy;
    position_z[i]   = npz;
  }

优化是什么?

优化不是- 正如建议的那样 - 将 1 个表达式分离为 3 个表达式。

优化是在对任何特定数据块进行操作的指令之间插入有用的指令。

如果您跟踪数据从velocity_x[i]到移动到vx,则 CPU 在每个步骤之间执行其他工作。nvxvelocity_x[i]

为什么这是优化?

现代 CPU 通常具有流水线架构

由于指令是分阶段执行的,CPU 允许同时处理多条指令。但是,当一条指令需要另一条尚未完全执行的指令的结果时,这条流水线就会停止。在停止的指令可以运行之前,不会执行进一步的指令。

为什么我的优化编译器不自动执行此操作?

有些人会。

GCC 在这种优化方面相对较差。

我使用 gcc 4.7(x86-64 架构,在 -O3 优化)反汇编了上面的两个循环。产生了类似的程序集,但指令的顺序不同,第一个版本产生了严重的停顿,其中单个浮点数将在几条指令的范围内加载、更改和存储。

你可以在这里阅读一些关于 gcc 指令调度的信息,或者只是在网上搜索gcc 指令调度来查看很多关于这个问题的令人沮丧的文章。

于 2013-02-26T16:03:28.173 回答
1

在我看来,陈旧/愚蠢的建议。我的意思是详细程度特定于编译器、编译器版本、处理器、处理器版本等。我会坚持可读性并让编译器完成它的工作。如果有人担心某个目标中可能存在一个或两个时钟周期,请为该目标编写一些#def 程序集,并将更高级别的代码留给其他目标和参考。

于 2013-02-26T16:40:45.427 回答