一方面,我读到或听到“函数调用很昂贵”并且它们会影响效率(例如,on Nicholas Zakas' Google tech talk)。
然而,另一方面,似乎认为函数/方法最好保持简短,并且应该只执行一项任务,如here中普遍接受的那样。
我在这里遗漏了什么,或者这两条建议是否相互冲突?是否有一些经验法则允许人们保持禅宗般的平衡?
答案 0 :(得分:42)
适用于所有语言的一般规则是:保持函数(方法,程序)尽可能小。当您添加适当的命名时,您将获得非常易于维护和可读的代码,您可以轻松地将注意力集中在一般情况上,并向下钻取以获得有趣的细节。通过一种巨大的方法,您始终可以查看详细信息并隐藏大图。
此规则专门适用于可以执行花哨的优化(如inlining)或发现哪些方法不是真正虚拟的聪明语言和编译器,因此不需要双重调度。
返回JavaScript - 这在很大程度上依赖于JavaScript引擎。在某些情况下,我会期望合适的引擎内联函数,避免执行成本,特别是在紧密循环中。但是,除非遇到性能问题,否则请选择较小的功能。可读性更重要。
答案 1 :(得分:12)
在一个完美的世界中,没有错误(因为代码只是神奇地修复了自己),并且要求从第一天开始就被冻结了,所以有可能拥有巨大的无所不能的功能。
但在这个世界上,它变得过于昂贵 - 而且不仅仅是“人月”。 Nicholas Zakas撰写a brilliant article描述了软件开发人员目前面临的大多数挑战。
过渡可能看起来有点人为,但我的观点是“一个功能 - 一个任务”的方法更易于维护和灵活 - 换句话说,它最终使得两个开发人员和客户满意。
但这并不意味着您不会尽可能少地使用函数调用:只要记住它不是最重要的。
答案 2 :(得分:4)
我的经验法则是,如果它不仅仅是一个充满长线的屏幕,那么它是时候将一个功能分解成更小的部分了,尽管我的许多功能自然最终比没有被“人为”分割的那样小一些。而且我通常会留下足够的空白区域,即使是满屏也不是很多代码。
我尝试让每个函数只执行一个任务,但是然后一个任务可能是“重新绘制屏幕”,这将涉及在单独的函数中实现的一系列子任务,这些子任务又可以将它们自己的子任务分开功能
开始感觉自然(对我而言)的可读性(因此易于维护)我不担心函数调用是昂贵的,除非特定的代码片段在测试时表现不佳 - 然后我会考虑带来事物在线上(特别是在循环中,从嵌套循环开始)。虽然已经说过,有时你只是知道一段特定的代码不能很好地执行并在进行测试之前重写它......
我会避免“过早优化”,尤其是使用可能在幕后进行相同优化的智能编译器的语言。当我第一次启动C#时,我被告知,由于JIT编译器的工作方式,在运行时将代码分解为较小的函数可能较少。
回到我的一个屏幕完整规则,在JavaScript中,通常有嵌套函数(由于JS闭包的工作方式),如果我使用另一个,这可以使包含函数比我想要的更长语言,所以有时最终的结果是妥协。
答案 3 :(得分:1)
函数调用总是很昂贵(特别是在循环中)并且内联不会像您想象的那样经常发生
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样式,运行时性能将使用调用堆栈大小线性降级,尽管看起来概念上很干净。
答案 4 :(得分:1)
对所有人来说:这更像是一种“评论”的感觉。确认。我选择使用“答案”的空间。请容忍。
@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美元,那么他们老板的时间价值就像我的一台计算机到三到四个小时的工作。在那种环境中:
与公司在需要的软件停止工作时损失的每小时美元相比如何?
当付费客户暂时无法使用业务合作伙伴生产的收缩包装软件时,这与失去的善意和声望相比如何?
还有很多其他问题。我会省略它们。
正如我解释的答案:可读性和可维护性统治着计算机性能。我的建议?相应地编写代码的第一个版本。我尊重的很多人说短功能有帮助。
完成代码并且不喜欢性能后,找到阻塞点。我尊重的很多人都说这些点从不你会期望它们。当你认识他们时工作。
所以双方都是对的。一些。
我吗?我猜我离开了某个地方。两分钱。