我这里先不说和C之间的性能差距,而是展开说JavaScript递归优化问题,有人问我为什么不说性能差距,我:???这个问题就跟问地球为什么是圆的一样(明摆着)

传统的递归函数,比如:

function a(){

return a()

}

这是一个经典的递归,在函数a内部调用自身,调用栈的机制如下:每调用一个函数,解释器就会把该函数添加进调用栈并开始执行.

正在调用栈中执行的函数还调用了其它函数,那么新函数也将会被添加进调用栈,一旦这个函数被调用,便会立即执行.

当前函数执行完毕后,解释器将其清出调用栈,继续执行当前执行环境下的剩余的代码.

当分配的调用栈空间被占满时,会引发“堆栈溢出”错误.

这里我们做一个测试,尝试运行这个递归函数,找出调用栈深度:

let index = 0

function a(){

index += 1

console.log(index)

return a()

}

执行结果如下:

// 省略多行输出

> 12559

> 12560

> 12561

> 12562

> 12563

> Uncaught RangeError: Maximum call stack size exceeded

at console.X.t. [as log] (init.js:1)

这里可以看出,当递归深度达到12563的时候,调用栈爆掉了,平时使用是没有问题的,但是对于某些特殊或者极端情况,你又有相似需求的情况下,怎么样突破这个调用栈限制,做到永不爆栈呢?这里需要一个比较hack的写法, 我们对上面的递归函数进行一下改造:

let index = 0

async function a(){

await undefined

index += 1

console.log(index)

return await a()

}

我们来执行一下看一下结果:

// 省略多行输出

> 100590

> 100591

> 100592

// 省略多行输出

事实上,这个递归函数永远不会停止,它会一直执行下去,也没有爆栈,这是一个神奇的优化,可以让你写出非常大深度的递归而不会出现问题,这个优化的关键就是:

async function() { await undefined }

首先将递归函数改为async函数,然后在内部最好第一行 await undefined;

这个操作的原理就是:

1, async创建微任务队列,然后执行器执行当前队列.

2,此时遇到await undefined,其实这个写法等同于await (async () => {})和await Promise.resolve(setTimeout)这几种写法效果等同,用unedfined只是为了在实现同样效果的情况下更简洁,既然已经等同了,那就从这三个写法分析起.

3,此时,执行器发现第一个任务完全没有等待,马上完成了,但是执行器发现后面的任务是需要等待的,并不会马上完成.

4,这时候执行器为了microtask(也就是协程)调度的合理优化,不会让这个微任务队列始终占有这个execution,而是会把当前微任务队列转移到别的execution去执行(您几位走得慢,请去那边空闲的地方走).

5,转移execution带来的操作就是,因为没办法直接转移调用栈,所以会先将当前调用栈入堆,然后把任务队列转移到别的execution.

6,然后队列里面接下来的任务全部都是使用新创建的execution去执行.

这个操作的本意就是为了让当前栈入堆,而且这个写法在C#和Kotlin里面是完全通用的,因为这3个语言的异步方案都是基本类似,而这个写法来自Rust群一位群友的发现,当时我看到这种写法的时候也表示了惊奇,然后对于递归大面积使用这种写法,目前没有发现什么问题.

这两天发现有人@星风雪月对执行机制有很深的成见,所以我这里使用Node.JS的async hooks做了一下异步执行的调试,这是测试代码:

const async_hooks = require("async_hooks")

let index = 0

let print_buffer = ""

/*** async hooks会追踪async调用,* 而console.log使用异步输出,* 所以这里使用同步方法模拟console*/

function println(log) {

print_buffer += log + "\n"

}

/* 创建钩子 */

async_hooks.createHook({

init(asyncId, type, triggerAsyncId) {

const eid = async_hooks.executionAsyncId()

println("init: *********************************")

println("init: triggerAsyncId " + triggerAsyncId)

println("init: executionAsyncId " + eid)

println("init: asyncId " + asyncId)

println("init: type " + type)

}

})

.enable()

/*********** 测试区 **********/

async function A() {

/* 为了观察方便只执行2次 */

println("A: runing")

index += 1

if (index === 2) return undefined

// 有优化递归 await undefined

return await A()

}

