【问题标题】:C++ / VS2008: Performance of Macros vs. Inline functionsC++ / VS2008:宏与内联函数的性能
【发布时间】:2026-02-12 19:35:02
【问题描述】:

全部,

我正在编写一些对性能敏感的代码,包括一个 3d 矢量类,它会产生大量的交叉积。作为一名资深的 C++ 程序员,我对宏的弊端和内联函数的各种好处了如指掌。长期以来,我一直认为内联函数的速度应该与宏大致相同。但是,在宏与内联函数的性能测试中,我发现了一个有趣的发现,我希望这是我在某个地方犯了一个愚蠢的错误的结果:我的函数的宏版本似乎比内联版本快 8 倍以上!

首先,一个简单矢量类的可笑精简版:

类 Vector3d { 民众: 双 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) {} 静态内联 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;

最终结果:在完全关闭优化的情况下,内联版本需要 3200 个滴答声,而宏版本需要 500 个滴答声……打开优化(/O2、最大化速度和其他速度调整)后,我可以得到内联版本降低到 1100 滴答声,这更好,但仍然不一样。

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

【问题讨论】:

  • 是的,更改代码而不检查它是否产生相同的结果是愚蠢错误之母。
  • 问题:您确实在启用优化的情况下执行了测试,对吗?编译器通常不会在调试中内联所有内容,因为内联函数不会出现在堆栈帧中,这使得调试变得更加困难。
  • “完全关闭优化后,内联版本需要 [更长]”。那么,当您关闭内联时,您会期待什么?

标签: c++ function macros inline


【解决方案1】:

注意发布此答案后,对原始问题进行了编辑以消除此问题。我会留下答案,因为它在多个层面上具有指导意义。

循环的作用不同!

如果我们手动展开宏,我们会得到:

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;

这很明显为什么第二个循环要快得多。

更新:这也是为什么宏是邪恶的一个很好的例子:)

【讨论】:

  • 哦,感谢您提供了这个完美示例,为什么人们应该总是使用大括号,即使是单线身体。绝对 +1,鹰眼!
  • 我不会说宏是邪恶的本身。当您粗心时,它们会咬您(例如,不在do { ... } while (0) 中包装多行宏)。
  • @DevSolar:当{ ... } 完美运行时,为什么将宏包装在do { ... } while(0) 中?强制用户在它后面加上一个分号很重要吗?
  • @Matthieu M.:是的。 1) 省略分号会导致编译器错误,强制宏调用模仿正确的函数调用。 (当您以后想将宏更改为函数时会更容易。)但更重要的是,2)尝试在if ... elseif 部分中使用您的{ ... } 宏。突然间,您不得输入分号...另请参阅c-faq.com/cpp/multistmt.html
  • 提交者回复:确认,在优化我发布的代码以提高可读性时,我删除了看似无关的大括号。在我的真实代码中,大括号在那里,并且循环确实完成了您期望它应该做的事情。我已经更新了样本。所以不幸的是,这不是答案。
【解决方案2】:

除了 Philipp 提到的,如果您使用 MSVC,您可以使用 __forceinline 或 gcc __attrib__ 等价物来纠正内联问题。

但是,还有一个可能的问题潜伏着,使用宏会导致宏的参数在每个点都被重新计算,所以如果你这样调用宏:

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,但仍然无法修复所有问题)

【讨论】:

    【解决方案3】:

    它还取决于优化和编译器设置。还要寻找您的编译器对始终内联/强制内联声明的支持。内联 和宏一样快。

    默认情况下,关键字是一个提示——强制内联/总是内联(大部分情况下)将控制权返回给程序员的关键字的初衷。

    最后,gcc(例如)可以在这样的函数未按指示内联时通知您。

    【讨论】:

      【解决方案4】:

      请注意,如果您使用 inline 关键字,这只是对编译器的提示。如果关闭优化,这可能会导致编译器不内联函数。您应该转到项目设置/C++/优化/并确保打开优化。 “内联函数扩展”你用了什么设置?

      【讨论】:

      • 开启完全优化,我的两个函数都返回时间 0,所以我怀疑整个循环都被优化了,因为它们没有做任何有用的事情。我将不得不再玩这个。
      • 您可能会访问结果(例如添加所有结果),然后输出最终总和或类似的东西。