在C++中执行宏可以提高性能

Do macros in C++ improve performance?

本文关键字:高性能 C++ 执行      更新时间:2023-10-16

我是C++的初学者,刚刚读到宏的工作原理是随时替换文本。在这种情况下,这是否意味着它使.exe运行得更快?这与内联函数有何不同?

例如,如果我有以下宏:

#define SQUARE(x) ((x) * (x))

和正常功能:

int Square(const int& x)
{
    return x*x;
}

和内联功能:

inline int Square(const int& x)
{
    return x*x;
}

这三者之间的主要区别是什么,尤其是内联函数和宏之间的区别?非常感谢。

如果可能的话,应该避免使用宏。内联函数总是更好的选择,因为它们是类型安全的。内联函数应该和宏一样快(如果它确实是由编译器内联的;请注意,inline关键字不是绑定的,只是对编译器的提示,如果无法内联,编译器可能会忽略它)。

PS:就风格而言,对于基本的参数类型,如intdouble,应避免使用const Type&。简单地使用类型本身,换句话说,使用

int Square(int x)

由于副本不会影响(甚至使性能变差)性能,请参阅例如此问题以了解更多详细信息。

宏翻译为:用模式B愚蠢地替换模式A。这意味着:一切都发生在编译器启动之前。有时它们会派上用场;但总的来说,它们应该避免。因为你可以做很多事情,以后在调试器中,你不知道发生了什么

此外:你的表演方式很好,很天真,可以说是友好的。首先你要学习这门语言(对于现代C++来说,这很难,因为有很多重要的概念和事情需要你去了解和理解)。然后你练习,练习,练习。然后,当您的现有应用程序出现性能问题时;然后进行分析以了解真正的问题

换言之:如果你对表演感兴趣,那么你问错了问题。你应该更多地担心架构(比如:潜在的瓶颈)、配置(系统中不同节点之间的延迟)等等。当然,你应该运用常识;并且不写明显浪费存储器或CPU周期的代码。但有时一段运行速度慢50%的代码。。。阅读和维护起来可能容易500%。如果执行时间是500ms,而不是250ms;这可能是完全可以的(除非那个特定的部分每分钟被调用一千次)。

在没有优化标志的编译器(clang++)上,平方函数不会内联。它生成的代码看起来像这个

4009f0:       55                      push   %rbp
4009f1:       48 89 e5                mov    %rsp,%rbp
4009f4:       89 7d fc                mov    %edi,-0x4(%rbp)
4009f7:       8b 7d fc                mov    -0x4(%rbp),%edi
4009fa:       0f af 7d fc             imul   -0x4(%rbp),%edi
4009fe:       89 f8                   mov    %edi,%eax
400a00:       5d                      pop    %rbp
400a01:       c3                      retq   

imul是执行工作的汇编指令,其余的是四处移动数据。调用它的代码看起来像

  400969:       e8 82 00 00 00          callq  4009f0 <_Z6squarei>

iI将-O3标志添加到内联中,该imul显示在C++代码中调用该函数的主函数中

0000000000400a10 <main>:
400a10:       41 56                   push   %r14
400a12:       53                      push   %rbx
400a13:       50                      push   %rax
400a14:       48 8b 7e 08             mov    0x8(%rsi),%rdi
400a18:       31 f6                   xor    %esi,%esi
400a1a:       ba 0a 00 00 00          mov    $0xa,%edx
400a1f:       e8 9c fe ff ff          callq  4008c0 <strtol@plt>
400a24:       48 89 c3                mov    %rax,%rbx
400a27:       0f af db                imul   %ebx,%ebx

这是一件合理的事情,为您的机器获得汇编语言的基本处理,并在源代码上使用gcc-s,或在二进制文件上使用objdump-D(就像我在这里所做的那样)来查看到底发生了什么

使用宏而不是内联函数可以得到非常类似的

0000000000400a10 <main>:
400a10:       41 56                   push   %r14
400a12:       53                      push   %rbx
400a13:       50                      push   %rax
400a14:       48 8b 7e 08             mov    0x8(%rsi),%rdi
400a18:       31 f6                   xor    %esi,%esi
400a1a:       ba 0a 00 00 00          mov    $0xa,%edx
400a1f:       e8 9c fe ff ff          callq  4008c0 <strtol@plt>
400a24:       48 89 c3                mov    %rax,%rbx
400a27:       0f af db                imul   %ebx,%ebx

请注意宏的许多危险之一:它的作用是什么?

x = 5; std::cout << SQUARE(++x) << std::endl; 

36?没有,42。它变成

std::cout << ++x * ++x << std::endl; 

变成6*7

不要因为别人告诉你不要关心优化而拖延。使用C或C++作为语言本身就是一种优化。试着弄清楚你是否在浪费时间,并且要理智。

宏只是执行文本替换来修改源代码。

因此,宏本身并不会影响代码的性能。用于设计和编码的技术显然会影响性能。因此,宏对性能的唯一影响是基于宏的作用(即编写宏要发出的代码)。

宏的最大危险在于它们不尊重范围。他们所做的改变是无条件的、跨职能的,诸如此类。编写宏时有很多微妙之处,可以使它们按预期运行(避免代码中意外的副作用,避免未定义的行为等)。这意味着使用宏的代码更难理解,也更难理解。

在最好的情况下,使用现代编译器,使用宏可以获得与内联函数相同的性能增益,但代价是增加代码错误行为的可能性。因此,您最好使用内联函数——与宏不同,它们是类型安全的,并且与其他代码一致工作。

现代编译器可能会选择不内联函数,即使您已将其指定为内联函数。如果发生这种情况,您通常不需要担心——在决定是否应该内联函数方面,现代编译器能够比大多数现代程序员做得更好。

只有当其参数本身是#defined常量时,使用这样的宏才有意义,因为计算将由预处理器执行。即便如此,也要仔细检查结果是否符合预期。

在处理经典变量时,(内联)函数形式应首选为:

  • 它是类型安全的
  • 它将以一致的方式处理用作参数的表达式。这不仅包括Peter引用的每/后增量的情况,而且当参数本身是计算密集型表达式时,使用宏形式强制对该参数求值两次(可能不一定求值为相同的值btw),而函数只求值一次

我不得不承认,我曾经为简单函数的快速原型设计编写过这样的宏,但这些年来让我失去的时间最终改变了我的想法!