函数调用的成本是多少?

时间:2008-09-18 17:40:12

标签: c++ optimization architecture memory

相比
  • 简单的内存访问
  • 磁盘访问
  • 另一台计算机(在同一网络上)的内存访问
  • 另一台计算机(在同一网络上)的磁盘访问

在Windows上的C ++中。

15 个答案:

答案 0 :(得分:27)

相对时间(不应超过100倍; - )

  • cache = 1
  • 中的内存访问
  • 函数调用/返回缓存= 2
  • 内存访问缓存= 10 .. 300
  • disk access = 1000 .. 1e8(摊销取决于传输的字节数)
    • 主要取决于寻求时间
    • 传输本身可以非常快
    • 涉及至少几千个操作,因为用户/系统阈值必须至少超过两次;必须安排I / O请求,必须写回结果;可能的缓冲区被分配......
  • 网络电话= 1000 .. 1e9(摊销取决于传输的字节数)
    • 与disk i / o相同的参数
    • 原始传输速度可能相当高,但另一台计算机上的某些进程必须执行实际工作

答案 1 :(得分:19)

函数调用只是将内存中的帧指针移位到堆栈上,并在其上添加新帧。函数参数被移入本地寄存器以供使用,堆栈指针被提前到堆栈的新顶部以执行该函数。

与时间比较

函数调用〜简单内存访问
函数调用<磁盘访问
函数调用<另一台计算机上的内存访问
函数调用<另一台计算机上的磁盘访问

答案 2 :(得分:8)

与简单的内存访问相比 - 稍微多一些,实际上可以忽略不计。

与列出的所有其他内容相比 - 数量级更少。

这适用于任何操作系统上的任何语言。

答案 3 :(得分:4)

通常,函数调用将比内存访问稍慢,因为它实际上必须执行多次内存访问才能执行调用。例如,对于在x86上使用__stdcall的大多数函数调用,需要多次推送和弹出堆栈。但是如果你的内存访问是一个甚至不在L2缓存中的页面,那么如果目标和堆栈都在CPU的内存缓存中,那么函数调用可以快得多。

对于其他一切,函数调用的速度要快很多(<)。

答案 4 :(得分:3)

很难回答,因为涉及很多因素。

首先,“简单内存访问”并不简单。因为在现代时钟速度下,CPU可以比从芯片的一侧到另一侧更快地添加两个数字(光速 - 这不仅仅是一个好主意,而是法律)

那么,该函数是在CPU内存缓存中调用的吗?内存访问是否也在进行比较?

然后我们有函数调用将清除CPU指令管道,这将以非确定的方式影响速度。

答案 5 :(得分:3)

假设你的意思是调用本身的开销,而不是被调用者可能做的事情,它肯定比“简单”的内存访问远远快得多。

它可能比内存访问慢,但请注意,由于编译器可以进行内联,因此函数调用开销有时为零。即使不是这样,在某些体系结构中至少可能对指令缓存中已有的代码进行调用可能比访问主(未缓存)内存更快。这取决于在进行调用之前需要将多少寄存器溢出来堆叠,以及那种事情。请参阅编译器和调用约定文档,尽管你不太可能比拆解发出的代码更快地解决它。

另请注意,“简单”内存访问有时不会 - 如果操作系统必须从磁盘中引入页面,那么您需要等待很长时间。如果你跳转到当前在磁盘上翻页的代码,情况也是如此。

如果基本问题是“我应该何时优化我的代码以最小化函数调用的总数?”,那么答案就是“非常接近”。

答案 6 :(得分:2)

此链接在Google中出现很多。为了将来参考,我在C#中运行了一个关于函数调用成本的简短程序,答案是:“大约是内联成本的六倍”。以下是详细信息,请参阅底部的输出。更新:为了更好地比较苹果和苹果,我更改了Class1.Method以返回'void',如下所示:public void Method1(){// return 0; }
内联速度提高了2倍:内联(平均):610毫秒;函数调用(avg):1380 ms。因此更新的答案是“大约两次”。

使用System; 使用System.Collections.Generic; 使用System.Linq; 使用System.Text; 使用System.Diagnostics;

