调用空函数有多浪费?

And*_*rew 4 c++ function

这三点都涉及相同的"空函数"问题:

  • 调用空函数时浪费了多少处理时间?
  • 调用100,甚至1000个空函数会产生巨大影响吗?
  • 如果这些空函数需要参数怎么办?

重要编辑调用空虚函数是一样的吗?


编辑 SO基本上你都说大多数情况下编译器会优化它.

但现在我很好奇,因为这仍然适用于这个问题.如果有这样的情况怎么办?在编译时不知道何时调用空函数?它会立即进入堆栈然后退出吗?

class base{
public:
    virtual void method() = 0;
};

class derived1: public base{
public:
    void method() { }
};

class derived2: public base{
public:
    void method(){ std::cout << " done something  "; }
};


int main()
{
    derived1 D1;
    derived2 D2;
    base* array[] = { &D1, &D2 };
    for( int i = 0; i < 1000; i ++)
    {
        array[0]->method();// nothing
        array[1]->method();// Something
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Sam*_*all 10

虽然它无疑会被任何体面的编译器完全优化,但这是一个比较.

void EmptyFunction() {}
void EmptyFunctionWithArgs(int a, int b, int c, int d, int e) {}
int EmptyFunctionRet() {return 0;}
int EmptyFunctionWithArgsRet(int a, int b, int c, int d, int e) {return 0;}

int main() {   
    EmptyFunction();
    EmptyFunctionWithArgs(0, 1, 2, 3, 4);
    EmptyFunctionRet();
    EmptyFunctionWithArgsRet(5, 7, 6, 8, 9);
}
Run Code Online (Sandbox Code Playgroud)

使用VC++ 11的调试模式下(如果有任何优化,它几乎没有)这里是调用的样子:

EmptyFunction();

call    ?EmptyFunction@@YAXXZ            ; EmptyFunction
Run Code Online (Sandbox Code Playgroud)

EmptyFunctionWithArgs(0, 1, 2, 3, 4);

push    4
push    3
push    2
push    1
push    0
call    ?EmptyFunctionWithArgs@@YAXHHHHH@Z    ; EmptyFunctionWithArgs
add     esp, 20                    ; 00000014H
Run Code Online (Sandbox Code Playgroud)

EmptyFunctionRet();

call    ?EmptyFunctionRet@@YAHXZ        ; EmptyFunctionRet
Run Code Online (Sandbox Code Playgroud)

EmptyFunctionWithArgsRet(5, 7, 6, 8, 9);

push    9
push    8
push    6
push    7
push    5
call    ?EmptyFunctionWithArgsRet@@YAHHHHHH@Z    ; EmptyFunctionWithArgsRet
add     esp, 20                    ; 00000014H
Run Code Online (Sandbox Code Playgroud)

int返回函数如下所示:

int EmptyFunctionRet()int EmptyFunctionWithArgsRet()

push   ebp
mov    ebp, esp
sub    esp, 192                ; 000000c0H
push   ebx
push   esi
push   edi
lea    edi, DWORD PTR [ebp-192]
mov    ecx, 48                    ; 00000030H
mov    eax, -858993460                ; ccccccccH
rep    stosd
xor    eax, eax
pop    edi
pop    esi
pop    ebx
mov    esp, ebp
pop    ebp
ret    0
Run Code Online (Sandbox Code Playgroud)

没有返回值的函数是相同的,除了它们没有xor eax, eax.

在发布模式下, VC++ 11不会打扰调用任何函数.但它确实为它们生成了定义.int返回函数如下所示:

int EmptyFunctionRet()int EmptyFunctionWithArgsRet()

xor    eax, eax
ret    0
Run Code Online (Sandbox Code Playgroud)

而非返回功能再次删除xor eax, eax.

您可以很容易地看到非优化函数非常臃肿,并且在多次循环中运行它们绝对会花费时间,而循环本身将在启用优化的情况下被删除.那么你自己是一个有利的人,优化发布.

你不应该担心这一点,因为它违反了"不要优化".如果您正在使用一个不起眼的平台(如27位MCU),并且您的编译器很垃圾,并且您的性能需要改进,那么您将分析您的代码以查看您需要改进的位置(它可能不是空函数调用) .就目前而言,这实际上只是一个练习,表明我们应该让编译器做多少工作.

编辑您的编辑:

有趣的是,虚函数是优化器的一个已知问题,因为它们在确定指针的实际位置时会遇到极大的麻烦.一些人为的例子可能会消除调用,但是如果通过指针进行调用,它可能不会被删除.

在VC++ 11发布模式下编译以下内容:

class Object {
public:
    virtual void EmptyVirtual() {}
};

int main() {   
    Object * obj = new Object();
    obj->EmptyVirtual();
}
Run Code Online (Sandbox Code Playgroud)

在设置代码中产生以下代码段:

mov     DWORD PTR [ecx], OFFSET ??_7Object@@6B@
mov     eax, DWORD PTR [ecx]
call    DWORD PTR [eax]
Run Code Online (Sandbox Code Playgroud)

哪个是对虚函数的调用.


Tho*_*ews 8

调用空函数时浪费了多少处理时间?

如果你可以使编译器保持空函数,则调用函数的开销和返回值的开销.调用的开销取决于参数的数量以及它们的传递方式.

实际开销取决于处理器以及编译器如何生成代码.
例如,处理器可能有足够的寄存器来包含每个参数,或者编译器可能必须在堆栈上推送参数,并在函数内的堆栈上访问它们.

Edit1:
处理器喜欢处理顺序数据指令.分支,跳转或函数调用强制处理器更改其指令指针,并可能重新加载其指令缓存.由于这些操作不处理数据指令,因此浪费时间并降低程序的性能.这些分支的性能下降仅在高性能程序(要处理的大量数据)或需要满足关键期限的程序(例如通过打印机移动纸张以避免卡纸)中才会显着.

调用100,甚至1000个空函数会产生巨大影响吗?

取决于您对"影响"的定义.根据台式机处理器的当前速度,可以在不到一秒的时间内完成100或1000次重复(更像是小于1毫秒).

您将花费更多时间来编译(翻译和链接)这些功能.

如果这些空函数需要参数怎么办?

就像我在顶部说的那样,取决于编译器和处理器.

调用空虚函数是一样的吗?

虚函数调用可能需要一个或两个以上的处理器指令; 取决于处理器.

个人资料不要假设
分析是你的朋友.分析将告诉您程序中每个函数花费了多少时间.

不要
微观优化您的顾虑被称为微优化.您可能正在优化或担心只能执行20%或更少时间的代码.或者它等待外部过程,如鼠标单击或文件I/O卡住.

抛开性能问题,关注程序的正确性和稳健性.无论运行速度有多快或多慢,运行不正确的程序都会很糟糕.无论崩溃多久,一个易于破解或崩溃的程序都毫无价值.

当用户抱怨或您的程序缺少关键的实时截止日期(例如从流输入源丢失数据)时,只担心性能.

  • 不能同意最后一段。我认为人们应该在设计系统时(可能在编码之前)考虑性能,而不是仅在遇到性能问题时才考虑。 (3认同)
  • @ShivendraMishra 根据我的经验,由不断担心优化的人们编写的大型代码库更难以阅读/维护,并且更有可能包含错误。在参与多个遗留项目后,我经常通过删除自定义容器定义(在作者看来可能是最佳的)、使用 std::algorithms 和删除指针/goto whichcraft 以支持更简单的方法来修复错误(甚至性能问题!)但是分支代码,在几乎所有情况下甚至都不是瓶颈! (2认同)

小智 5

试试g++-4.8 -O2 -S -o test.asm main.cpp && cat test.asm.你会看到它在-O2那里g++优化它.

例如,使用以下代码:

void empty_function(int a, int b, int c) { }

int main() {
    int a = 42, b = a, c = a;
    empty_function(a, b, c); 
}
Run Code Online (Sandbox Code Playgroud)

我没有在程序集输出中看到它被调用.


我按照pmr的建议做了以下的练习:

Object.h

#ifndef _OBJECT_H_
#define _OBJECT_H_

class Object {
 public:
  Object() { }
  void empty_function();
};

#endif
Run Code Online (Sandbox Code Playgroud)

Object.cpp

#include "Object.h"

void Object::empty_function() { }
Run Code Online (Sandbox Code Playgroud)

main.cpp

#include "Object.h"

int main() {
  Object object;
  object.empty_function();
}
Run Code Online (Sandbox Code Playgroud)

有趣的是,它确实调用了空函数:

call    __main
leaq    47(%rsp), %rcx
call    _ZN6Object14empty_functionEv
xorl    %eax, %eax
addq    $56, %rsp
ret
Run Code Online (Sandbox Code Playgroud)

  • 所以,现在,当您知道编译器非常智能时,请编写针对人类优化的代码 (3认同)
  • 有趣的练习:当函数的定义在不同的翻译单元或库中时会发生什么? (3认同)
  • @AndersLindén 在一般情况下,是的。然而,这样的建议对于性能关键代码并不是那么有用。编译器不是万能的。 (2认同)
  • @pmr添加`-flto`标志.编译器无法删除调用,因为它无法跨翻译单元查看.`-flto`将运行链接时优化传递以专门查看. (2认同)