Qking:跨端 JS 引擎的深度探索与突破

如果无法正常显示,请先停止浏览器的去广告插件。
分享至:
1. Qking:跨端 JS 引擎的深 度探索与突破 胥加成 (花名:平逸) 阿里巴巴无线开发专家
2. 目录 01 背景介绍 02 性能优化 03 功能增强 04 质量保障
3.
4. 背景介绍 跨端JS引擎的前世今生
5. JS引擎的跨端应用场景 前端开发 01 01 跨端UI框架 02 客户端开发 02 03 小程序 逻辑需要动态性的各类场景
6. JS引擎的跨端应用场景 前端开发 01 01 跨端UI框架 启动速度快 运行性能高 02 内存占用小 小程序 JS可调试 包体积小 客户端开发 02 可调试 03 逻辑需要动态性的各类场景
7. JS引擎的跨端应用场景 前端开发 01 01 谁能全部满足? 客户端开发 02 03 跨端UI框架 02 小程序 逻辑需要动态性的各类场景
8. JS引擎的选型 常见方案 WebView V8 JSC JerryScript DukeTape • 资源开销大 • 资源开销大 • 性能较差 • 启动慢 • 启动慢 • JS 标准支持不全 • 使用不方便 • 双端一致性差 • 无法调试 JS • 一致性差 • 稳定性问题严重 • 无法调试JS
9. JS引擎的选型 新生代引擎 QuickJS • 启动性能不够好 • 缺失调试功能 • 堆栈还原无法输出列号 导致 JS minify 文件的 线上报错无法定位 Hermes • ES 标准支持太低 • 字节码预编译优化和版 本绑定,难以进行网络 下发 • 缺少线上的 eval 支持
10. JS引擎的选型 对比 项目 V8 JSC JerryScript DukeTape WebView Hermes QuickJS 包体积 C A A B A 内存占用 C A C B B 启动性能 B C C A+ B- 执行性能 A C A B B JS 标准支持 A C B C A 双端一致性 B A C A A 稳定性 C B C B B 使用复杂度 B A C C A JS 调试支持 无 无 有 有 无
11. 常见方案面临的问题 前端业务开发 问题 客户端框架开发 耗费开发时间进行优化 性能问题 耗费开发时间进行优化 靠console.log进行调试 浪费时间 JS不可调试 更大的polyfill产物 更多的适配耗时 ES标准支持不一致 引擎JS行为不一致 发布上线的风险更大 资源占用大 引擎不稳定容易crash 更多时间排查问题 端上crash问题恶化
12. Qking 基于QuickJS的JS引擎 低内存占用 支持内存安全性检查 高ES标准支持 低包大小 支持内存泄露检查 高可用性 质量 毫秒级别启动性能 支持Chrome调试 性能 执行性能提升 20%+ 功能 支持JS minify后 行列号还原
13. Qking 提升交付效率 & 质量 前端业务开发 特性 客户端框架开发 更好的性能表现 更少时间用在优化上 字节码预编译 优化启动性能 更好的性能表现 更少时间用在优化上 开发效率和线上问题 排查效率都更高 原生Chrome Debug Protocol调试支持 更小的前端产物 更少的适配用时 高ES标准支持 双端行为一致 更稳定的框架 低资源占用 内存Checker检查 稳定性有保障 问题排查有手段
14. 性能优化
15. 性能的构成 链路长,耗时长 AST分析 Token流分析 Bytecode生成 JS文件 产物下载 引擎创建 时间轴 JS parse JS Execute Bytecode优化
16. 性能的构成 WebAssembly: bytecode生成被前置到离线流程里 Token流分析 AST分析 WASM Bytecode生成 Bytecode优化 产物下载 引擎创建 时间轴 Wasm Execute
17. 性能的构成 1. 线上统计部分复杂业务 的 Parse : Execute 耗 时达到 3 : 1 2. QuickJS 的 parse 本身 相对V8慢 优化手段 • V8: bytecode cache • Hermes: 预编译bytecode • QuickJS: 预编译bytecode 缺陷 JS文件 • Bytecode 无法在引擎版本间兼容, 就很难发布作为CDN的产物 产物下载 引擎创建 JS parse JS Execute • V8: 产物大小膨胀 • Hermes 阉割掉了在线 eval 的支持 • 没有利用离线parse更充裕的分析时 间 时间轴
18. 二进制预编译优化 • 实现了版本间字节码兼容 • 保留了原有 QuickJS 的在线 eval 支持 • 利用离线 parse 更充裕的分析时间提升字节码质量 字节码分析优化 JS文件 优化产物大小 毫秒级解码 预编译 离线编译 优化执行性能 产物下载 引擎创建 解码 时间轴 JS Execute 首屏时间大幅提升
19. 二进制预编译优化 版本兼容的方案 版本1产物 编译 & 运行 版本1引擎 版本2产物 运行 编译 & 运行 版本2引擎 • 字节码变动频率相对很低 • 保持运行时向前兼容只需要保持对老字节码的支持,代码膨胀较少
20. 二进制预编译优化 版本兼容的方案 • Dispatch table切换 • 极低额外开销 1%
21. 二进制预编译优化 字节码优化的思路 Token Parse Emit OPCode 优化后 窥孔优化1 Token Parse Token Parse Emit OPCode Emit OPCode 窥孔优化1 窥孔优化1 Optimizer 窥孔优化2 QuickJS原版流程 问题: 窥孔优化2 在线Parse不变 保证速度 • 窥孔优化可优化的 case 不够泛化 (基于栈的字节码数据关系是隐含的) • 为了保障 parse 速度,不能做太复杂的分析 窥孔优化2 离线 Parse 增加 Optimizer流程 最大化优化
22. 功能: 1. 控制流分析 1. 数据溢出分析 优化后 2. Deadcode删除 2. 数据流分析 1. 指令合并 2. 指令改写 3. 常量传播 3. 可视化 控制流分析 数据流分析 指令数量减少
23. 二进制预编译优化 得与失 牺牲 获得 锁死JS引擎 首屏性能提升一倍 对产物发布链路有一定 依赖 执行性能提升10~20% 额外的功能优势
24. 其他优化手段 内存分配池化 & 字符串Binding优化 利用JS频繁分配释放 & QuickJS 的单线程 设计, 进行池化分配, 减少 malloc / free 次数 字符串内容 分配池 String类 数据结构共享 指针直接引用 上层框架 Qking 和JS引擎通过共享字符串结构和常量字符串表 减少跨语言拷贝的开销 系统malloc / free • 字符串的拷贝是JS binding中的主 要内存 & 性能开销之一
25. 性能对比 执行性能对比 测试项目 QuickJS Qking Hermes V8 JIT V8 NOJIT JSC JIT JSC NOJIT cripto 153.772 126.4 91.904 4.3 160.64 4.7 84 deltablue earley- boyer navier- strokes raytrace 11.139 9.95 8.009 0.6 6.5 0.6 8.5 181.903 146.9 134.051 8.5 77.84 4.7 107.9 117.27 88.1 90.4 5.4 133.605 6.8 79.68 101.018 76.2 62.713 2.9 32.588 1.6 45.1 regexp 449.265 382.2 251.02 15.996 34 18.5 244.4 richards2 5.505 5 3.558 0.12 3.9 0.25 3.83 splay 4.951 2.26 2.266 0.31 1.535 0.18 2.34
26. 性能对比 启动性能对比 测试项目 QuickJS Qking (JS) 引擎启动 + Parse耗时 PC 62 40 2.5 引擎启动 + Parse耗时 Android低端机 241.6 160.2 8.4 • 以一个270KB的 JS 业务case为例 Qking Hermes (字节码) V8 JSC 2.6 17 18.2 - - 71.3
27. 性能对比 内存对比 • 测试项目 QuickJS Qking 分配次数 242394 138913 (-40%) 分配量 18MB 13MB (-27%) 内存占用量 9.3MB 8.1MB (-13%) 以一个线上 JS 业务case为例
28. 功能增强
29. 调试功能 传统方案的调试功能实现 V8 JSC • 比较难运行在IOS上 • 调试协议非chrome • 全功能的调试器代码较 • IOS上系统提供的JSC不 多,包大小大 包含debugger
30. 调试功能 传统方案的调试功能实现: 虚拟环境 上层跨端框架 JS Binding 注入 RPC • 维护投入多 • 和真实引擎环境有差异 • 不能在问题现场连入 虚拟环境JS Binding 注入 底层JS引擎 Node/Web虚拟环境 运行时 调试 WebSocket 调试器
31. 调试功能 传统方案的调试功能实现: 替换引擎 上层跨端框架 JS Binding 注入 底层JS引擎(JSC) • 维护投入多 • 和真实引擎环境有差异 • 前端开发换包麻烦 上层跨端框架 JS Binding 注入 底层JS引擎(支持调试的引擎) 线上包 调试专用包
32. 调试功能 原生调试 vs 虚拟调试 项目 原生调试 虚拟调试/专用包 维护成本 一次性投入,小 持续投入,大 可靠性 非常高 容易出现和真实环境不一致 架构复杂性 低 高 内存dump 和真实场景占用一致 不一致 线上问题现场 可以接入调试 无法接入,现场丢失 正确好用的调试能降低成本,而错误的调试一定会大幅增加成本
33. 调试功能 难点 & 问题 • • 需要修改 QuickJS 内部的实现,提 部分非核心的CDP的功能无法实现, 供一套Debug用的API出来 比如代码自动提示 (需要无副作用的 和CDP协议对齐需要大量的JSON协 执行环境,较难实现) 议处理 & 类型转换 • • 断点的实现需要实现 字节码OP offset 和 行列号 之间的双向映射能 力,原生 QuickJS 只有行号的还原, 没有列号也没有 行列到offset的映 射。
34. 预编译二进制 行列号映射支持 QuickJS 没有列号映射原因: 1. 影响parse速度 2. 导致产生的bytecode映射 mapping过大 Qking的预编译二进制: 1. 离线parse可以不影响线上性能 2. 映射mapping可以抽离单独的 文件发布 预编译天生适合解决此问题
35. 预编译二进制 行列号映射支持 QuickJS 仅行号支持 Token Parse Bytecode Emit OP_add OP_linenum 1 OP_get_field Bytecode Opt1 OP_add OP_get_field Bytecode Opt2 bytecode 行号映射 Linenum 信息收集 Qking 在线流程: 和 QuickJS 原版保持一致 Token Parse Bytecode Emit Bytecode Opt1 Bytecode Opt2 bytecode 行号映射
36. 预编译二进制 行列号映射支持 QuickJS 仅行号支持 Token Parse Bytecode Emit Bytecode Opt1 Bytecode Opt2 行号映射 Qking 离线流程 Token Parse bytecode Bytecode Emit OP_add OP_linenum 1 2 OP_get_field Bytecode Opt1 Bytecode Opt2 OP_add OP_get_field OP_linenum增加列号参数 Parse期间分析并发射列号 收集成单独的mapping文件 减少产物大小 二进制文件 行列号映射文件
37. Debugger线程 异步线程修改 debug 标记位 JS线程 JS引擎执行中 发送CDP事件 接收CDP指令 Debugger接入事件 JS引擎 断点拦截 模式 • console eval代码 • 设置断点 • 其他 Debug任务
38. 调试功能 效果
39. 预编译二进制 原生 JS 功能上的一些缺陷 问题 解决方案 JS文件有线上损坏的问题 (由于文件或网络的原因被截断) 二进制里增加checksum进行自校验 QuickJS 没有列号映射 影响 JS minify 文件的报错监控 改造离线的parser 在预编译二进制里支持列号映射
40. 预编译二进制 • 功能 JS文件 (QuickJS) Hermes 二进制 QuickJS 原版二进制 Qking 预编译二进制 版本间兼容性 无兼容问题 不支持 不支持 支持 搭建场景文件拼接 支持 不支持 不支持 支持 行列号映射 只支持行号 支持 只支持行号 支持 额外 Resource 打包 不支持 不支持 不支持 支持 文件自校验 不支持 支持 不支持 支持 预编译二进制的功能集是 原生 JS 的超集
41. 预编译二进制 和QuickJS 原版bytecode对比 QuickJS bytecode Strings 预编译二进制 Header • 版本号 magic number等 Checksum • 自校验功能 • 类似原版bytecode格式,存储 Strings function的方法体 Object Object Resource Sections • 额外Resource打包 Meta Section • 携带meta信息 可继续扩展
42. 质量保障
43. 质量保障 跨端 JS 引擎的常见问题 问题 建议 需求 内存泄露,内存占用 高 App不要使用全局的JS 引擎,从业务维度多实 例隔离。确保业务页面 退出可以释放干净。 JS 引擎的创建销毁足够轻量 保障对引擎的API的正 确使用,防止泄露/野 指针出现 API设计本身不容易出错 使用出错的时候可排查定位 JS引擎内野指针造成 crash
44. 质量保障 跨端 JS 引擎的常见问题 JSC V8 相对更重 容易出错 API 设计 不易 出错 JerryScript QuickJS 需求 轻量 JS 引擎的创建销毁足够轻量 引用计数风格API 非常容易出错 API设计本身不容易出错 使用出错的时候可排查定位
45. 质量保障 跨端 JS 引擎的常见问题 1. 可能立刻crash 2. 可能不crash 3. 可能在很久之后不相关的函数里crash 多 Free 一次 Crash! Runtime释放 时间轴 少 Free 一次 相关联的全部 JS 对象一直泄露到引擎释放为止 Runtime释放 时间轴
46. 质量保障 现实情况 墨菲定律: 会出错的事总会出错 问题的特点: 难以定位,出现具有随机性 令人头痛的线上问题
47. LeakChecker 防止泄露 操作堆栈记录: +1 hello.c:32 JS_NewObject 遍历未释放的对象,报告泄露根源 Leak at hello.c:32: ref count: 1 JS_FreeRuntime 时间轴 效果 泄露问题只知道发生了, 不知道为什么 泄露问题一定可以 被发现 & 定位
48. PointerChecker 防止野指针 TAG Object TAG Object 操作堆栈记录: +1 hello.c:32 操作堆栈记录: -1 hello.c:42 JS_NewObject JS_FreeValue TAG Object 报告操作记录并abort: +1 at hello.c:32 -1 at hello.c:42 Use Value 时间轴 效果 问题指针的使用 不一定会被发现 有问题的使用一定会被发现 并且报告出错的位置
49. Checker使用效果 • 发现了大批线上 Binding 代码的错误使用 case • 保障了 Qking 的上层框架在2w行 DOM binding代码中没有线上问题的产生 • 大幅提高了开发期间写 binding 的开发效率
50. 感谢大家观看
51. 扫码回复「D2」 获取第十七届 D2 演讲 PDF 材料 后续也将推送 D2 会后技术文章,敬请关注!!

Accueil - Wiki
Copyright © 2011-2024 iteam. Current version is 2.137.1. UTC+08:00, 2024-11-16 23:43
浙ICP备14020137号-1 $Carte des visiteurs$