命名空间FunctionCallCost {     课程     {         static void Main(string [] args)         {             的Debug.WriteLine( “停止1”);             int iMax = 100000000; // 100M             DateTime funcCall1 = DateTime.Now;             秒表sw = Stopwatch.StartNew();

        for (int i = 0; i < iMax; i++)
        {
            //gives about 5.94 seconds to do a billion loops, 
          // or 0.594 for 100M, about 6 times faster than
            //the method call.
        }

        sw.Stop(); 

        long iE = sw.ElapsedMilliseconds;

        Debug.WriteLine("elapsed time of main function (ms) is: " + iE.ToString());

        Debug.WriteLine("stop2");

        Class1 myClass1 = new Class1();
        Stopwatch sw2 = Stopwatch.StartNew();
        int dummyI;
        for (int ie = 0; ie < iMax; ie++)
        {
          dummyI =  myClass1.Method1();
        }
        sw2.Stop(); 

        long iE2 = sw2.ElapsedMilliseconds;

        Debug.WriteLine("elapsed time of helper class function (ms) is: " + iE2.ToString());

        Debug.WriteLine("Hi3");


    }
}

//这里是第1类 使用系统; 使用System.Collections.Generic; 使用System.Linq; 使用System.Text;

命名空间FunctionCallCost {     class Class1     {

    public Class1()
    {
    }

    public int Method1 ()
    {
        return 0;
    }
}

}

//输出: 停止1 主函数的经过时间(ms)为:595 停止2 辅助类功能(ms)的经过时间为:3780

STOP1 主函数的经过时间(ms)为:592 停止2 辅助类功能(ms)的经过时间为:4042

STOP1 主函数的经过时间(ms)为:626 停止2 辅助类功能(ms)的经过时间为:3755

答案 7 :(得分:1)

实际调用该函数但不完全执行它的成本?或实际执行功能的成本?简单地设置函数调用并不是一项代价高昂的操作(更新PC?)。但很明显,完全执行函数的成本取决于函数的作用。

答案 8 :(得分:1)

我们不要忘记C ++有虚拟调用(显着更昂贵,约为x10),而在WIndows上你可以期待VS内联调用(按定义为0,因为二进制文件中没有调用)

答案 9 :(得分:0)

取决于该函数的作用,如果它在内存中对象执行逻辑,它将在列表中排名第二。如果它包含磁盘/网络访问,则在列表的下方。

答案 10 :(得分:0)

函数调用通常只涉及几个内存副本(通常是寄存器,所以它们不应占用太多时间)然后是跳转操作。这将比内存访问慢,但比上面提到的任何其他操作更快,因为它们需要与其他硬件进行通信。对于任何OS /语言组合,通常也应如此。

答案 11 :(得分:0)

如果函数在编译时内联,则函数的成本变为等于0。

0当然是,没有函数调用你会得到什么,即:自己内联。

当我这样写时,这当然听起来过于明显。

答案 12 :(得分:0)

函数调用的成本取决于体系结构。 x86相当慢(每个函数参数有几个时钟加上一个时钟左右),而64位则要少得多,因为大多数函数参数都是在寄存器而不是堆栈中传递的。

答案 13 :(得分:0)

函数调用实际上是堆栈上的参数副本(多个内存访问),寄存器保存,实际代码执行,最后是结果复制和寄存器恢复(寄存器保存/恢复取决于系统)。

所以......说起来相对:

  • 功能调用&gt;简单的内存访问。
  • 函数调用&lt;&lt;磁盘访问 - 与内存相比,它可能要贵几百倍。
  • 函数调用&lt;&lt;另一台计算机上的内存访问 - 网络带宽和协议是这里的重要时间杀手。
  • 函数调用&lt;&lt;&lt;另一台计算机上的磁盘访问 - 以上所有以及更多:)

答案 14 :(得分:0)

只有内存访问比函数调用更快。

但是如果使用内联优化的编译器(对于GCC编译器,而不仅仅在使用优化级别3(-O3)时激活),则可以避免调用。