函数调用的成本是多少?
Posted
技术标签:
【中文标题】函数调用的成本是多少?【英文标题】:What is the cost of a function call? 【发布时间】:2010-09-10 19:44:17 【问题描述】:相比
简单的内存访问 磁盘访问 另一台计算机上的内存访问(在同一网络上) 另一台计算机上的磁盘访问(在同一网络上)在 Windows 上的 C++ 中。
【问题讨论】:
【参考方案1】:相对时间(偏差不应超过 100 倍;-)
缓存中的内存访问 = 1 缓存中的函数调用/返回 = 2 内存访问超出缓存 = 10 .. 300 磁盘访问 = 1000 .. 1e8(摊销取决于传输的字节数) 主要取决于寻道时间 传输本身可以非常快 涉及至少几千个操作,因为用户/系统阈值必须至少超过两次;一个 I/O 请求必须被调度,结果必须被写回;可能分配了缓冲区... 网络调用 = 1000 .. 1e9(摊销取决于传输的字节数) 与磁盘 i/o 相同的参数 原始传输速度可能相当高,但其他计算机上的某些进程必须完成实际工作【讨论】:
都同意了,除了我知道的缓存外内存访问大约是缓存访问的 200~300 倍。 已更改。 10... 范围是包括一级缓存。另外,我不想给人一种磁盘访问可以和内存访问一样快的印象。情况并没有那么糟糕;-) 在你的缓存图中 - 你没有忘记参数处理吗?【参考方案2】:函数调用只是将内存中的帧指针转移到堆栈上,并在其上添加一个新帧。函数参数被移到本地寄存器中使用,堆栈指针前进到新的堆栈顶部以执行函数。
与时间相比
函数调用~简单的内存访问 函数调用 函数调用 函数调用
【讨论】:
您真的是指“堆栈指针的移位”吗? “指令指针的移位”对我来说更合适。我将其描述为:IP 被压入堆栈 IP 被设置为函数地址 SP 被高级以保留局部变量空间(可能包括堆栈帧的创建) 感谢您的澄清!我将为将来浏览的任何人更新我的答案【参考方案3】:与简单的内存访问相比 - 稍微多一点,真的可以忽略不计。
与列出的其他所有内容相比 - 数量级要小。
这应该适用于任何操作系统上的任何语言。
【讨论】:
【参考方案4】:一般来说,函数调用会比内存访问稍慢,因为它实际上必须执行多次内存访问才能执行调用。例如,在 x86 上使用 __stdcall 的大多数函数调用都需要多次推送和弹出堆栈。但是,如果您的内存访问的是一个甚至不在 L2 缓存中的页面,那么如果目标和堆栈都在 CPU 的内存缓存中,则函数调用会快得多。
对于其他一切,函数调用要快很多(很多)数量级。
【讨论】:
【参考方案5】:很难回答,因为涉及的因素很多。
首先,“简单内存访问”并不简单。由于在现代时钟速度下,CPU 可以将两个数字相加,而不是从芯片的一侧到另一侧获得一个数字(光速——这不仅仅是一个好主意,它是定律)
那么,函数是在 CPU 内存缓存中调用的吗?您是否也在比较它的内存访问?
然后我们有函数调用将清除 CPU 指令流水线,这将以非确定性的方式影响速度。
【讨论】:
在进行调用时不应在现代 CPU 上刷新管道。 CPU 仅在错误预测条件跳转(或发生中断或类似的不可预知事件时)才会刷新其管道。 “定律”:构成内存值的电脉冲以光速移动,需要几个 CPU 周期才能将 CPU 一端的三英寸移动到另一端. 这是法律!法律的定义是它是用来解释的。另一方面,规则是绝对的。这就是为什么橄榄球有法律而其他运动有规则的原因。 @James:三英寸??我想知道你正在使用什么样的怪物 CPU... @James:实际的处理器芯片要比这小得多。 Core i7 的 die 为 263mm^2,即(假设为正方形)约 0.64"x0.64"【参考方案6】:假设您指的是调用本身的开销,而不是被调用者可能执行的操作,那么它肯定比“简单”内存访问要快得多。
它可能比内存访问慢,但请注意,由于编译器可以进行内联,因此函数调用开销有时为零。即使不是,至少在某些架构上,对指令缓存中已经存在的代码的某些调用可能比访问主(未缓存)内存更快。这取决于在进行调用之前需要将多少寄存器溢出到堆栈,等等。请查阅您的编译器和调用约定文档,尽管您不太可能比反汇编发出的代码更快。
另请注意,有时不是“简单”的内存访问 - 如果操作系统必须从磁盘中引入页面,那么您将等待很长时间。如果您跳转到当前在磁盘上分页的代码,情况也是如此。
如果基本问题是“我什么时候应该优化我的代码以尽量减少函数调用的总数?”,那么答案是“非常接近永远不会”。
【讨论】:
【参考方案7】:此链接在 Google 中出现了很多。为了将来参考,我在 C# 中运行了一个关于函数调用成本的简短程序,答案是:“大约是内联成本的六倍”。以下是详细信息,请参阅底部的//输出。更新:为了更好地比较苹果和苹果,我将 Class1.Method 更改为 return 'void',如下所示: public void Method1 () // return 0; 尽管如此,内联速度还是快了 2 倍:内联(平均):610 毫秒;函数调用(平均):1380 毫秒。所以更新后的答案是“大约两倍”。
使用系统; 使用 System.Collections.Generic; 使用 System.Linq; 使用 System.Text; 使用 System.Diagnostics;
命名空间 FunctionCallCost 课堂节目 静态无效主要(字符串 [] 参数) 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 辅助类函数的运行时间(ms)为:3780
停止1 主要功能的经过时间(毫秒)为:592 停止2 辅助类函数的运行时间(ms)为:4042
停止1 主要功能的经过时间(毫秒)为:626 停止2 辅助类函数的运行时间(毫秒)为:3755
【讨论】:
【参考方案8】:实际调用函数但没有完全执行的成本?还是实际执行该功能的成本?简单地设置一个函数调用并不是一项昂贵的操作(更新 PC?)。但显然,一个函数完全执行的成本取决于该函数在做什么。
【讨论】:
【参考方案9】:我们不要忘记 C++ 有虚拟调用(明显更贵,大约 x10),在 WIndows 上,您可以期望 VS 内联调用(定义为 0 成本,因为二进制文件中没有剩余调用)
【讨论】:
【参考方案10】:取决于该函数的作用,如果它对内存中的对象进行逻辑处理,它将在您的列表中排名第二。如果它包括磁盘/网络访问,则在列表的下方。
【讨论】:
【参考方案11】:一个函数调用通常只涉及几个内存副本(通常是到寄存器中,所以它们不应该占用太多时间),然后是一个跳转操作。这将比内存访问慢,但比上述任何其他操作都快,因为它们需要与其他硬件进行通信。这通常适用于任何操作系统/语言组合。
【讨论】:
【参考方案12】:如果函数在编译时内联,则函数的成本等于 0。
0 当然是,如果没有函数调用,你会得到什么,即:自己内联它。
当我这样写时,这当然听起来太明显了。
【讨论】:
【参考方案13】:函数调用的成本取决于架构。 x86 的速度要慢得多(每个函数参数需要几个时钟加上一个时钟左右),而 64 位则要慢得多,因为大多数函数参数都是在寄存器中而不是在堆栈中传递的。
【讨论】:
【参考方案14】:函数调用实际上是将参数复制到堆栈上(多次内存访问),寄存器保存,实际代码执行,最后是结果复制和寄存器恢复(寄存器保存/恢复取决于系统)。
所以..相对来说:
函数调用 > 简单的内存访问。 函数调用 函数调用 函数调用【讨论】:
【参考方案15】:只有内存访问比函数调用快。
但是,如果编译器具有内联优化(对于 GCC 编译器,并且不仅在使用优化级别 3 (-O3) 时激活),则可以避免调用。
【讨论】:
以上是关于函数调用的成本是多少?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 VisualVM 获取每个函数的时间成本 [重复]
节省 58% IT 成本,调用函数计算超过 30 亿次,石墨文档的 Serverless 实践