关于优化浮点计算的建议:求解二次方程

时间:2011-04-20 13:02:18

标签: c optimization sse

我正在研究颗粒动力学问题。计算上昂贵的部分是下面的函数,它解决了二次方程以检测两个粒子的碰撞。

我想知道这是否可以轻松优化,或者我是否做了明显愚蠢的事情?例如,使用这些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);
                }
        }
}

3 个答案:

答案 0 :(得分:1)

计算a,b和c,我没有看到任何明显的错误。使用临时变量x1 = p1-&gt; x等对于一个不错的优化编译器既不会有帮助也不会受到伤害,因为在这种情况下由于没有写入p1和p2而没有混叠问题(一个常见的性能问题),并且在计算a,b和c时,编译器将决定将哪些变量保留在寄存器中。如果你想确定wrt别名,你总是可以将参数标记为

struct particle * const restrict p1

和p2相同。

我认为一个(次要的?)改进可能是在求和之后将乘法移动2,从而节省了一些乘法。 E.g。

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

答案 1 :(得分:1)

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

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

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

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

答案 2 :(得分:0)

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