将条件转换为算术表达式

时间:2018-11-16 17:25:58

标签: c++ floating-accuracy micro-optimization arithmetic-expressions

作为模拟许多物体之间的引力和碰撞的粒子系统的一部分,我遇到了一个奇怪的错误。

这是一个旨在通过矢量化(OpenMP SIMD)进行优化的学校项目,作为优化的一部分,我想摆脱以下表达式:

if (r > COLLISION_DISTANCE) {
    resultVelX += gravityVelX;
    // ... same for remaining dimensions
}
if (r > 0.f && r < COLLISION_DISTANCE) {
    resultVelX += collisionVelX;
    // ... same for remaining dimensions
}

我的想法是捕获变量中的两个条件,然后使用简单的算术在没有if的情况下将值添加到结果中:

const int condGrav = (r > COLLISION_DISTANCE);
const int condCol = (r > 0.f && r < COLLISION_DISTANCE);
resultVelX += condGrav * gravityVelX + condCol * collisionVelX;
// ... same for remaining dimensions

我们已经为此项目提供了一组测试,令我惊讶的是,虽然两个版本的代码都通过了简单的测试,但在最复杂的情​​况下,第二个版本的测试却失败了,报告了无限的精度错误(e + 616正如我从最翔实的日志中发现的那样。)

所有计算都在浮点数上完成。使用intel 2016a编译器icpc进行编译。

问题:第二段代码有什么问题?只是错了还是我想念的东西要浮起来?

2 个答案:

答案 0 :(得分:3)

假设gravityVelX从未是NaN,那么您的更改看起来与我相同。布尔值将转换为0.01.0

如果您的更改启用了以前无法实现的优化,则可能是ICC的默认设置-ffast-math引起了问题。 (默认实际上是-fp-model fast=1https://software.intel.com/en-us/node/522979。这类似于gcc的-ffast-math,后者允许通过优化来更改结果。)


顺便说一句,您可能会得到更好的结果,因为SSE2可以直接做到这一点

resultVelX += r > COLLISION_DISTANCE ? gravityVelX : 0.0;

最直接用C表示您希望编译器发出的内容
cmpps r,collision_distance / andps gravityVelX, cmp_result / addps resultVelX, and_result)。您实际上并不需要或不需要相乘,并且创建实际的1.0比仅添加0或所需的要麻烦。

x86 SIMD比较指令产生一个全零或全一元素的向量,您可以直接将其用作AND掩码。这对于条件加法非常有用,因为全零位模式表示IEEE 754 0.0,而零是可加性。

(没有-ffast-math,编译器不能总是假设添加0.0是无操作。我想是因为有符号零。通常需要额外的选项来告诉编译器FP操作可以引发无论如何,使用ICC的默认选项,它应该能够自行将if转换为无分支代码,但是如果遇到问题,请手动处理三元组总是添加某物是一种方法。)

答案 1 :(得分:0)

用于SIMD(SSE)比较的伪代码g如下:

__m128 _mm_cmpgt_ps (__m128 a, __m128 b)

FOR j := 0 to 3
    i := j*32
    dst[i+31:i] := ( a[i+31:i] > b[i+31:i] ) ? 0xffffffff : 0
ENDFOR

比较的结果不是1.0f,而是NaN
我不太清楚,因为我看不到完整的代码,但是如果您在程序中使用SSE,那可能就是错误计算的原因。