深入理解 V8 的 Call Stack

简介: Call Stack(调用栈) 一般指计算机程序执行时子程序之间消息处理的相互调用产生的一些列函数序列,而且几乎所有的计算机程序都依赖于调用栈。

作者:UC 国际研发 叫兽

image.png

Call Stack 与 Stack 的概念

Call Stack(调用栈) 一般指计算机程序执行时子程序之间消息处理的相互调用产生的一些列函数序列,而且几乎所有的计算机程序都依赖于调用栈。

在探讨 Call Stack 前,先来搞清楚 Stack(栈)的概念。

Stack 就是一种特殊的串列形式的数据结构,特殊之处在于只能允许在链接串列或阵列的一端(称为堆叠顶端指标,英语:top)进行加入数据(英语:push)和输出数据(英语:pop)的运算。因此栈的数据结构只允许在一端进行操作,按照后进先出(LIFO, Last In First Out)的原理运作。

image.png

Call Stack 是如何运作的

让我们看看下面的代码:

image.png

它的执行结果是:

c
b
a

该代码执行过程经历了两个阶段 首先是执行入栈。

执行 a() 方法后,此时 a 就被添加到调用栈的顶部。

image.png

在 a 内部调用了 b(),此时的调用栈顶部添加了 b:
image.png

同样 b 内部调用了 c(),此时的调用栈顶部添加了 c,最终的调用栈变成了:

image.png

此时 console.log('c'); 首先被执行。

当执行完 c 后,调用并不就此完成,开始第二阶段的出栈:

image.png

b 方法重新获得了线程控制, 执行了 console.log('b'); 。

b 执行完成,栈退到 a 方法上:

image.png

执行 console.log('a'); 。

最后调用完成,调用栈 emptied。

image.png

调用栈的大小

由于操作系统对每组线程的栈内存有一定的限制,为适应线程各种操作系统,所以 Node.js 默认的栈大小为 984k。

Slightly less than 1MB, since Windows' default stack size for the main execution thread is 1MB for both 32 and 64-bit. @src/globals.h:108:1

如何获取当前环境的调用栈大小?

image.png

不过,由于不同版本的 Node 集成的 V8 版本和优化等不同,即使同样 size 的栈空间,调用栈的栈深浅各不相同,我们尝试使用递归函数来测试一下每个版本的 Node.js 环境的可用栈深情况。
image.png

node v4.8.3

computeMaxCallStackSize 15705

node v5.12.0

computeMaxCallStackSize 15700

node v6.10.2

computeMaxCallStackSize 15718

node v7.9.0

computeMaxCallStackSize 15674

从执行结果看,虽然各个版本的调用栈空间默认都是 984kB,从 4.8.3、5.12.0 和 6.10.2 数个版本栈深度大约在 15700 以上,而 7.9.0 版的深度则为 15674。

从实际使用上看,这样的栈深表示一个线程上执行函数的调用栈可达到 15700 层,除非代码中出现"死循环"等情况,对于日常的运算基本是不会有任何问题。

但需要注意的是,调用栈的深度要根据当前调用函数的函数体大小和 local 变量的多少来决定,假如调用栈需要保存的本地变量数量较多,则需要占用较多栈空间来放置这些变量指针,那么栈深度就将远小于该值。

如果需要修改栈的大小,可以通过以下指令增加其大小:

image.png

V8 的调用栈优化

V8 为提高 JavaScript code 的运行性能,从一开始就采取激进的基于机器码编码方案,那么 V8 在处理调用栈的问题上,是否又有进行了优化呢?

我们对以上的代码进行修改,尝试对同一段代码进行 10 次重复执行。

image.png

各个版本下,我们看到输出的数据:

node v4.8.3 (v8@4.5.103.47)

image.png

node v5.12.0 (v8@4.6.85.32)

image.png

node v6.10.2 (v8@5.1.281.98)

image.png

node v7.9.2 (v8@5.5.372.43)

image.png

实验的结果,在栈大小不变的情况下,代码被重复执行 2、3 遍后,栈的深度会增加(但 6.10.2 除外,比较诡异),可以理解为栈的内存得到了优化。在而 7.9.2 的版本,运行了两次后,栈的深度更大幅增加 14.28%。

根据 V8 的优化机制,当程序进入 V8 VM 环境后,代码会首先进行简单编译(Full Compliler),这个过程为 gencode,生成机器码并后才开始执行,而 Crankshaft 的优化编译机制并不会被启动,因为此阶段对于编译器来说,看到的只是代码,还无法分析出这些代码哪部分需要优化的。

