从函数中退出以减少激活对象比递归或调用嵌套函数更好吗?

Posted

技术标签:

【中文标题】从函数中退出以减少激活对象比递归或调用嵌套函数更好吗?【英文标题】:Is it better to exit from a Function to cut-down on Activation Objects, than recursively or calling nested functions? 【发布时间】:2013-01-30 18:46:44 【问题描述】:

javascript 和其他语言中,我听说过在调用方法/函数时会创建激活对象。为了优化和保持良好的性能,听起来开发人员应该限制调用的函数数量。

现在如果没有办法,你必须调用多个方法,是不是一个接一个地调用一个方法,像这样:

myFunc1();
myFunc2();
myFunc3();

// or...
var myFuncs = [myFunc1, myFunc2, myFunc3];
for(var a=0, aLen=myFuncs.length; a<aLen; a++) 
  myFuncs[a]();

或者,像这样嵌套它们:

function myFunc1() 
  // Do something...
  myFunc2();


function myFunc2() 
  // Do Something else...
  myFunc3();


function myFunc3() 
  //Do one last thing.


//Start the execution of all 3 methods:
myFunc1();

我假设使用第一种技术更有意义,因为它会回到以前的范围并释放最后一个激活对象...但是如果有人可以确认这一点, 我真的很想知道!

谢谢

【问题讨论】:

我相信按顺序调用这些方法会有最好的性能。循环中的额外变量存在开销,嵌套函数调用将使每个函数的激活对象保持活动状态,直到所有函数都完成执行。 以最合乎逻辑(和可读性)的方式构建代码。不要嵌套这些函数,除非它们实际上彼此依赖以特定顺序执行某些操作,例如,如果在myFunc1() 的一部分中,它实际上需要myFunc2() 的结果。仅当您确实注意到问题时才担心性能。 (注意:您展示的示例中没有任何递归。) 您使用嵌套函数调用 (CPS) 而不是默认线性函数的原因是什么,是否涉及一些异步代码? @nnnnnn 我意识到示例中没有递归,但递归也是一种相互嵌套的函数形式,可能会导致激活对象的深层链。 @Bergi 我主要是在问一般情况下哪个对性能更好。例如,如果您正在为解析/导出工具编写一些子例程(并且这些子例程仅相互依赖),您宁愿一个接一个地单独调用这些方法/在数组中迭代它们,还是将它们嵌套在每个方法的执行顺序?这基本上是我的问题。 【参考方案1】:

为了优化和保持良好的性能,听起来开发人员应该限制调用的函数数量。

是和不是。函数(或更一般地,子例程)在那里被调用,不这样做是没有意义的。如果您可以通过引入另一个函数来使您的代码更加干燥,那么就这样做。

唯一合理的地方是不使用它们的高性能循环运行数千次,几乎没有什么工作,而函数调用会增加显着的开销。不要尝试prematurely optimize!

此外,有些语言不能很好地处理递归,您需要将递归函数调用转换为循环,以防止堆栈溢出异常。但是,这种情况也很少见。

是一个接一个地调用方法,还是嵌套它们?

这取决于,因为这两种技术做不同的事情。对于 #1,只有 3 个 独立 函数,它们相互调用。相比之下,#2 定义了总是相互调用的函数——没有myFunc3,你就无法获得myFunc2。这是故意的吗?

如果是,那么这个嵌套没有问题。两个额外的堆栈层不会损害您的性能。

【讨论】:

好的,很高兴知道。我对 JavaScript 解释器/引擎在方法完成后释放内存的方式不是很熟悉。有没有一种对垃圾收集器更友好的方式来标记和丢弃(假设 JavaScript 有某种 GC 处理程序)? PS。我意识到 3 个子例程太小,无法真正衡量显着差异,但是如果这些方法被迭代数百次,并且某些任务比其他任务更昂贵(在我的情况下,我正在编写一些 JSFL 脚本来自动化 Flash PNG 导出),技术 #1 听起来更有效。【参考方案2】:

关于激活对象的信息,请参考http://dmitrysoshnikov.com/ecmascript/chapter-2-variable-object/#more-546

但是,这不是优化级别的问题,因为您列出的问题是 EXTREME 预优化的示例,您的时间不值得这种类型的投资。实际上,在您上面列出的示例中,当您单独查看激活对象时几乎没有节省。

不过,为了正确使用,我会尽可能地封装。如果一个函数不必进入全局作用域,并且可以存在于另一个函数的作用域内,那么这就是它应该被声明的地方。

例如,为了更好地确定范围。


var f2 = function() 


var f1 = function() 
  f2()


// is not as nice as:

var f1 = function() 
  var f2 = function()

  f2()


// or even better.. 

var f1 = function() 
  function() 
  ()  ; execute


【讨论】:

【参考方案3】:

责任分离:

private function myFunc1(): void




private function myFunc2(): void




private function myFunc3(): void




private function doAllFunc(): void

     myFunc1();
     myFunc2();
     myFunc3();

【讨论】:

Gary,您确实意识到您正在回答一个 Javascript 问题,对吧?这看起来像 C/C++,而不是 Javascript。 同意,但那是什么语言? @RobertK - 哦,拜托。 jbabey 这是 Pierre(提问者)非常熟悉的 ActionScript,因为它是他的主要语言。 问题是询问 javascript 的性能,而不是如何最好地用不同的语言编写他的任意示例。 投票点不会转换成美元是件好事。一些用户过于重视声誉积分。感谢 Gary,责任分离 是一个值得考虑的好方面。听起来也很有意义:易于阅读和理解的代码 = 易于执行的代码。 (但显然,它可能不是在所有情况下都是最快的!)

以上是关于从函数中退出以减少激活对象比递归或调用嵌套函数更好吗?的主要内容,如果未能解决你的问题,请参考以下文章

函数的递归

php 递归流程详细解析

递归函数

在 Julia 中使用递归调用减少 JIT 时间

从嵌套对象数组中递归创建字符串?

函数递归内置函数