/*********** 测试区 **********/

A().then(() => {

console.log(print_buffer)

})

执行之后的输出:

init: *********************************

init: triggerAsyncId 1

init: executionAsyncId 1

init: asyncId 2

init: type PROMISE

A: runing

init: *********************************

init: triggerAsyncId 2

init: executionAsyncId 1

init: asyncId 3

init: type PROMISE

init: *********************************

init: triggerAsyncId 3

init: executionAsyncId 1

init: asyncId 4

init: type PROMISE

init: *********************************

init: triggerAsyncId 2

init: executionAsyncId 1

init: asyncId 5

init: type PROMISE

init: *********************************

init: triggerAsyncId 4

init: executionAsyncId 4

init: asyncId 6

init: type PROMISE

A: runing

init: *********************************

init: triggerAsyncId 6

init: executionAsyncId 4

init: asyncId 7

init: type PROMISE

这里看executionAsyncId标志,这个是当前执行器的ID,这里可以看到当经过await undefined之后, 执行器从1变成了4,说明这里发生了execution转移,下面我修改一下代码,变成不优化的写法:

// 无优化递归// await undefinedreturn await A()

我这里将await undefined删除,再来执行看看会是什么情况:

init: *********************************

init: triggerAsyncId 1

init: executionAsyncId 1

init: asyncId 2

init: type PROMISE

A: runing

init: *********************************

init: triggerAsyncId 1

init: executionAsyncId 1

init: asyncId 3

init: type PROMISE

A: runing

init: *********************************

init: triggerAsyncId 3

init: executionAsyncId 1

init: asyncId 4

init: type PROMISE

init: *********************************

init: triggerAsyncId 2

init: executionAsyncId 1

init: asyncId 5

init: type PROMISE

再看executionAsyncId,这里始终是使用1去执行,所以没有转移execution执行,这里就很能说明问题了,await undefined可以转移执行器执行,让当前栈入堆,这样可以使调用栈不会溢出,达到深递归优化的目的.

对于评论区朋友说明,这种方式会阻塞macrotask,所以不推荐这种写法,我这里并不表示完全反对意见,我这里来说一下我自己的看法:

异步任务归属microtask,而其他事件回调归属macrotask,microtask的优先级本身就比macrotask要高,所以肯定是microtask先执行,然后才轮到macrotask,像setTimeout(0)这种本身就是属于macrotask,肯定要等到microtask执行完成之后才能执行,不过这确实会带来一个问题,就是对已经运行的macrotask产生时间分辨率精度影响,比如定时器偏移,定时器不会精准得按时间分片执行任务,所以这种写法见仁见智,你如果需要精确macrotask执行的场景还是慎用.

