带有 SSE 的条件语句
Posted
技术标签:
【中文标题】带有 SSE 的条件语句【英文标题】:Conditional statements with SSE 【发布时间】:2017-06-23 01:01:12 【问题描述】:我正在尝试为我的游戏进行一些计算,并且我正在尝试计算两点之间的距离。本质上,我使用圆方程来查看这些点是否在我定义的半径范围内。
(x - x1)^2 + (y - y1)^2 <= r^2
我的问题是:如何使用 SSE 评估条件语句并解释结果?到目前为止,我有这个:
float distSqr4 = (pow(x4 - k->getPosition().x, 2) + pow(y4 - k->getPosition().y, 2));
float distSqr3 = (pow(x3 - k->getPosition().x, 2) + pow(y3 - k->getPosition().y, 2));
float distSqr2 = (pow(x2 - k->getPosition().x, 2) + pow(y2 - k->getPosition().y, 2));
float distSqr1 = (pow(x1 - k->getPosition().x, 2) + pow(y1 - k->getPosition().y, 2));
__m128 distances = _mm_set_ps(distSqr1, distSqr2, distSqr3, distSqr4);
__m128 maxDistSqr = _mm_set1_ps(k->getMaxDistance() * k->getMaxDistance());
__m128 result = _mm_cmple_ps(distances, maxDistSqr);
一旦我得到结果变量,我就迷路了。如何使用刚刚得到的结果变量?我的计划是,如果评估的条件结果为真,则进行一些光照计算,然后在屏幕上绘制像素。在这种情况下,我如何解释真与假?
非常感谢您对正确方向的任何帮助!
【问题讨论】:
我不确定我是否完全理解您的问题;例如,您可以使用_mm_and_ps(distances, result)
将超出范围的值归零。
顺便说一句,也许这只是为了简洁,但你为什么不在 SSE 中做(x - x1)^2 + (y - y1)^2
?
【参考方案1】:
我的计划是,如果评估的条件为真,则进行一些光照计算,然后在屏幕上绘制像素。
那你真的别无选择,只能分支。
使用 SSE 进行条件测试的最大优势在于它允许您编写 无分支 代码,这可以大大提高速度。但在您的情况下,您几乎必须进行分支,因为如果我对您的理解正确,如果条件评估为 false,您永远不会希望在屏幕上输出任何内容。
我的意思是,我猜你可以无条件地(推测地)进行所有计算,然后只使用条件的结果来旋转像素值中的位,基本上导致你离开屏幕。这会给你无分支的代码,但这很愚蠢。分支错误预测会受到惩罚,但不会像所有计算和绘图代码那样昂贵。
换句话说,一旦您获得最终结果,您使用 SIMD 来利用的并行性就会耗尽。这只是一个简单的标量比较和分支。首先,您测试条件评估是否为真。如果没有,您将跳过进行照明计算和像素绘制的代码。否则,您将无法执行该代码。
棘手的部分是编译器不允许您在常规的旧if
语句中使用__m128
变量,因此您需要将result
“转换”为可以用作基础的整数为有条件的。最简单的方法是使用 _mm_movemask_epi8
内在函数。
所以你基本上会这样做:
__m128 distances = _mm_set_ps(distSqr1, distSqr2, distSqr3, distSqr4);
__m128 maxDistSqr = _mm_set1_ps(k->getMaxDistance() * k->getMaxDistance());
__m128 result = _mm_cmple_ps(distances, maxDistSqr);
if (_mm_movemask_epi8(result) == (unsigned)-1)
// All distances were less-than-or-equal-to the maximum, so
// go ahead and calculate the lighting and draw the pixels.
CalcLightingAndDraw(…);
这是有效的,因为_mm_cmple_ps
如果比较为真,则将每个压缩双字设置为全 1,如果比较为假,则全为 0。 _mm_movemask_epi8
然后将其折叠成一个整数大小的掩码并将其移动到一个整数值。然后,您可以在正常的条件语句中使用该整数值。
注意:使用 Clang 和 ICC,您可以将 __m128
值传递给 _mm_movemask_epi8
内在函数。在 GCC 上,它坚持使用 __m128i
值。你可以通过演员来处理这个问题:_mm_movemask_epi8((__m128i)result)
。
当然,我在这里假设您仅在 所有 距离小于或等于最大距离时才进行绘图。如果你想独立地处理这四个距离中的每一个,那么你需要在掩码上添加更多的条件测试:
__m128 distances = _mm_set_ps(distSqr1, distSqr2, distSqr3, distSqr4);
__m128 maxDistSqr = _mm_set1_ps(k->getMaxDistance() * k->getMaxDistance());
__m128 result = _mm_cmple_ps(distances, maxDistSqr);
unsigned condition = _mm_movemask_epi8(result);
if (condition != 0)
// One or more of the distances were less-than-or-equal-to the maximum,
// so we have something to draw.
if ((condition & 0x000F) != 0)
// distSqr1 was less-than-or-equal-to the maximum
CalcLightingAndDraw(distSqr1);
if ((condition & 0x00F0) != 0)
// distSqr2 was less-than-or-equal-to the maximum
CalcLightingAndDraw(distSqr2);
if ((condition & 0x0F00) != 0)
// distSqr3 was less-than-or-equal-to the maximum
CalcLightingAndDraw(distSqr3);
if ((condition & 0xF000) != 0)
// distSqr4 was less-than-or-equal-to the maximum
CalcLightingAndDraw(distSqr4);
这不会产生非常高效的代码,因为您必须执行很多条件测试和分支操作。您也许可以继续并行化主if
块内部 的一些照明计算。我不能确定这是否可行,因为我没有关于您的算法/设计的足够详细信息。
否则,如果您无法从绘图代码中获得更多的并行性,那么使用显式 SSE 内在函数在这里不会给您带来太多好处。您能够并行化 一个 比较 (_mm_cmple_ps
),但设置该比较的开销 (_mm_set_ps
,可能会编译为 vinsertps
或 unpcklps
+movlhps
指令,假设输入已经在 XMM 寄存器中)将抵消您可能获得的任何微不足道的收益。可以说你也可以像这样编写代码:
float maxDistSqr = k->getMaxDistance() * k->getMaxDistance();
if (distSqr1 <= maxDistSqr)
CalcLightingAndDraw(distSqr1);
if (distSqr2 <= maxDistSqr)
CalcLightingAndDraw(distSqr2);
if (distSqr3 <= maxDistSqr)
CalcLightingAndDraw(distSqr3);
if (distSqr4 <= maxDistSqr)
CalcLightingAndDraw(distSqr4);
【讨论】:
关于从__m128
到__m128i
的演员,你提到GCC 需要(MSVC 也需要),有内在函数就是为了这个。在这种情况下,它将是_mm_castps_si128
。有点冗长,但就是这样。我认为检测在整数和浮点指令之间切换可能出现的性能问题是/很有帮助的。以上是关于带有 SSE 的条件语句的主要内容,如果未能解决你的问题,请参考以下文章