所有
我正在编写一些性能敏感的代码,包括一个可以执行大量交叉产品的3d矢量类。作为一名长期的C ++程序员,我了解宏的弊端和内联函数的各种好处。我一直认为内联函数应该与宏的速度大致相同。然而,在性能测试宏与内联函数中,我发现了一个有趣的发现,我希望是因为我在某处犯了一个愚蠢的错误:我的函数的宏版本似乎是内联版本的8倍以上!
首先,一个简单的矢量类的荒谬修剪版本:
class Vector3d { public: double m_tX, m_tY, m_tZ; Vector3d() : m_tX(0), m_tY(0), m_tZ(0) {} Vector3d(const double &tX, const double &tY, const double &tZ): m_tX(tX), m_tY(tY), m_tZ(tZ) {} static inline void CrossAndAssign ( const Vector3d& cV1, const Vector3d& cV2, Vector3d& cV ) { cV.m_tX = cV1.m_tY * cV2.m_tZ - cV1.m_tZ * cV2.m_tY; cV.m_tY = cV1.m_tZ * cV2.m_tX - cV1.m_tX * cV2.m_tZ; cV.m_tZ = cV1.m_tX * cV2.m_tY - cV1.m_tY * cV2.m_tX; } #define FastVectorCrossAndAssign(cV1,cV2,cVOut) { \ cVOut.m_tX = cV1.m_tY * cV2.m_tZ - cV1.m_tZ * cV2.m_tY; \ cVOut.m_tY = cV1.m_tZ * cV2.m_tX - cV1.m_tX * cV2.m_tZ; \ cVOut.m_tZ = cV1.m_tX * cV2.m_tY - cV1.m_tY * cV2.m_tX; } };
这是我的示例基准测试代码:
Vector3d right;
Vector3d forward(1.0, 2.2, 3.6);
Vector3d up(3.2, 1.4, 23.6);
clock_t start = clock();
for (long l=0; l < 100000000; l++)
{
Vector3d::CrossAndAssign(forward, up, right); // static inline version
}
clock_t end = clock();
std::cout << end - start << endl;
clock_t start2 = clock();
for (long l=0; l<100000000; l++)
{
FastVectorCrossAndAssign(forward, up, right); // macro version
}
clock_t end2 = clock();
std::cout << end2 - start2 << endl;
clock_t start = clock();
for (long l=0; l < 100000000; l++)
{
Vector3d::CrossAndAssign(forward, up, right); // static inline version
}
clock_t end = clock();
std::cout << end - start << endl;
clock_t start2 = clock();
for (long l=0; l<100000000; l++)
{
FastVectorCrossAndAssign(forward, up, right); // macro version
}
clock_t end2 = clock();
std::cout << end2 - start2 << endl;
最终结果:优化完全关闭后,内联版本需要3200个刻度,宏版本500刻度...开启优化(/ O2,最大化速度和其他速度调整),我可以得到内联版本低至1100刻度,这是更好但仍然不一样。
所以我呼吁你们所有人:这是真的吗?我在某个地方犯过一个愚蠢的错误吗?或者内联函数真的这么慢 - 如果是这样,为什么?
答案 0 :(得分:12)
注意:发布此答案后,编辑了原始问题以解决此问题。我将留下答案,因为它在几个层面上具有指导意义。
循环的不同之处在于它们的作用!
如果我们手动扩展宏,我们得到:
for (long l=0; l<100000000; l++)
right.m_tX = forward.m_tY * up.m_tZ - forward.m_tZ * up.m_tY;
right.m_tY = forward.m_tZ * up.m_tX - forward.m_tX * up.m_tZ;
right.m_tZ = forward.m_tX * up.m_tY - forward.m_tY * up.m_tX;
注意大括号的缺席。所以编译器将其视为:
for (long l=0; l<100000000; l++)
{
right.m_tX = forward.m_tY * up.m_tZ - forward.m_tZ * up.m_tY;
}
right.m_tY = forward.m_tZ * up.m_tX - forward.m_tX * up.m_tZ;
right.m_tZ = forward.m_tX * up.m_tY - forward.m_tY * up.m_tX;
这显然是为什么第二个循环要快得多。
Udpate:这也是为什么宏是邪恶的一个很好的例子:)
答案 1 :(得分:0)
请注意,如果使用inline关键字,这只是编译器的提示。如果关闭优化,则可能导致编译器不内联函数。您应该转到项目设置/ C ++ /优化/并确保打开优化。您在“内联函数扩展”中使用了哪些设置?
答案 2 :(得分:0)
它还取决于优化和编译器设置。还要查找编译器对始终内联/强制内联声明的支持。内联与一样快。
默认情况下,关键字是一个提示 - 强制内联/总是内联(大部分)将控件返回给关键字初始意图的程序员。
最后,可以指示gcc(例如)在没有按照指示内联这样的函数时通知你。
答案 3 :(得分:0)
除了Philipp提到的内容之外,如果你使用的是MSVC,你可以使用__forceinline
或gcc __attrib__
等效来纠正带有内联的probelems。
然而,潜伏着另一个可能的问题,使用宏会导致宏的参数在每个点被重新评估,所以如果你像这样调用宏:
FastVectorCrossAndAssign(getForward(), up, right);
它将扩展为:
right.m_tX = getForward().m_tY * up.m_tZ - getForward().m_tZ * up.m_tY;
right.m_tY = getForward().m_tZ * up.m_tX - getForward().m_tX * up.m_tZ;
right.m_tZ = getForward().m_tX * up.m_tY - getForward().m_tY * up.m_tX;
当你关注速度时,不希望你想要:)(特别是如果getForward()
不是轻量级函数,或者每次调用都会增加一些,如果是内联函数,编译器可能修复调用量,前提是它不是volatile
,但仍然无法解决所有问题。