【问题标题】:Can I use setTimeout to create a cheap infinite loop?我可以使用 setTimeout 创建一个廉价的无限循环吗?
【发布时间】:2011-08-11 12:54:51
【问题描述】:
var recurse = function(steps, data, delay) {
    if(steps == 0) {
        console.log(data.length)
    } else {
        setTimeout(function(){
            recurse(steps - 1, data, delay);
        }, delay);
    }
};

var myData = "abc";
recurse(8000, myData, 1);

这段代码让我烦恼的是,我要传递一个字符串 8000 次。这会导致任何类型的内存问题吗?

另外,如果我使用 node.js 运行此代码,它会立即打印,这不是我所期望的。

【问题讨论】:

  • 将超时设置为 1(实际上小于 30 左右)会导致浏览器在主循环的下一次迭代中执行该函数。
  • @Emil:它因实现而异。某些实现不允许您将延迟设置为小于 5 或​​ 10,即使它们循环早于该时间。或者有人告诉我,我实际上并没有亲自去查看源代码。

标签: javascript node.js functional-programming memory-management


【解决方案1】:

如果您担心字符串被复制 8,000 次,请不要担心,字符串只有一个副本;传递的是参考。

更大的问题是调用函数时创建的对象(称为“执行上下文”的“变量绑定对象”)是否被保留,因为您正在创建一个闭包,并且它具有对变量的引用上下文的对象,因此只要在某处仍然引用闭包,它就会将其保存在内存中。

答案是:是的,但只有在计时器触发之前,因为一旦它什么都不做,就不再引用闭包,因此垃圾收集器可以回收它们。所以你不会有 8,000 个优秀的,只有一两个。当然,GC 运行的时间和方式取决于实现。

奇怪的是,就在今天早些时候,我们有another question 讨论了一个非常相似的主题;也可以在那里查看my answer

【讨论】:

  • 针对我的错误逻辑进行了编辑。这在技术上是封闭的吗?我不确定在递归场景中如何处理链式参数。此外,互联网似乎无法就闭包的定义达成一致。不过,我的总体想法是把你的脚放在完成范围的大门上。
  • @Erik:是的,从技术上讲,它是一个闭包(更多:Closures are not complicated)。 :-) 这使用旧的第 3 版规范术语,但您仍然可以遵循它。在更广泛的计算(和数学)世界中,“闭包”是具有内在绑定数据的函数的通用术语。在 JavaScript 中,被绑定的数据是对创建函数的作用域链顶部的变量绑定对象的引用(它具有指向先前执行上下文的 VBO 的链接,如果有的话等)。
【解决方案2】:

它立即打印,因为程序“立即”执行。在我的 Intel i5 机器上,整个操作耗时 0.07s,根据time node test.js.

对于内存问题,不管这是一个“廉价的无限循环”,你只需要试验和测量。

如果你想在 node 中创建一个异步循环,你可以使用process.nextTick。它会比setTimeout(func, 1) 更快。

【讨论】:

    【解决方案3】:

    一般 Javascript does not support tail call optimization,因此编写递归代码通常存在导致堆栈溢出的风险。如果你像这样使用setTimeout,它会有效地重置调用堆栈,因此堆栈溢出不再是问题。

    但性能将成为问题,因为每次调用 setTimeout 通常都需要相当长的时间(大约 10 毫秒),即使您将 delay 设置为 0。

    【讨论】:

      【解决方案4】:

      “1”是 1 毫秒。它也可能是一个 for 循环。 1 秒是 1000。我最近写了一些类似的东西来检查后端一批进程的进度,并将延迟设置为 500。如果我没记错的话,旧浏览器不会看到 1 到 15 毫秒之间的任何实际差异。我认为 V8 实际上可能比这更快。

      我认为在最后一次迭代完成之前不会对任何函数进行垃圾收集,但是这些新一代的 JS JIT 编译器比我更了解的那些要聪明得多,所以他们可能会看到超时后什么都没有发生,并从内存中提取这些参数。

      不管怎样,即使为这些参数的每个实例都保留了内存,也需要超过 8000 次迭代才能导致问题。

      防止更多内存密集型参数出现潜在问题的一种方法是传入一个带有所需参数的对象。那么我相信参数将只是对内存中某个位置的引用。

      比如:

      var recurseParams ={ steps:8000, data:"abc", delay:100 }  //outside of the function
      
      //define the function
      
      recurse(recurseParams);
      
      //Then inside the function reference like this:
      
      recurseParams.steps--
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-24
        • 1970-01-01
        • 1970-01-01
        • 2011-02-16
        相关资源
        最近更新 更多