编辑2:程序计算时间减少16%!参见计算底部
我编写了一个N体模拟器,实现了Barnes-Hut算法。现在,我有一个名为CheckNode
的无辜功能。它很简单,并且不需要很长时间来计算,但问题是,它被称为数百万次次,因此它占用了每帧之间的大部分计算时间。
我对代码进行了分析,这个函数负责总计算时间的84.58%
,这只有10K粒子,当我用这个高达10倍时,这个函数使用越来越大的百分比。
现在这里是函数,右边的时间占红色的百分比。
现在这里有一些令人担忧的事情,就像一个简单的if语句取9.17%
而另一个if语句占用超过20%的计算时间!有没有,即使是最轻微的优化可以在这里完成,它将成倍增加数百万个函数调用,以使我的程序运行得更快?
编辑:
以下是CalculateForceNode
功能:
void CalculateForceNode(Body* bi, Node* bj) //bi is being attracted to bj. 15 flops of calculation
{
//vector from the body to the center of mass
double vectorx = bj->CenterOfMassx - bi->posX;
double vectory = bj->CenterOfMassy - bi->posY;
//c^2 = a^2 + b^2 + softener^2
double distSqr = vectorx * vectorx + vectory * vectory + Softener * Softener;
// ivnDistCube = 1/distSqr^(3/2)
double distSixth = distSqr * distSqr * distSqr;
double invDistCube = 1.0f / (sqrt(distSixth));
double Accel = (bj->TotalMass * invDistCube * _GRAV_CONST);
bi->AccelX += vectorx * Accel;
bi->AccelY += vectory * Accel;
}
编辑2:
优化结果
CheckNode
函数现在占用总计算时间的82.03%
(在1分37秒样本中测量),而不是先前占用84.58%
。
现在,逻辑告诉剩余的15%
计算时间与第二个程序的剩余18%
计算时间相同。所以这些相同的句点(它是相同的代码)占用了第一个程序的15%
和第二个程序的18%
。让时间完成其他代码为x
第一个程序花了1/0.15
= 6.666x
,第二个代码花了1/0.18
= 5.555x
。然后,您可以找到5.555x
计算为6.666x
的{{1}}分数,因此程序计算时间减少了~0.83
16% !
答案 0 :(得分:6)
我要尝试的第一件事是在你的一个条件中反转元素,替换:
if(withSqr / distanceSqr < nodeThresholdSqr || pNode->HasChildren == false)
使用:
if(pNode->HasChildren == false || (withSqr / distanceSqr < nodeThresholdSqr))
如果条件的第一部分为真pNode->HasChildren == false
,则第二部分(withSqr / distanceSqr < nodeThresholdSqr)
将永远不会执行(读取:已评估)。检查简单条件比对浮点数操作要快得多(在您的情况下除法)。你甚至可以把它提升到一个新的水平:*你需要在distanceSqr
时计算pNode->HasChildren == false
AT ALL吗?
编辑:更好:
if(pNode->HasChildren == false)
{
CalculateForceNode(pBody,pNode);
}
else
{
double distanceSqr = ((diffX * diffX) + (diffY * diffY));
double withSqr = pNode->width * pNode->width;
if(withSqr / distanceSqr < nodeThresholdSqr)
{
CalculateForceNode(pBody,pNode);
}
else
{//if not, repeat function with child
if(pNode->Child[0]->Bodies.size() > 0)
CheckNode(pNode->Child[0],pBody);
//..... - all the rest of your code
}
}
答案 1 :(得分:3)
根据花费的时间进行分析是不够的,您需要知道这段时间花在了什么上 - 换句话说使用更高级的分析器。
此外,您不会提及有关您正在使用的编译器或平台的任何信息。
对于使用9%时间的if语句,我不认为它花费在比较中,它用于获取数据。您有多个间接级别(使用指针访问数据,将您带到另一个指针,依此类推)。这对于缓存和分支预测是不好的,我猜你花时间从内存中获取数据或者由于分支未命中预测而进行无用的计算,而不是进行实际比较。
我注意到的另一个注意事项:if(pNode-&gt; HasChildren == false)那么您不需要为查找widthSqr而进行的所有计算。我认为您应该重构逻辑以首先检查它,如果条件为假,那么您可以计算widthSqr并继续您的逻辑。
答案 2 :(得分:2)
inline
函数Bodies.size()
或直接访问size
,这样就没有函数调用的开销(将所有需要的信息推送到堆栈并将其弹出需要时间)。 widthSqr
。可以在不在函数中分配width
时计算。CalculateForceNode
中检查Softener*Softener
是否可以预先计算。 sqrt
功能非常耗时。 sqrt
算法是迭代的,因此您可以通过减少迭代次数来牺牲速度的准确性,或者您可以使用查找表。您在CalculateForceNode
进行了两次相同的计算。
void CalculateForceNode(Body* bi, Node* bj)
{
//vector from the body to the center of mass
double vectorx = bj->CenterOfMassx - bi->posX;
double vectory = bj->CenterOfMassy - bi->posY;
//c^2 = a^2 + b^2 + softener^2
double distSqr = vectorx * vectorx + vectory * vectory...
vectorx,vectory and distSqr
已在CheckNode
中被diffX, diffY and distanceSqr
计算为CalculateForceNode
。手动内联整个函数{{1}}。
答案 3 :(得分:2)
由于函数被调用很多次,你应该通过手动内联代码来消除调用CalculateForceNode(...)
的开销。一旦你这样做,你会注意到其他技巧:
void CheckNode(Node* pNode, Body* pBody)
{
double diffX = (pNode->CenterOfMass - pBody->posX);
double diffY = (pNode->CenterOfMass - pBody->posY);
double distanceSqr = ((diffX * diffX) + (diffY * diffY));
double widthSqr = pNode->width * pNode->width;
if (widthSqr / distanceSqr < NodeThresholdSqr || pNode->hasChildren == false)
{
//vector from the body to the center of mass
double vectorx = pNode->CenterOfMassx - pBody->posX;
double vectory = pNode->CenterOfMassy - pBody->posY;
//c^2 = a^2 + b^2 + softener^2
double distSqr = vectorx * vectorx + vectory * vectory + Softener * Softener;
// ivnDistCube = 1/distSqr^(3/2)
double distSixth = distSqr * distSqr * distSqr;
double invDistCube = 1.0f / (sqrt(distSixth));
double Accel = (pNode->TotalMass * invDistCube * _GRAV_CONST);
pBody->AccelX += vectorx * Accel;
pBody->AccelY += vectory * Accel;
}
else
{
CheckChildren(pNode, pBody);
}
}
现在您可以看到diffX = vectorx
,diffY = vectory
,distSqr = distanceSqr*Softner*Softner
。重复使用已经进行的一些计算并预先计算任何可能的计算应该可以节省一些周期:
void CheckNode(Node* pNode, Body* pBody)
{
double diffX = (pNode->CenterOfMass - pBody->posX);
double diffY = (pNode->CenterOfMass - pBody->posY);
double distanceSqr = ((diffX * diffX) + (diffY * diffY));
double widthSqr = pNode->width * pNode->width;
double SoftnerSq = Softener * Softener; //precompute this value
if (widthSqr / distanceSqr < NodeThresholdSqr || pNode->hasChildren == false)
{
//c^2 = a^2 + b^2 + softener^2
double distSqr = distanceSqr + SoftnerSq;
// ivnDistCube = 1/distSqr^(3/2)
double distSixth = distSqr * distSqr * distSqr;
double invDistCube = 1.0f / (sqrt(distSixth));
double Accel = (pNode->TotalMass * invDistCube * _GRAV_CONST);
pBody->AccelX += diffX * Accel;
pBody->AccelY += diffY * Accel;
}
else
{
CheckChildren(pNode, pBody);
}
}
希望这适合你。
答案 4 :(得分:1)
交换if语句并将所有计算移到pNode->hasChildren == false
部分:
void CheckChildren(Node* pNode, Body* pBody)
{
if (pNode->Child[0]->Bodies.size() > 0)
CheckNode(...
}
void CheckNode(Node* pNode, Body* pBody)
{
if (pNode->hasChildren != false)
{
double diffX = (pNode->CenterOfMass - pBody->posX);
double diffY = (pNode->CenterOfMass - pBody->posY);
double distanceSqr = ((diffX * diffX) + (diffY * diffY));
double widthSqr = pNode->width * pNode->width;
if (widthSqr / distanceSqr < NodeThresholdSqr)
{
CalculateForceNode(pBody, pNode);
}
else
{
CheckChildren(pNode, pBody);
}
}
else
{
CheckChildren(pNode, pBody);
}
}