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执行的场景还是慎用.
js与c语言效率_JavaScript比c语言的性能差了多少?相关推荐
- js与c语言效率_JavaScript控制流及关键字与C语言之比较
学习JavaScript控制流及关键字概念前,对有过C语言学习经验的同学来说,那么关键字,控制语句概念并不陌生.我们先来看看C语言吧: C语言的32个关键字和9种控制语句 9种控制语句: if.if- ...
- python弱类型好处_JavaScript弱类型语言的优缺点有哪些
弱类型语言也称为弱类型定义语言.与强类型定义相反.像vb,php等就属于弱类型语言· 例如:在vbscript中,可以将字符串 12 和整数 3 进行连接得到字符串 123,然后可以把它看成整数 12 ...
- jQuery.each() 和原生JS的for loop效率对比
Sent: Thursday, March 26, 2015 1:51 PM Subject: jquery each vs for loop 在看前端代码的时候,看到不少地方都有用到jQuery.e ...
- odoo openerp 分享-oe嵌入qweb】用js读取数据库数据,用类似html语言重写web报表
[分享-oe嵌入qweb]用js读取数据库数据,用类似html语言重写web报表 (阅读 4403 次) 阿狸 新手上路 帖子: 26 人气: 1 开启阅读模式 [分享-oe嵌入qweb]用js读取 ...
- python程序设计语言是什么类型的语言-Python 是弱类型的语言 强类型和弱类型的语言区别...
Python 是弱类型的语言 在强类型的编程语言中,定义变量时要指明变量的类型,而且赋值的数据也必须是相同类型的,C语言.C++.Java 是强类型语言的代表. 下面我们以 C++ 为例来演示强类型语 ...
- c语言stand(time(0)),C语言复习0_准备工作
前言: 第一次接触C语言是大一下学期的时候,2013年上半年,那个时候的开发工具还是VS C++,今天了解了一下,常用的开发工具变成了CodeBlocks.决定趁着毕业到入职这一段CD时间,拾起这门语 ...
- Go语言学习之路——Go语言简介
本文首发于我的博客 很多人将GO语言称为21世纪的C语言,因为GO不仅拥有C的简洁和性能,而且还很好的提供了21世纪互联网环境下服务端开发的各种实用特性,让开发者在语言级别就可以方便的得到自己想要的东 ...
- 大猛网赚编程之从易语言走向c,浅析易语言网赚应用心得
以下就是关于易语言网赚应用学习心得内容: 最开始学习易语言,还是因为混互联网比较方便,当年网赚很流行,很多东西希望能够软件化自动化,所以一气之下就学了. 任何一种编程语言都是博大精深的,就算是易语言也 ...
- 编译型和解释型、动态语言和静态语言、强类型定义语言和弱类型定义语言
一.编译型与解释型语言 我们编写程序也就是源代码基本是用高级编程语言,比如JavaScript, java, c等等,这些语言计算机是不理解的,所以需要转化(翻译)成计算机理解的机器语言,或者说目标C ...
最新文章
- 【错误记录】Google Play 上架报错 ( 此版本不符合 Google Play 关于提供 64 位版本应用的要求 )
- Linux socket 流模式(STREAM)跟数据报模式(DGRAM)的区别
- java程序设计实例教程 刘志成_Java程序设计实例教程教学课件作者刘志成章节05_Java图形用户界面技术.PPT...
- PCL:PCL1.9.0更新
- 【Linux】【Services】【nfs】nfs安装与配置
- 使用WinIO库实现保护模式下的IO和内存读写
- Object to XML
- LeetCode 58 Spiral Matrix II
- 计算机组成原理试题 t4,计算机组成原理(四版)本科生试题库整理附答案
- cv2 imread函数 python_opencv-python库基础操作(一)
- 2016北京集训测试赛(十三) Problem B: 网络战争
- ip地址在c语言中长度是多少_c语言中(++i)+(++i)+(++i)究竟等于多少?
- py-faster-rcnn标注FDDB人脸便于其在FDDB上进行测试
- Snagit 10 截图 虚拟打印机 【下载|注册码】
- tensorflow实战之手写体识别
- 良品铺子天猫618爆卖300万个手撕面包,还用数据改造线下
- 微信分享链接优化 title icon 描述
- 北斗GPS同步时钟(授时系统)技术原理详解
- SYN攻击原理以及防范技术
- 2023年网络安全比赛--网络安全事件响应中职组(超详细)
热门文章
- POJ 1936 字符匹配(水题)
- win7家庭版远程桌面补丁_无需惊慌!微软漏洞数月后再被“预警”打补丁即可防御...
- js固定表格行列_纯前端表格控件SpreadJS V14.0发布:组件化编辑器+数据透视表
- ICLR2020 | 如何判断两个神经网络学到的知识是否一致
- 大圣魔方——美团点评酒旅BI报表工具平台开发实践
- 2018最新Java面试78题:数据结构+网络+NoSQL+分布式架构
- 论文浅尝 | 面向时序知识图谱推理的循环事件网络
- 斯坦福李纪为博士毕业论文:让机器像人一样交流
- Cannot re-initialize CUDA in forked subprocess. To use CUDA with multiprocessing, you
- 论文阅读课1-Attention Guided Graph Convolutional Networks for Relation Extraction(关系抽取,图卷积,ACL2019,n元)