“函数调用很贵”与“保持函数很小”

一方面,我读到或听说“函数调用是昂贵的”,并且影响效率(例如, Nicholas Zakas的Google技术讲座 )。

但另一方面,似乎认为function/方法最好是保持简短,只能真正执行一项任务,正如在这里通常所接受的那样。

我在这里错过了些什么,或者这两条build议是否相互矛盾呢? 是否有一些经验法则可以让人们保持禅宗般的平衡?

适用于所有语言的一般规则是:保持函数(方法,程序)尽可能小。 当你添加适当的命名,你得到非常可维护和可读的代码,您可以轻松地关注一般图片,并深入到有趣的细节。 有一个巨大的方法,你总是在看细节,大图是隐藏的。

这个规则特别适用于聪明的语言和编译器,可以做内嵌的优化或发现哪些方法不是真正的虚拟,因此不需要双重调度。

返回JavaScript – 这严重依赖于JavaScript引擎。 在某些情况下,我希望像样的引擎内联函数,避免执行的代价,特别是在紧密的循环中。 但是,除非出现性能问题,否则更喜欢较小的function。 可读性更重要。

在完美的世界里,没有任何错误(因为代码只是神奇的修复),并且需求从第一天开始就被冻结,所以可能会有巨大的无所不能的function。

但在这个世界上,它变得太贵了,而不仅仅是“人月”。 Nicholas Zakas撰写了一篇精彩的文章,描述了目前软件开发人员面临的大部分挑战。

这个过渡看起来有些虚幻,但我的观点是,“一个function – 一个任务”的方法更具可维护性和灵活性,换句话说,最终让开发人员和客户都感到满意。

但是,这并不意味着你不会尽力使用尽可能less的函数调用,只要记住它不是最重要的。

我的经验法则是,如果一个函数的长度超过一个屏幕长度,那么是时候将函数分解成更小的部分,尽pipe我的许多函数自然会比没有被人为地分割的函数小得多。 而且我通常留下足够的空白,甚至一个完整的屏幕也不是一个完整的代码。

我尝试让每个函数只执行一个任务,但是接下来的一个任务可能是“重新绘制屏幕”,这将涉及一系列在不同function中实现的子任务,而这些子任务又可能在不同的function中有自己的子任务。

为了可读性(因此便于维护),我已经开始了对自然(对我来说)的开始,我不担心函数调用是昂贵的,除非特定的代码在testing时执行的很糟糕 – 那么我会考虑把事情带回(特别是在循环中,从嵌套循环开始)。 虽然有时候你只是知道某个特定代码在执行testing之前不会很好地执行并重写它。

我会避免“不成熟的优化”,特别是使用智能编译器的语言,可能在幕后做相同的优化。 当我第一次启动C#时,我被告知,由于JIT编译器的工作方式,在运行时将代码分解成更小的函数可能会便宜。

回到我的一个完整的屏幕规则,在JavaScript中,通常有嵌套的函数(由于JSclosures工作的方式),这可以使包含函数比我想要的更长,如果我使用另一种语言,有时最终的结果是妥协。

对所有人来说:这更多的是“评论”的感觉。 确认。 我select了使用“答案”的空间。 请容忍。

@StefanoFratini:请把我的注意力放在你的工作上。 我想避免被批评。

这里有两种方法可以进一步改善你的文章中的代码:

  • 使用来自process.hrtime()的元组的两半。 它返回一个数组[秒,纳秒]。 你的代码使用元组(元素1)的纳秒部分,我不能发现它使用秒部分(元素0)。
  • 明确单位。

