我有以下功能(来自开源项目"recast navigation"):
/// Derives the dot product of two vectors on the xz-plane. (@p u . @p v)
/// @param[in] u A vector [(x, y, z)]
/// @param[in] v A vector [(x, y, z)]
/// @return The dot product on the xz-plane.
///
/// The vectors are projected onto the xz-plane, so the y-values are ignored.
inline float dtVdot2D(const float* u, const float* v)
{
return u[0]*v[0] + u[2]*v[2];
}
我通过VS2010 CPU性能测试运行它,它向我展示了在这个函数u[0]*v[0] + u[2]*v[2]
的所有重铸代码库代码行中,CPU最热。
如何通过CPU优化(例如通过SSE或GLSL like GLM (if it is easier or faster and appropriate in such case))?
编辑:调用显示的上下文:
bool dtClosestHeightPointTriangle(const float* p, const float* a, const float* b, const float* c, float& h) {
float v0[3], v1[3], v2[3];
dtVsub(v0, c,a);
dtVsub(v1, b,a);
dtVsub(v2, p,a);
const float dot00 = dtVdot2D(v0, v0);
const float dot01 = dtVdot2D(v0, v1);
const float dot02 = dtVdot2D(v0, v2);
const float dot11 = dtVdot2D(v1, v1);
const float dot12 = dtVdot2D(v1, v2);
// Compute barycentric coordinates
const float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01);
const float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
const float v = (dot00 * dot12 - dot01 * dot02) * invDenom;
答案 0 :(得分:21)
在纸上尝试了一些事情后,我想出了可能为你工作的东西。它是SSE中函数的正确并行/矢量化实现。
但它确实需要进行数据重组,因为我们会同时对4个三角形进行并行处理。
我会逐步分解它并在这里和那里使用指令名称,但是请使用C内在函数(_mm_load_ps(),_ mm_sub_ps()等,它们在VC中的xmmintrin.h中) - 当我说寄存器这只是意味着__m128。
第1步。
我们根本不需要Y坐标,因此我们设置指向X和Z对的指针。每次调用至少提供4对(即总共4个三角形)。 我会将每个X和Z对称为顶点。
第2步。
使用MOVAPS(要求指针对齐到16位)将每个指针指向的前两个顶点加载到寄存器中。
从 a 加载的寄存器如下所示:[a0.x,a0.z,a1.x,a1.z]
第3步。
现在,使用单个减法指令,您可以一次计算2个顶点的增量( v0 , v1 , v2 )
计算 v0 , v1 和 v2 不仅适用于前两个三角形,还适用于后两个三角形! 正如我所说,每个输入应该提供总共4个顶点或8个浮点数。只需重复该数据的第2步和第3步。
现在我们有2对 vx 寄存器,每对包含2个三角形的结果。我将它们称为 vx_0 (第一对)和 vx_1 (第二对),其中 x 是0到2。 / p>
第4步。
Dot产品。为了并行化重心计算(稍后),我们需要4个三角形中每个三角形的每个点积的结果,在1个单个寄存器中。
例如,在您计算 dot01 的地方,我们会做同样的事情,但同时对于4个三角形。每个 v -register包含2个向量的结果,因此我们首先将它们相乘。
让我们说你的点积函数中的 u 和 v - 参数 - 现在是 v0_0 和 v1_0 < / i>(计算 dot01 ):
将 u 和 v 相乘得到:[(v0_0.x0)*(v1_0.x0),(v0_0.z0)*(v1_0.z0),( v0_0.x1)*(v1_0.x1),(v0_0.z1)*(v1_0.z1)]
由于 .x0 / .z0 和 .x1 / .z1 ,这可能看起来很混乱,但是查看在步骤2中加载的内容: a0 , a1 。
如果现在这仍然感觉模糊,拿起一张纸并从头开始写。
接下来,对于相同的点积,请对 v0_1 和 v1_1 (,即第二对三角形)进行乘法运算。 / p>
现在我们有2个寄存器,2个X&amp;每个Z对(总共4个顶点),相乘并准备加在一起形成4个独立的点积。 SSE3有一个完成此操作的指令,它被称为HADDPS:
xmm0 = [A,B,C,D] xmm1 = [E,F,G,H]
HADDPS xmm0,xmm1执行此操作:
xmm0 = [A + B,C + D,E + F,G + H]
需要X&amp;来自我们的第一个寄存器的Z对,来自第二个寄存器的Z对将它们加在一起并将它们存储在目标寄存器的第一,第二,第三和第四个组件中。 Ergo:此时我们已经为所有4个三角形获得了这个特殊的点积!
**现在对所有点积重复此过程: dot00 等。 **
第5步。
最后的计算(据我所知,所提供的代码)是重心的东西。这是代码中的100%标量计算。但是你的输入现在不是标量点积结果(即单浮点数),它们是SSE向量/寄存器,每个4个三角形都有一个点积。
因此,如果你通过使用对所有4个浮点运算的并行SSE运算来展平矢量化,那么你最终将得到1个寄存器(或结果),其中包含4个高度,每个三角形为1个。
由于我的午休时间已经过去了,因为我不会拼出这个,但考虑到设置/想法我已经给出了这是最后一步,不应该很难弄清楚。
我知道这个想法有点牵强,需要一些爱好来自它上面的代码,也许需要一些有铅笔和纸张的质量时间,但它会很快(如果你和你甚至可以添加OpenMP) #39; d like)。
祝你好运:)(并且原谅我的模糊解释,如果需要,我可以掀起函数=))
<强>更新强>
我已经编写了一个实现,并没有像我预期的那样,主要是因为Y组件超出了你最初粘贴在你的问题中的代码段(我查了一下)。我在这里所做的不仅仅是要求你重新排列XZ对中的所有点并按4进给它们,而且还要为4个指针(对于点A,B和C)提供4个指针。三角形。从当地的角度来看,这是最快的。我仍然可以修改它以要求来自被叫方的更少侵入性的更改,但请让我知道什么是可取的。
然后是一个免责声明:这个代码很简单就像地狱一样(我发现它与SSE方面的编译器工作得很好......他们可以重新组织,因为看起来合适,x86 / x64 CPU占用它们的份额太)。也是命名,它不是我的风格,它不是任何人,只是用你认为合适的方式做。
希望它有所帮助,如果不是,我很乐意再次讨论它。如果这是一个商业项目,那么我也可以选择让我参与其中;)
无论如何,我已将它放在pastebin上:http://pastebin.com/20u8fMEb
答案 1 :(得分:4)
您可以使用SSE指令实现单点产品,但结果不会比现在编写的代码快得多(甚至可能更慢)。您的重写可能会破坏正在帮助当前版本的编译器优化。
为了从重写SSE或CUDA中获得任何好处,您必须优化调用该点产品的循环。对于CUDA来说尤其如此,其中执行一个点积的开销很大。如果您向GPU发送了数千个向量来计算数千个点积,那么您只能看到加速。同样的想法适用于CPU上的SSE,但您可能会看到对较少数量的操作的改进。但它仍然不仅仅是一个点积。
最简单的尝试可能是g++ -ftree-vectorize
。 GCC将能够内联您的小功能,然后尝试为您优化循环(事实上它可能已经是,但没有SSE指令)。树矢量化器将尝试自动执行您建议手动执行的操作。它并不总是成功。
答案 2 :(得分:3)
SSE指令用于优化处理表示为整数或浮点数的大数据块的alghoritms。典型的大小是需要以某种方式处理的数百万和数十亿个数字。优化仅处理四个(或二十个)标量的函数是没有意义的。使用SSE获得的功能可能会因函数调用开销而丢失。一次调用函数处理的合理数字数量至少为千。使用SSE内在函数可以获得巨大的性能提升。但根据您提供的信息,很难根据您的需求为您提供具体建议。您应该编辑您的问题并提供更高级别的问题视图(位于您的callstack上的功能)。例如,每秒调用多少次dtClosestHeightPointTriangle方法并不明显?这个数字对客观判断过渡到SSE是否具有实用价值至关重要。组织数据也非常重要。理想情况下,您的数据应存储在尽可能少的线性内存段中,以便有效地利用CPU的缓存子系统。
答案 3 :(得分:2)
您要求提供算法的SSE版本,所以这里是:
// Copied and modified from xnamathvector.inl
XMFINLINE XMVECTOR XMVector2DotXZ
(
FXMVECTOR V1,
FXMVECTOR V2
)
{
#if defined(_XM_NO_INTRINSICS_)
XMVECTOR Result;
Result.vector4_f32[0] =
Result.vector4_f32[1] =
Result.vector4_f32[2] =
Result.vector4_f32[3] = V1.vector4_f32[0] * V2.vector4_f32[0] + V1.vector4_f32[2] * V2.vector4_f32[2];
return Result;
#elif defined(_XM_SSE_INTRINSICS_)
// Perform the dot product on x and z
XMVECTOR vLengthSq = _mm_mul_ps(V1,V2);
// vTemp has z splatted
XMVECTOR vTemp = _mm_shuffle_ps(vLengthSq,vLengthSq,_MM_SHUFFLE(2,2,2,2));
// x+z
vLengthSq = _mm_add_ss(vLengthSq,vTemp);
vLengthSq = _mm_shuffle_ps(vLengthSq,vLengthSq,_MM_SHUFFLE(0,0,0,0));
return vLengthSq;
#else // _XM_VMX128_INTRINSICS_
#endif // _XM_VMX128_INTRINSICS_
}
bool dtClosestHeightPointTriangle(FXMVECTOR p, FXMVECTOR a, FXMVECTOR b, FXMVECTOR c, float& h)
{
XMVECTOR v0 = XMVectorSubtract(c,a);
XMVECTOR v1 = XMVectorSubtract(b,a);
XMVECTOR v2 = XMVectorSubtract(p,a);
XMVECTOR dot00 = XMVector2DotXZ(v0, v0);
XMVECTOR dot01 = XMVector2DotXZ(v0, v1);
XMVECTOR dot02 = XMVector2DotXZ(v0, v2);
XMVECTOR dot11 = XMVector2DotXZ(v1, v1);
XMVECTOR dot12 = XMVector2DotXZ(v1, v2);
// Compute barycentric coordinates
XMVECTOR invDenom = XMVectorDivide(XMVectorReplicate(1.0f), XMVectorSubtract(XMVectorMultiply(dot00, dot11), XMVectorMultiply(dot01, dot01)));
XMVECTOR u = XMVectorMultiply(XMVectorSubtract(XMVectorMultiply(dot11, dot02), XMVectorMultiply(dot01, dot12)), invDenom);
XMVECTOR v = XMVectorMultiply(XMVectorSubtract(XMVectorMultiply(dot00, dot12), XMVectorMultiply(dot01, dot02)), invDenom);
}
XMVector2Dot取自xnamathvector.inl,我重命名并修改为在X / Z坐标上操作。
XNAMath是微软的一个很好的矢量化跨平台数学库;我通过导入sal.h标头在OS X上使用它(我不确定那里的许可问题,所以要注意)。
实际上,任何支持内在函数SSE 的平台都应支持它。
要注意的几件事情: