函数调用的成本是多少?

时间:2020-03-06 14:22:31  来源:igfitidea点击:

相比

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

在Windows上的C ++中。

解决方案

取决于该函数的功能,如果它对内存中的对象进行逻辑处理,它将在列表中排第二。如果它包括磁盘/网络访问权限,则在列表的下方。

比起简单的内存访问要稍微多一点,确实可以忽略不计。

与其他所有事物相比,列出的数量要少几个数量级。

任何操作系统上的几乎任何语言都应如此。

函数调用只是将内存中的帧指针移到堆栈上,并在此之上添加一个新帧。功能参数被移入本地寄存器以供使用,并且堆栈指针前进到堆栈的新顶部以执行功能。

与时间相比

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

通常,函数调用的速度将比内存访问慢一些,因为它实际上必须进行多次内存访问才能执行该调用。例如,对于大多数在x86上使用__stdcall的函数调用,需要多次压入和弹出堆栈。但是,如果内存访问是什至不在L2高速缓存中的页面,那么如果目标和堆栈都在CPU的内存高速缓存中,则函数调用会更快。

对于其他所有功能,函数调用的速度要快很多(许多)。

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

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

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

当我这样写的时候,这听起来当然太明显了。

难以回答,因为其中涉及许多因素。

首先,"简单内存访问"并不简单。由于以现代时钟速度运行,CPU可以比从芯片一侧到另一侧获得数字快两个数字(光速-这不只是一个好主意,这是法律)

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

然后,我们可以通过函数调用清除CPU指令流水线,这将以不确定的方式影响速度。

实际调用该函数但不完整执行该函数的成本?或者实际执行功能的成本?仅仅设置一个函数调用并不是一项昂贵的操作(要更新PC吗?)。但是很明显,一个函数完全执行的成本取决于该函数在做什么。

假设意思是调用本身的开销,而不是被调用者可能要执行的开销,那么它肯定比除"简单"内存访问之外的所有访问都要快得多,要快得多。

它可能比内存访问慢,但是请注意,由于编译器可以进行内联,因此函数调用开销有时为零。即使不是,至少在某些体系结构上,某些对指令高速缓存中已经存在的代码的调用可能比访问主(未缓存)存储器更快。这取决于在进行调用之前需要将多少个寄存器溢出到堆栈中,以及类似的事情。请咨询编译器和调用约定文档,尽管我们不大可能比拆卸发出的代码更快地弄清它。

还要注意的是,如果操作系统必须从磁盘将页面放入,则有时不是"简单"的内存访问,因此我们将需要很长时间才能完成。如果我们跳入当前在磁盘上分页的代码,则同样如此。

如果根本的问题是"我何时应该优化代码以最大程度地减少函数调用的总数?",那么答案是"非常接近永不"。

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

  • 缓存中的内存访问= 1
  • 缓存中的函数调用/返回= 2
  • 内存访问超出缓存= 10 .. 300
  • 传输本身可以很快
  • 涉及至少几千次操作,因为用户/系统阈值必须至少穿越两次;必须安排一个I / O请求,结果必须写回;可能分配了缓冲区...
  • 原始传输速度可能很高,但是另一台计算机上的某些进程必须完成实际工作

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

函数调用实际上是将参数复制到堆栈上(多存储器访问),寄存器保存,实际代码执行,最后是结果复制和寄存器还原(寄存器保存/还原取决于系统)。

所以..相对来说:

  • 函数调用>简单存储器访问。
  • 函数调用<<磁盘访问-与内存相比,它的价格可能高出数百倍。
  • 函数调用<<在另一台计算机上进行内存访问-网络带宽和协议是这里的主要杀手time。
  • 函数调用<<<<在另一台计算机上进行磁盘访问-以上所有以及更多:)

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

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

别忘了C ++具有虚拟调用(价格昂贵得多,大约是x10),在Windows上,我们可以期望VS进行内联调用(按定义,成本为0,因为二进制中没有调用)

该链接在Google中经常出现。供以后参考,我在Con中运行了一个简短的程序,调用了函数,而答案是:"大约是内联成本的六倍"。下面是详细信息,请参见//底部的输出。更新:为了更好地将苹果与苹果进行比较,我将Class1.Method更改为返回" void",如下所示:public void Method1(){//返回0; }
内联速度仍然快了2倍:内联(平均):610毫秒;函数调用(平均):1380毫秒。因此,更新后的答案是"大约两次"。

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

命名空间FunctionCallCost
{
班级计划
{
静态void Main(string [] args)
{
Debug.WriteLine(" stop1");
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
{
类Class1
{

public Class1()
    {
    }

    public int Method1 ()
    {
        return 0;
    }
}

}

// 输出:
停止1
主要功能经过的时间(毫秒)为:595
停止2
助手类功能的经过时间(毫秒)为:3780

停止1
主要功能经过的时间(毫秒)是:592
停止2
助手类功能的经过时间(毫秒)为:4042

停止1
主要功能经过的时间(毫秒)是:626
停止2
助手类功能的经过时间(毫秒)为:3755