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

我这里先不说和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执行的场景还是慎用.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/485947.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

服务器物理内存总是九十几,Solr总是使用超过90%的物理内存(Solr always use more than 90% of physical memory)...

Solr总是使用超过90%的物理内存(Solr always use more than 90% of physical memory)我在solr索引中存储了300000个文档。 并使用4GB RAM作为solr服务器。 但它消耗超过90%的物理内存。 所以我把我的数据转移到一台有16 GB RAM的新服务器上。 再次solr消…

​​《自然》2020年十大科学发现出炉:病毒,冷冻电镜与快速射电暴

来源:科研圈作者:陈梦圆、谢一璇、李姗珊、邱燕宁、魏潇科学成就

Http协议--Get和Post区别

1. 请求路径不同 post请求,在url后面不跟上任何的数据 Get请求,在地址后面跟上数据 2. 带上的数据不同,Post请求会使用流的方式写数据。 Get请求是在地址栏上跟数据 3. 由于Post请求使用流的方式写数据, 所以一定需要一个Con…

Hibernate中用到联合主键的使用方法,为何要序列化,为何要重写hashcode 和 equals 方法...

联合主键用Hibernate注解映射方式主要有三种: 第一、将联合主键的字段单独放在一个类中,该类需要实现java.io.Serializable接口并重写equals和hascode,再将该类注解为Embeddable,最后在主类中(该类不包含联合主键类中的字段)保存该联合主键类…

命令行设置dns_dos命令netsh图文教程,设置修改IP地址子网掩码网关命令行改dns...

大家好,我是老盖,首先感谢观看本文,本篇文章做的有视频,视频讲述的比较详细,也可以看我发布的视频。今天我们学习dos命令中的netsh,这个命令是网络命令,它有很多的功能,今天我们用它…

Leetcode--5081. 步进数

如果一个整数上的每一位数字与其相邻位上的数字的绝对差都是 1,那么这个数就是一个「步进数」。 例如,321 是一个步进数,而 421 不是。 给你两个整数,low 和 high,请你找出在 [low, high] 范围内的所有步进数&#x…

文件操作的基本流程

1、读模式 f.read()f.readable()#这个文件是否可读,返回布尔值;f.readline(“读模式”)#读取第一行;f.readlines(“读模式”)#读取所有内容 以字符串方式存储在列表内; f open(小重山,r,encodingutf-8) #第一个参数是文件位置&am…

第三代人工智能基础设施背后,是一次技术应用的常识普及运动

来源:脑极体买了新电脑和手机,你会提前安装好杀毒或安全软件,还是等被黑客攻破了才悔之晚矣?处理传染病疫情,是从源头释放药物和疫苗,还是坐等医院的患者超过治疗和处理能力?面对危机舆情&#…

5 ui自适应窗口_Qt编写地图综合应用5-自适应拉伸

一、前言用过echart的人都会遇到一个问题,就算是代码中写了window.onresize echart.resize,也只是横向自适应拉伸填充页面,垂直方向不会变化,除非指定高度才可以,这就比较郁闷了,为何echart本身不会自适应…

Leetcode--85. 最大矩形

给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。 示例: 输入: [ ["1","0","1","0","0"], ["1","0","1","1","…

PowerDesigner生成sql语句时自动导出注释

1、为sql生成注释,操作如下,在其中选择Tools----》Excute commands-----》Edit/Run Script打开的窗口中添加以下信息 ****************************************************************************** * File: name2comment.vbs * Purpo…

为什么俄罗斯的数学那么牛?

编辑 ∑Gemini来源:奇趣数学苑但在国际上也有一个很著名的说法,就是世界上最好的大学,其实是“美国的学校,中国的学生,俄罗斯的教授”。俄罗斯数学家佩雷尔曼虽然俄罗斯现在相比以前来说,尤其明显的是在一些需要大量设…

查看屏幕大小_疑似“iPhone 12” 的OLED屏幕组件泄露

来自可靠的泄密者的新照片显示了即将面世的“ iPhone 12”所搭载的OLED屏幕。这名泄密者以前曾显示过未来苹果产品的准确图像,此前曾展示过苹果A14芯片组的早期图片,并声称“ iPhone 12”将配备20W电源适配器。现在他在推特上发布了一张照片,…

Leetcode--198. 打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的…

搭建Hexo博客(一)-创建Hexo环境

Hexo配合github,可以创建自己的博客。基本原理是使用Hexo生成静态页面,发布到github上。在本地需要搭建Hexo环境。 1、安装nodejs 下载并安装NodeJS,官网地址:https://nodejs.org/en/ 2、安装完后,查看安装情况 1 node…

python logging模块的作用及应用场景_Python logging模块原理解析及应用

一、logging日志模块等级常见log级别从高到低:CRITICAL 》ERROR 》WARNING 》INFO 》DEBUG,默认等级为WARNING,即>WARNING级别的log才输出。日志等级(level)描述CRITICAL当发生严重错误,导致应用程序不能继续运行时记录的信息E…

Nature:2020年最佳科学影像,每一张都是壁纸,每一张都是历史

文章来源:学术头条2020年是独一无二的一年。COVID-19大流行将科学推到了最前沿,并深刻影响了人们的生活。但是这一年也产生了许多与病毒无关的新影像。从薄薄的太阳能电池,到经过基因编辑的鱿鱼,下面就是Nature新闻和艺术团队评选…

Leetcode--213. 打家劫舍Ⅱ

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚…

转 从红帽、GitHub和Docker看开源商业模式的进阶

从红帽、GitHub和Docker看开源商业模式的进阶 发表于2014-12-16 10:26| 7594次阅读| 来源http://stratechery.com/| 0 条评论| 作者Ben ThompsonDocker红帽GitHub开源CoreOS摘要:从技术角度来说,Docker无疑是可圈可点的,比如“write once run…

antimalware可以关闭吗_iPhone最好关闭这4个设置,手机流畅还省电

你抢到新款的iPhone12了吗?80%的小伙伴都会忽略的设置,你的iPhone手机最好关闭以下这4个设置。①关闭ios系统更新操作步骤:打开【设置】-【通用】-【软件更新】-【自动更新】-【下载iOS更新】,关闭即可。这样就不会莫名其妙地下载…