繁体   English   中英

尾部呼叫优化javascript

[英]tail call optimization javascript

注意:这仅是为了学习和改善自己。 我知道可用的数组排序方法。 我只是想降低TCO的基础知识。

当前尝试使用递归进行排序算法。 但是,当我尝试处理大型数据集(+4000个对象)时,仍然出现堆栈溢出错误。 我正在尝试实施TCO。 我对这个概念还很陌生,但是我想知道它的要旨。 但是,我仍然收到堆栈溢出错误。

const sort = (arr, counter) => {
  if (!counter) {
    counter = arr.length - 1;
  }
  for (let n = 1; n <= counter; n++) {
    if(arr[n - 1] < arr[n]) {
      let placeHolder = arr[n];
      arr[n] = arr[n - 1];
      arr[n - 1] = placeHolder;
    }
  }
  counter -= 1;
  return counter === 0 ? arr : sort(arr, counter);
};

function sortRecursive(arr) {
  return sort(arr);
}

更新:

我设法使其正常运行,但是我不太明白为什么。 我设法毫无问题地处理了100,000次递归。 我必须移动检查计数器是否已定义的布尔值。 但是,我不太明白为什么它能起作用。

const sort = (arr, counter) => {
  if (!counter) {
    counter = arr.length - 1;
  }
  for (let n = 1; n <= counter; n++) {
    if(arr[n - 1] < arr[n]) {
      let placeHolder = arr[n];
      arr[n] = arr[n - 1];
      arr[n - 1] = placeHolder;
    }
  }
  counter -= 1;
  if (counter === 0) {
    return arr;
  } else {
    return sort(arr, counter);
  }
};

function sortRecursive(arr) {
  return sort(arr, arr.length - 1);
}

输出:

let firstArr = [];
let secondArr = [];

for (let x = 0; x < 100000; x++) {
  firstArr.push(Math.ceil(Math.random() * 100000));
  secondArr.push(Math.ceil(Math.random() * 100000));
}

sortRecursive(firstArr);
//Array[100000]

您可能知道,尾调用优化是一种编译器技术,可以通过不为每个递归调用分配更多的内存来使程序无限递归。

Javascript当前尚未进行尾调用优化,但是语言规范的ES2015标准包括TCO。 每次函数使用Javascript调用自身时,都会创建一个新的堆栈框架,分配新的内存,因此它最终将耗尽并崩溃。

有避免这种情况的技术,包括蹦床和不使用递归循环。 但是目前您不能无限递归地使用Javascript。

为什么需要递归(因为它超过了每个调用堆栈,所以它永远无法使用4000+个元素)? 你不能做:

const sort = (arr) => {
     var counter = arr.length;
     while(counter-->0){
       for (let n = 1; n <= counter; n++) {
           if(arr[n - 1] < arr[n]) {
              let placeHolder = arr[n];
              arr[n] = arr[n - 1];
              arr[n - 1] = placeHolder;
           }
        }
     }
     return arr;
}

如果您想进行某种递归,则可以使用qeue来保持堆栈为空(需要传递回调):

setTimeout(sort,0,arr,counter);//instead of sort(arr,counter);

顺便说一句,更容易,更快(因为它是本地实现的):

arr.sort((a,b)=>a-b);

确定要进行尾声优化吗?

这是使用更新后的代码进行的测试。 我更改的唯一内容是:

  1. 增加了'use strict'; 将代码置于严格模式。 某些将来支持TCO的浏览器可能要求使用严格模式才能使用TCO。
  2. 添加了console.trace()以在每个sort()调用上打印调用堆栈。
  3. 出于我上面的评论中提到的原因,将测试数组设置更改为使用Math.floor()而不是Math.ceil()
  4. 将数组长度更改为10。

运行代码片段之前,请打开开发者控制台,并观察调用堆栈的踪迹。

我在最新版本的Chrome 59.0.3071.109,Firefox 54.0和Edge 15.15063中对此进行了测试。 来自所有这些堆栈的堆栈跟踪显示了每次调用时调用堆栈的增长,表明没有尾部调用优化。

只是为了踢球,我还在length = 100000 Chrome中尝试了一下。 它运行了很长的时间(可能是一分钟左右),然后在堆栈达到大约10257个调用的深度时,由于堆栈溢出而失败。 为了进行比较,标准sort( function( a, b ) { return b - a; } )在大约5秒钟内完成。

这是一篇有关JavaScript尾部调用优化和相关主题的好文章 文章提到的一件事是,您可以通过使用--harmony_tailcalls命令行开关以及'use strict';来在node.js中获得TCO 'use strict';

 'use strict'; const sort = (arr, counter) => { console.trace(); if (!counter) { counter = arr.length - 1; } for (let n = 1; n <= counter; n++) { if(arr[n - 1] < arr[n]) { let placeHolder = arr[n]; arr[n] = arr[n - 1]; arr[n - 1] = placeHolder; } } counter -= 1; if (counter === 0) { return arr; } else { return sort(arr, counter); } }; function sortRecursive(arr) { return sort(arr, arr.length - 1); } let firstArr = []; let length = 10; for (let x = 0; x < length; x++) { firstArr.push(Math.floor(Math.random() * length)); } console.clear(); sortRecursive(firstArr); //firstArr.sort( function( a, b ) { return b - a } ); console.log( firstArr ); 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM