【问题标题】:Advice on optimization floating point calculations: solving a quadratic equation关于优化浮点计算的建议:求解二次方程
【发布时间】:2011-04-20 13:02:17
【问题描述】:

我正在研究一个细粒度的动力学问题。计算量大的部分是下面的函数,它求解一个二次方程来检测两个粒子的碰撞。

我想知道这是否可以轻松优化,或者我是否正在做一些明显愚蠢的事情?例如,使用这些const double x1 = p1->x; 结构来给编译器一个提示是个好主意吗?查看汇编代码,编译器使用 SSE 指令,但我不知道它们是否在任何方面都是最优的(可能不是)。根据分析器,大部分时间都花在计算表达式abc 上。当您尝试优化如下所示的某些内核功能时,您(通常)会做什么?

void detect_collision_of_pair(struct particle* p1, struct particle* p2){
        const double x1  = p1->x;
        const double y1  = p1->y;
        const double z1  = p1->z;
        const double x2  = p2->x;
        const double y2  = p2->y;
        const double z2  = p2->z;
        const double vx1 = p1->vx;
        const double vy1 = p1->vy;
        const double vz1 = p1->vz;
        const double vx2 = p2->vx;
        const double vy2 = p2->vy;
        const double vz2 = p2->vz;

        const double a = vx1*vx1 - 2.*vx1*vx2 + vx2*vx2 + vy1*vy1 - 2.*vy1*vy2 + vy2*vy2 + vz1*vz1 - 2.*vz1*vz2 + vz2*vz2;
        const double b = 2.*vx1*x1 - 2.*vx2*x1 - 2.*vx1*x2 + 2.*vx2*x2 + 2.*vy1*y1 - 2.*vy2*y1 - 2.*vy1*y2 + 2.*vy2*y2 + 2.*vz1*z1 - 2.*vz2*z1 - 2.*vz1*z2 + 2.*vz2*z2;
        const double c = -4.*particle_radius*particle_radius + x1*x1 - 2.*x1*x2 + x2*x2 + y1*y1 - 2.*y1*y2 + y2*y2 + z1*z1 - 2.*z1*z2 + z2*z2;

        double root = b*b-4.*a*c;
        if (root>=0.){
                root = sqrt(root);
                double time1 = (-b-root)/(2.*a);
                double time2 = (-b+root)/(2.*a);

                if ( (time1>-dt && time1<0.) || (time1<-dt && time2>0) ){
                        double times = -dt;
                        if (time1>-dt || time2<0){
                                if (time1>-dt){
                                        times = time1;
                                }else{
                                        times = time2;
                                }
                        }
                        resolve_collision(p1,p2,times);
                }
        }
}

【问题讨论】:

  • 谢谢大家。到目前为止,我的速度提高了 30%。

标签: c optimization sse


【解决方案1】:

为什么要扩展所有方程?这不是一个好主意。为什么不计算:

vx = v1x-v2x;
vy = v1y-v2y;
vz = v1z-v2z;
a = vx*vx + vy*vy + vz*vz;

这比例如计算 a 所执行的操作要少得多。 b 和 c 可以做同样的事情。您也可以对位置进行类似的操作,即计算 px、py、pz 作为位置的差异,然后将它们平方。

另一方面,摆脱所有 const 的东西,编译器不需要那些用于局部变量的东西。实际上,您可能不需要复制到局部变量,只需为您需要的东西创建局部变量,例如上面的 vx,vy,vz。您在这里做的太多了,这就是代码花费太多时间的原因:-)

【讨论】:

  • 每个人都会时不时发生这种情况... :-)
【解决方案2】:

对于计算 a、b 和 c,我看不出有什么明显的错误。使用临时变量 x1 = p1->x 等对一个体面的优化编译器既无帮助也无害,因为在这种情况下,由于未写入 p1 和 p2,因此不存在别名问题(常见的性能问题),并且编译器将决定在计算 a、b 和 c 时将哪些变量保存在寄存器中。如果你想确保 wrt 别名,你总是可以将参数标记为

struct particle * const restrict p1

p2 也是如此。

我想一个(小的?)改进可能是在求和之后将乘法移动 2,从而节省一些乘法。例如

const double b = 2 * (vx1*x1 - vx2*x1 - vx1*x2 + vx2*x2 + vy1*y1 - vy2*y1 - vy1*y2 + vy2*y2 + vz1*z1 - vz2*z1 - vz1*z2 + vz2*z2);

OTOH,您的代码的一个问题是,使用您所做的明显公式计算二次方程的根很容易发生灾难性的取消。参见例如

http://download.oracle.com/docs/cd/E19957-01/806-3568/ncg_goldberg.html

https://secure.wikimedia.org/wikipedia/en/wiki/Quadratic_equation#Floating_point_implementation

【讨论】:

  • 感谢const restrict 的提示!
  • 更好的是,为什么不把整个东西移位,因为它会乘以 2。
  • @leetNightshade:嗯,因为位移浮点值不等于乘以 2?此外,即使对于位移相当于乘以 2 的无符号整数变量,这也正是编译器完全能够自行完成的那种微优化。
【解决方案3】:

除了其他人提到的......你绝对需要使用双打吗?使用浮点数肯定是加快速度的一种方法。或者如果你需要这么高的精度,你可以使用 compile 来编译 64bit 平台;据我所知,32 位编译器使用一些“技巧”来进行 64 位数学运算,因此仅针对 64 位处理器进行编译应该会对您有所帮助,因为涉及的指令应该更少,或者类似的东西。

【讨论】:

  • 32 位机器通常有更宽的浮点寄存器,x86 肯定有(并且一直有)。
  • -1 表示“浮动会更快”的谬误。在除 ARM 之外的几乎所有现代拱门上,浮点数的速度完全相同,但精度极差,不应使用。
  • @R..“几乎所有现代的,”对吧?如果这是真的,那么它就不是在所有硬件上,而是在现代硬件上。感谢您对我如此广泛的具体和事实的事情投反对票......哦等等。
  • 顺便说一下,我投反对票的原因是因为这些错误信息是无穷无尽的错误和错误代码的来源。我们经常遇到使用float 的提问者,大概是假设有一些理由比double 更喜欢它,并且遇到默认促销或精度损失的问题。无论如何,您的答案与 OP 的问题无关。
  • 标签sse指的是x86上的sse指令集,用于向量化数学。单精度 float 只有一个 23 位尾数,如果将其转换为十进制,则产生类似 7 位小数的结果。这意味着它甚至不能在不损失精度的情况下存储大整数值(大于几百万)。这也意味着小的累积误差可能会很快变大 - 想象一下诸如运行窗口平均值或应用一系列线性变换之类的事情......
猜你喜欢
  • 1970-01-01
  • 2023-01-13
  • 1970-01-01
  • 2017-04-24
  • 1970-01-01
  • 2013-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多