作为模拟许多物体之间的引力和碰撞的粒子系统的一部分,我遇到了一个奇怪的错误。
这是一个旨在通过矢量化(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进行编译。
问题:第二段代码有什么问题?只是错了还是我想念的东西要浮起来?
答案 0 :(得分:3)
假设gravityVelX
从未是NaN,那么您的更改看起来与我相同。布尔值将转换为0.0
或1.0
。
如果您的更改启用了以前无法实现的优化,则可能是ICC的默认设置-ffast-math
引起了问题。 (默认实际上是-fp-model fast=1
:https://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,那可能就是错误计算的原因。