一方面,我读到或听说“函数调用很昂贵”并且它们会影响效率(例如,在 Nicholas Zakas 的 Google 技术谈话中)。
然而,另一方面,似乎可以接受函数/方法最好保持简短,并且应该只真正执行一项任务,正如在此处普遍接受的那样。
我在这里遗漏了什么,或者这两条建议不是相互矛盾的吗?是否有一些经验法则可以让人们保持禅宗般的平衡?
一方面,我读到或听说“函数调用很昂贵”并且它们会影响效率(例如,在 Nicholas Zakas 的 Google 技术谈话中)。
然而,另一方面,似乎可以接受函数/方法最好保持简短,并且应该只真正执行一项任务,正如在此处普遍接受的那样。
我在这里遗漏了什么,或者这两条建议不是相互矛盾的吗?是否有一些经验法则可以让人们保持禅宗般的平衡?
适用于所有语言的一般规则是:使函数(方法、过程)尽可能小。当您添加正确的命名时,您将获得非常易于维护和可读的代码,您可以轻松地专注于一般图片并深入了解有趣的细节。使用一种巨大的方法,您总是在查看细节,而大局被隐藏了。
这条规则特别适用于聪明的语言和编译器,它们可以进行花哨的优化,例如内联或发现哪些方法不是真正的虚拟方法,因此不需要双重分派。
回到 JavaScript——这在很大程度上依赖于 JavaScript 引擎。在某些情况下,我希望体面的引擎能够内联函数,从而避免执行成本,尤其是在紧密循环中。但是,除非您有性能问题,否则更喜欢较小的函数。可读性更为重要。
在一个完美的世界里,没有错误(因为代码只是神奇地自我修复),并且从一开始就冻结了需求,也许可以忍受巨大的无所不能的功能。
但在这个世界上,它变得太昂贵了——而且不仅仅是在“人月”方面。Nicholas Zakas 写了一篇精彩的文章,描述了软件开发人员如今面临的大部分挑战。
这种转变可能看起来有些人为,但我的观点是,“一个功能 - 一项任务”方法更易于维护和灵活 - 换句话说,最终让开发人员和客户都感到高兴。
但是,这并不意味着您不会努力使用尽可能少的函数调用:请记住,这不是首要任务。
我的经验法则是,如果一个函数的行长超过一个屏幕,那么是时候将它分解成更小的部分了,尽管我的许多函数最终会自然而然地比它小一些,而不会被“人为”分割。而且我通常会留下足够的空白,即使是全屏也不是很多代码。
我尝试让每个函数只执行一项任务,但随后一项任务可能是“重绘屏幕”,这将涉及在单独的函数中实现的一系列子任务,而这些子任务又可能在单独的函数中具有自己的子任务。
从(对我来说)可读性(因此易于维护)感觉自然的东西开始,我不担心函数调用会很昂贵,除非特定代码在测试时表现不佳 - 然后我会考虑把东西带回来-line(特别是在循环中,从嵌套循环开始)。虽然曾经说过,有时您只知道某段特定的代码不会很好地执行并在进行测试之前重写它......
我会避免“过早的优化”,尤其是使用智能编译器的语言,这些编译器可能会在幕后进行相同的优化。当我第一次开始使用 C# 时,有人告诉我,由于 JIT 编译器的工作方式,将代码分解为更小的函数在运行时成本会更低。
回到我的一个全屏规则,在 JavaScript 中,嵌套函数很常见(由于 JS 闭包的工作方式),如果我使用另一种语言,这会使包含函数比我想要的更长,所以有时最终结果是妥协。
函数调用总是很昂贵(尤其是在循环中),并且内联并不像您想象的那样经常发生
Node.js(任何版本)附带的 V8 引擎应该进行广泛的内联,但实际上这种能力受到很大限制。
以下(微不足道的)代码片段证明了我的观点(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 编译器内联这些函数,但实际上a
,b
或者c
可能在代码中的其他地方被调用,并且不适合您使用 V8 获得的容易实现的内联方法。
我已经看到大量代码(Java、Php、Node.js)由于方法或函数调用滥用而在生产中表现不佳:如果您编写代码 Matryoshka 风格,运行时性能将随着调用堆栈大小线性下降,尽管在概念上看起来很干净。
致所有人:这更有“评论”的感觉。承认。我选择使用“答案”的空间。请忍耐。
@StefanoFratini:请把我的笔记作为你工作的基础。我想避免批评。
以下是进一步改进帖子中代码的两种方法:
我能配得上我的咆哮吗?不知道。这是 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)。
我承认以下论点
我会在经济论点上挂上帽子:我的电脑和之前的电脑每台都不到 400 美元(美国)。如果一个软件工程师每小时挣 90 到 130 美元,那么他们对老板的时间价值就是一台像我这样的计算机与他们三四个小时的工作的比率。在那种环境下:
这与公司需要的软件停止工作时每小时损失的美元相比如何?
当付费客户暂时无法使用业务合作伙伴生产的收缩包装软件时,这与失去的商誉和声望相比如何?
还有很多其他这样的问题。我将省略它们。
当我解释答案时:可读性和可维护性支配着计算机性能。我的建议?相应地编写代码的第一个版本。我尊敬的许多人都说短函数有帮助。
完成代码并且不喜欢性能后,请找到瓶颈。我尊敬的许多人说,这些点从来都不是你所期望的。当你了解他们时,工作他们。
所以双方都是对的。一些。
我?我猜我在某个地方。两分钱。