js与c语言效率_JavaScript比c语言的性能差了多少?相关推荐

  1. js与c语言效率_JavaScript控制流及关键字与C语言之比较

    学习JavaScript控制流及关键字概念前,对有过C语言学习经验的同学来说,那么关键字,控制语句概念并不陌生.我们先来看看C语言吧: C语言的32个关键字和9种控制语句 9种控制语句: if.if- ...

  2. python弱类型好处_JavaScript弱类型语言的优缺点有哪些

    弱类型语言也称为弱类型定义语言.与强类型定义相反.像vb,php等就属于弱类型语言· 例如:在vbscript中,可以将字符串 12 和整数 3 进行连接得到字符串 123,然后可以把它看成整数 12 ...

  3. jQuery.each() 和原生JS的for loop效率对比

    Sent: Thursday, March 26, 2015 1:51 PM Subject: jquery each vs for loop 在看前端代码的时候,看到不少地方都有用到jQuery.e ...

  4. odoo openerp 分享-oe嵌入qweb】用js读取数据库数据,用类似html语言重写web报表

    [分享-oe嵌入qweb]用js读取数据库数据,用类似html语言重写web报表  (阅读 4403 次) 阿狸 新手上路 帖子: 26 人气: 1 开启阅读模式 [分享-oe嵌入qweb]用js读取 ...

  5. python程序设计语言是什么类型的语言-Python 是弱类型的语言 强类型和弱类型的语言区别...

    Python 是弱类型的语言 在强类型的编程语言中,定义变量时要指明变量的类型,而且赋值的数据也必须是相同类型的,C语言.C++.Java 是强类型语言的代表. 下面我们以 C++ 为例来演示强类型语 ...

  6. c语言stand(time(0)),C语言复习0_准备工作

    前言: 第一次接触C语言是大一下学期的时候,2013年上半年,那个时候的开发工具还是VS C++,今天了解了一下,常用的开发工具变成了CodeBlocks.决定趁着毕业到入职这一段CD时间,拾起这门语 ...

  7. Go语言学习之路——Go语言简介

    本文首发于我的博客 很多人将GO语言称为21世纪的C语言,因为GO不仅拥有C的简洁和性能,而且还很好的提供了21世纪互联网环境下服务端开发的各种实用特性,让开发者在语言级别就可以方便的得到自己想要的东 ...

  8. 大猛网赚编程之从易语言走向c,浅析易语言网赚应用心得

    以下就是关于易语言网赚应用学习心得内容: 最开始学习易语言,还是因为混互联网比较方便,当年网赚很流行,很多东西希望能够软件化自动化,所以一气之下就学了. 任何一种编程语言都是博大精深的,就算是易语言也 ...

  9. 编译型和解释型、动态语言和静态语言、强类型定义语言和弱类型定义语言

    一.编译型与解释型语言 我们编写程序也就是源代码基本是用高级编程语言,比如JavaScript, java, c等等,这些语言计算机是不理解的,所以需要转化(翻译)成计算机理解的机器语言,或者说目标C ...

最新文章

  1. 【错误记录】Google Play 上架报错 ( 此版本不符合 Google Play 关于提供 64 位版本应用的要求 )
  2. Linux socket 流模式(STREAM)跟数据报模式(DGRAM)的区别
  3. java程序设计实例教程 刘志成_Java程序设计实例教程教学课件作者刘志成章节05_Java图形用户界面技术.PPT...
  4. PCL:PCL1.9.0更新
  5. 【Linux】【Services】【nfs】nfs安装与配置
  6. 使用WinIO库实现保护模式下的IO和内存读写
  7. Object to XML
  8. LeetCode 58 Spiral Matrix II
  9. 计算机组成原理试题 t4,计算机组成原理(四版)本科生试题库整理附答案
  10. cv2 imread函数 python_opencv-python库基础操作(一)
  11. 2016北京集训测试赛(十三) Problem B: 网络战争
  12. ip地址在c语言中长度是多少_c语言中(++i)+(++i)+(++i)究竟等于多少?
  13. py-faster-rcnn标注FDDB人脸便于其在FDDB上进行测试
  14. Snagit 10 截图 虚拟打印机 【下载|注册码】
  15. tensorflow实战之手写体识别
  16. 良品铺子天猫618爆卖300万个手撕面包,还用数据改造线下
  17. 微信分享链接优化 title icon 描述
  18. 北斗GPS同步时钟(授时系统)技术原理详解
  19. SYN攻击原理以及防范技术
  20. 2023年网络安全比赛--网络安全事件响应中职组(超详细)

热门文章

  1. POJ 1936 字符匹配(水题)
  2. win7家庭版远程桌面补丁_无需惊慌!微软漏洞数月后再被“预警”打补丁即可防御...
  3. js固定表格行列_纯前端表格控件SpreadJS V14.0发布:组件化编辑器+数据透视表
  4. ICLR2020 | 如何判断两个神经网络学到的知识是否一致
  5. 大圣魔方——美团点评酒旅BI报表工具平台开发实践
  6. 2018最新Java面试78题:数据结构+网络+NoSQL+分布式架构
  7. 论文浅尝 | 面向时序知识图谱推理的循环事件网络
  8. 斯坦福李纪为博士毕业论文:让机器像人一样交流
  9. Cannot re-initialize CUDA in forked subprocess. To use CUDA with multiprocessing, you
  10. 论文阅读课1-Attention Guided Graph Convolutional Networks for Relation Extraction(关系抽取,图卷积,ACL2019,n元)