一图流
语言范式的根本矛盾和调和
编译型语言
- 预先编译成机器相关的机器码(执行效率高)
- 强类型支持静态内存布局(执行&内存效率高)
- 函数调用采用固定偏移量寻址
- 执行效率比解释器高 10 - 100 倍
解释型语言
- 灵活
- 访问属性需要哈希查找
- eval 等特性导致无法预知的上下文污染
- 闭包链管理开销
JIT 哲学
运行时类型特化
runtime Specialization
哲学本质:将动态类型转换为静态类型信息,在保证语言灵活性的前提下创造确定性的优化空间
隐藏类系统(Hidden Class)
- 相同构造方式的对象共享隐藏类
csharp
// 构造器顺序决定隐藏类结构
function A() { this.x = 1; this.y = 2 } // 隐藏类C1
function B() { this.y = 2; this.x = 1 } // 隐藏类C2(与C1不同)
- 属性访问转换为固定偏移量查找
ini
; 机器码示例(x64)
mov rax, [obj + 0x18] ; 直接内存偏移访问
内联缓存(Inline Cache)
- 记录最近10次调用点的类型信息
- 生成特化的机器码片段
javascript
function add(x, y) {
return x + y; // 根据x,y类型生成特化代码
}
- 单态调用(单一类型)速度接近C++
- 多态调用(2-4种类型)性能下降50%
- 超多态(5+类型)退化为哈希查找
投机优化
speculative Optimization
哲学本质:基于概率统计的预测执行,用空间换时间换取大概率性能提升
类型预测(Type Prediction)
ini
// 热代码段的类型假设
for (let i = 0; i < 1e6; i++) {
sum += arr[i]; // 假设arr元素始终是SMALL_INTEGER
}
- 生成带类型检查的机器码
ini
cmp qword [rax], 0x1a3 ; 检查隐藏类
jne deoptimization ; 跳转到去优化例程
分支预测(Branch Prediction)
- 记录循环条件的历史走向
- 预编译最可能的执行路径
典型案例:
sql
// 引擎会优先优化true分支
if (condition) { /* 执行概率90% */ }
else { /* 10%概率 */ }
渐进式编译
tiered Compilcation
哲学本质:在编译开销与执行效率之间动态平衡,实现冷热代码区别对待
从解释到提前编译的升级过程(基于函数/循环的迭代次数判断是否编译)
- 解释执行,生成紧凑字节码(收集反馈信息)
- 基线编译器(baseLine)生成简单优化的机器码(编译耗时1ms内)
- 优化编译器(Trubofan) ,激进的进行优化(编译耗时 10 - 100ms)
去优化陷阱
deoptimization Bailout
哲学本质:承认预测可能失败,建立安全回退机制保障正确性
对于
- 属性类型突变
- 函数多态
会承认优化无效,抛弃优化代码。重建解释器
去优化 > 3 次则永久禁用优化
最佳实践
了解JS引擎的优化思路,以避免写出反优化的代码
经过高效优化的JS在浏览器中能够达到C++的70%以上
- 避免元素类型震荡
js 对于同种类型的数组会采用不同的内存格式策略
ini
const arr = [1,2,3]; // PACKED_SMI_ELEMENTS
arr.push(4.5); // → PACKED_DOUBLE_ELEMENTS
arr[2] = {}; // → PACKED_ELEMENTS
- 避免动态属性破坏隐藏类
ini
// 反模式代码
function createObj() {
const obj = {};
for (let i = 0; i < 10; i++) {
obj[`prop${i}`] = i; // 每次迭代改变隐藏类
}
return obj;
}
- 使用类型稳定的数组和结构稳定的对象
笔者才疏学浅,各位读者多多担待,不吝赐教。