C ++ / VS2008:宏与内联函数的性能

时间:2010-09-28 06:31:43

标签: c++ function macros inline

所有

我正在编写一些性能敏感的代码,包括一个可以执行大量交叉产品的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刻度,这是更好但仍然不一样。

所以我呼吁你们所有人:这是真的吗?我在某个地方犯过一个愚蠢的错误吗?或者内联函数真的这么慢 - 如果是这样,为什么?

4 个答案:

答案 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,但仍然无法解决所有问题。