每个经过 FC 编译的函数都会包含一个计数器,当函数返回或完成一轮循环的时候,就会减少计数的值, 分析器在计数减到 0 的时候,内置性能分析器就可以挑选这类的热点函数,并启动 Crankshaft(优化编译)对其进一步的优化处理,指向其代码的指针就会被改写指向为一个 V8 内置的函数——Lazy Recompile,这样函数再次被调用时将执行经过优化的函数代码。(笔者认为:同时堆栈上的空间上用于存储的函数将被替换,指针指向了栈外的某个堆内存上,节省了栈空间的占用)。

总结

Call Stack(调用栈)实际上就是用于存储函数的一种内存数据,而且遵循 LIFO 原理实现的进栈和出栈等一系列操作。栈的大小受到操作系统的限制,一般会少于 1MB 的空间,能使用的回调栈层数受制于栈中每个栈函数的内部变量数量等不同,调用栈的深浅也不一样。

从我们的开发层面看,代码的执行和栈深一般都是有限的,所以默认的情况下代码都不会出现调用栈溢出异常的问题发生。

在了解调用栈的工作原理,及调用栈在各个版本上的运行表现后,其实我们应该思考一下,假设我需要设计一个类似 process.nextTick() 或者 co.next() 这样的函数时,应该如何设计函数方法体,让该函数的代码既有效率地执行同时又能被系统做优化处理,而什么样的代码不行的问题。

目录
相关文章
|
JavaScript 前端开发 测试技术
如何测试 CORS 是否正常工作?
通过以上多种方法的综合测试,可以全面地检查 CORS 是否正常工作,及时发现和解决跨域资源共享中可能存在的问题,确保不同源的网页和服务器之间能够正常地进行数据交互。
1241 60
EMQ
|
运维 Linux 网络性能优化
MQTT 5.0 报文解析 05:DISCONNECT
在 MQTT 中,客户端和服务端可以在断开网络连接前向对端发送一个 DISCONNECT 报文,来指示连接关闭的原因。客户端发送的 DISCONNECT 报文还可以影响服务端在连接断开后的行为,例如是否发送遗嘱消息,是否更新会话过期间隔。
EMQ
566 0
MQTT 5.0 报文解析 05:DISCONNECT
|
人工智能 Rust 安全
WebAssembly运行时库(WASM runtime:wasmer 或 wasmtime)\将rust官方demo猜数字编译为WASI目标并使用Wasmer运行
WebAssembly运行时库(WASM runtime:wasmer 或 wasmtime)\将rust官方demo猜数字编译为WASI目标并使用Wasmer运行
309 2
|
机器学习/深度学习 自然语言处理 搜索推荐
推荐系统的算法分类和操作流程介绍
推荐系统的算法分类和操作流程介绍
|
XML Web App开发 前端开发
Brotli 研究实践
通过配置brotli压缩来提高http请求的压缩率,降低出口带宽,提升访问速度。
1131 0
|
Android开发
电脑控制安卓手机:scrcpy
电脑控制安卓手机:scrcpy
519 0
电脑控制安卓手机:scrcpy
|
存储 NoSQL Redis
五.Redis中那些你不知道的秘密-五大基本结构SortedSet的实现原理
SortedSet(zset)有序集合可以看做是在Set集合的的基础上为集合中的每个元素维护了一个顺序值: score,它允许集合中的元素可以按照score进行排序,所以它的经典实用场景如:考生按分数排名,某游戏玩家分数排行,网站首页某数据排行,最新评论按时间排序等等。 Redis是一个内存数据库,它在保证读写速度的同时也需要考虑内存开销,那对于SortedSet有序集合而言它需要维护一个顺序值,而对于有序集合的底层实现可以选择:数组,链表,平衡树或者红黑树等结构,但是SortedSet没有选择这些结构。数组插入和删除元素性能很差,链表查询慢,平衡树或红黑树虽然查询效率高,但是在插入和删除元
|
SQL 数据采集 监控
网站流量日志分析--统计分析--基础指标统计分析(pv、uv)|学习笔记
快速学习网站流量日志分析--统计分析--基础指标统计分析(pv、uv)
653 0
网站流量日志分析--统计分析--基础指标统计分析(pv、uv)|学习笔记
|
存储 Kubernetes 大数据
Data Mesh,数据架构的下一个变革!
Data Mesh 的潜力既令人兴奋又令人生畏,就像从前的微服务架构一样。
1729 0
Data Mesh,数据架构的下一个变革!