我可以匹配我的咆哮吗? 不知道。 这是Stephano代码的开发。 它有缺陷; 如果有人告诉我,我不会感到惊讶。 那样会好的。

 "use strict"; var a = function(val) { return val+1; } var b = function(val) { return val-1; } var c = function(val) { return val*2 } var time = process.hrtime(); var reps = 100000000 for(var i = 0; i < reps; i++) { a(b(c(100))); } time = process.hrtime(time) let timeWith = time[0] + time[1]/1000000000 console.log(`Elapsed time with function calls: ${ timeWith } seconds`); time = process.hrtime(); var tmp; for(var i = 0; i < reps; i++) { tmp = 100*2 - 1 + 1; } time = process.hrtime(time) let timeWithout = time[0] + time[1]/1000000000 console.log(`Elapsed time without function calls: ${ timeWithout } seconds`); let percentWith = 100 * timeWith / timeWithout console.log(`\nThe time with function calls is ${ percentWith } percent\n` + `of time without function calls.`) console.log(`\nEach repetition with a function call used roughly ` + `${ timeWith / reps } seconds.` + `\nEach repetition without a function call used roughly ` + `${ timeWithout / reps } seconds.`) 

这显然是Stephano的代码的后代。 结果是完全不同的。

 Elapsed time with function calls: 4.671479346 seconds Elapsed time without function calls: 0.503176535 seconds The time with function calls is 928.397693664312 percent of time without function calls. Each repetition with a function call used roughly 4.671479346e-8 seconds. Each repetition without a function call used roughly 5.0317653500000005e-9 seconds. 

像Stephano一样,我使用Win10和Node(v6.2.0)。

我承认这一点

  • “从透视angular度来看,在十亿分之一秒(十亿分之一,十亿分之一),光线传播大约12英寸。
  • “我们只是谈论几个纳秒(47至5),所以谁在乎百分比?
  • “有些algorithm每秒都会调用大量的函数调用,所以它们相加起来。”
  • “我们大多数开发人员不会使用这些algorithm,所以担心函数调用的次数对于我们大多数人来说是适得其反的。”

我会喋喋不休地谈论经济学的观点:我的电脑和之前的电脑每台的价格都不到400美元(美国)。 如果一个软件工程师每小时挣90到130美元,那么他们的老板的时间就是像我这样的一台计算机的工作时间,到他们工作的三到四个小时。 在这样的环境下:

那么当软件需要停止工作时,公司每小时会损失多less美元呢?

如果付费客户暂时不能使用业务合作伙伴生产的缩小包装软件,那么这与失去良好意愿和声望相比如何呢?

还有很多其他的问题。 我会省略它们

正如我解释的答案:可读性和可维护性统治计算机的性能。 我的build议? 相应地编写代码的第一个版本。 我尊重很多人说短的function帮助。

一旦你完成你的代码,不喜欢的性能,find瓶颈点。 我尊敬的许多人说,这些点从来没有你期望的地方。 工作时,当你知道他们。

所以双方都是对的。 一些。

我? 我想我会离开某个地方 两分钱。

函数调用总是很昂贵的(特别是在循环中),内联不会像你想像的那样频繁发生

Node.js(任何版本)附带的V8引擎应该广泛地内联,但实际上这个function受到很大的限制。

以下(不重要的)代码片段certificate了我的观点(Win10x64上的节点4.2.1)

 "use strict"; var a = function(val) { return val+1; } var b = function(val) { return val-1; } var c = function(val) { return val*2 } var time = process.hrtime(); for(var i = 0; i < 100000000; i++) { a(b(c(100))); } console.log("Elapsed time function calls: %j",process.hrtime(time)[1]/1e6); time = process.hrtime(); var tmp; for(var i = 0; i < 100000000; i++) { tmp = 100*2 + 1 - 1; } console.log("Elapsed time NO function calls: %j",process.hrtime(time)[1]/1e6); 

结果

 Elapsed time function calls: 127.332373 Elapsed time NO function calls: 104.917725 

性能下降+/- 20%

人们会期望V8 JIT编译器内联这些函数,但实际上abc可能会在代码中的其他地方被调用,并且不适合用V8获得的低挂水果内联方法

我已经看到很多代码(Java,Php,Node.js)由于方法或函数调用滥用而在生产中performance不佳:尽pipe如此,如果您编写代码Matryoshka风格,运行时间性能将随着调用堆栈大小线性降级 ,尽pipe看起来在概念上干净。