强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第2章:JIT 工作原理

第2章:JIT 工作原理

“JIT 编译器的精妙之处,在于它能在程序运行的同时,悄无声息地完成优化。”

2.1 JIT 编译的整体架构

一个典型的 JIT 编译系统包含以下核心组件:

源代码 / 动态语言代码
         │
         ▼
   ┌─────────────┐
   │   解释器     │  ← 初始执行路径
   │ (Interpreter)│
   └──────┬──────┘
          │ 收集 Profile 数据
          ▼
   ┌─────────────┐
   │ 热点检测器   │  ← 识别值得优化的代码
   │ (Profiler)   │
   └──────┬──────┘
          │ 触发编译
          ▼
   ┌─────────────┐
   │  前端        │  ← 解析、生成 IR
   │  (Frontend)  │
   └──────┬──────┘
          │
          ▼
   ┌─────────────┐
   │  优化管线    │  ← 多趟优化
   │ (Optimizer)  │
   └──────┬──────┘
          │
          ▼
   ┌─────────────┐
   │  后端        │  ← 生成机器码
   │  (Backend)   │
   └──────┬──────┘
          │
          ▼
   ┌─────────────┐
   │ 代码缓存     │  ← 存储生成的机器码
   │ (Code Cache) │
   └─────────────┘

2.2 字节码与解释执行

2.2.1 什么是字节码

字节码(Bytecode)是源代码编译后的中间表示,比源码低级但比机器码高级。它设计为解释器可以高效执行的格式。

// Java 源码
public int add(int a, int b) {
    int result = a + b;
    return result;
}

// 对应字节码 (javap -c)
// 0: iload_1      // 加载局部变量 1 (参数 a)
// 1: iload_2      // 加载局部变量 2 (参数 b)
// 2: iadd         // 整数加法
// 3: istore_3     // 存储到局部变量 3 (result)
// 4: iload_3      // 加载 result
// 5: ireturn      // 返回整数

2.2.2 不同语言的字节码格式

语言字节码格式特点
JavaJVM Bytecode栈式架构,类型丰富
PythonCPython Bytecode栈式架构,动态类型
C#CIL (Common Intermediate Language)栈式架构,面向对象
LuaLua Bytecode寄存器式架构
JavaScriptV8 Bytecode (Ignition)寄存器式架构

2.2.3 自定义字节码设计示例

// 简单虚拟机的字节码定义
typedef enum {
    OP_LOAD_CONST,  // 加载常量
    OP_LOAD_VAR,    // 加载变量
    OP_STORE_VAR,   // 存储变量
    OP_ADD,         // 加法
    OP_SUB,         // 减法
    OP_MUL,         // 乘法
    OP_DIV,         // 除法
    OP_CALL,        // 函数调用
    OP_RETURN,      // 返回
    OP_JUMP,        // 无条件跳转
    OP_JUMP_IF_FALSE, // 条件跳转
} Opcode;

// 字节码指令结构
typedef struct {
    uint8_t opcode;
    uint16_t operand;
} Instruction;

// 解释器循环
Value execute(VM* vm, Function* func) {
    Instruction* ip = func->code;  // 指令指针
    Value stack[256];              // 操作数栈
    int sp = 0;                    // 栈指针

    for (;;) {
        switch (ip->opcode) {
            case OP_LOAD_CONST:
                stack[sp++] = func->constants[ip->operand];
                break;
            case OP_ADD: {
                Value b = stack[--sp];
                Value a = stack[--sp];
                stack[sp++] = a + b;
                break;
            }
            case OP_RETURN:
                return stack[--sp];
            // ... 其他指令
        }
        ip++;
    }
}

2.3 中间表示(IR)

2.3.1 IR 的作用

中间表示(Intermediate Representation,IR)是编译器内部使用的代码表示形式,连接前端和后端。

源代码 → [前端] → IR → [优化器] → 优化IR → [后端] → 机器码
         (解析)        (多趟优化)           (代码生成)

2.3.2 常见 IR 类型

IR 类型代表特点
AST早期编译器保留源码结构
三地址码GCC简单、易优化
SSALLVM、HotSpot静态单赋值,优化友好
Sea of NodesV8 TurboFan、Graal图表示,灵活

2.3.3 SSA 形式的 IR

SSA(Static Single Assignment,静态单赋值)是一种 IR 形式,每个变量只被赋值一次。

// 原始代码
int x = 1;
x = x + 2;
x = x * 3;

// SSA 形式
int x1 = 1;
int x2 = x1 + 2;
int x3 = x2 * 3;

// 含分支的 SSA (使用 φ 函数)
// 原始代码
int x = 1;
if (cond) {
    x = 2;
}
print(x);

// SSA 形式
int x1 = 1;
if (cond) {
    int x2 = 2;
    // ...
}
int x3 = φ(x1, x2);  // φ 函数选择来自不同路径的值
print(x3);

2.3.4 Graal IR 示例(Sea of Nodes)

// Java 源码
public int compute(int x) {
    int a = x * 2;
    int b = a + 1;
    return b;
}

// Graal IR (Sea of Nodes) 概念表示
// 每个节点代表一个操作,边代表数据/控制依赖

// Node 1: Parameter(0) [x]
// Node 2: Constant(2)
// Node 3: Mul(Node 1, Node 2) [a]
// Node 4: Constant(1)
// Node 5: Add(Node 3, Node 4) [b]
// Node 6: Return(Node 5)

2.3.5 自定义 SSA IR

// Rust 实现简单 SSA IR
#[derive(Debug, Clone)]
enum Value {
    Const(i64),
    Param(usize),
    BinaryOp {
        op: BinaryOp,
        left: Box<Value>,
        right: Box<Value>,
    },
}

#[derive(Debug, Clone)]
enum BinaryOp {
    Add,
    Sub,
    Mul,
    Div,
}

#[derive(Debug)]
struct Instruction {
    result: Variable,
    value: Value,
}

#[derive(Debug)]
struct BasicBlock {
    label: String,
    instructions: Vec<Instruction>,
    terminator: Terminator,
}

#[derive(Debug)]
enum Terminator {
    Return(Value),
    Branch {
        condition: Value,
        true_target: String,
        false_target: String,
    },
    Jump(String),
}

// 使用示例
fn build_ssa_example() -> Vec<BasicBlock> {
    // 计算 fib(n)
    // entry:
    //   n_1 = Param(0)
    //   cmp = n_1 <= 1
    //   Branch(cmp, base_case, recursive)
    // base_case:
    //   Return(n_1)
    // recursive:
    //   n_2 = n_1 - 1
    //   call1 = Call(fib, n_2)
    //   n_3 = n_1 - 2
    //   call2 = Call(fib, n_3)
    //   result = call1 + call2
    //   Return(result)
    todo!()
}

2.4 编译管线

2.4.1 典型编译管线阶段

┌──────────────────────────────────────────────────────────┐
│                   JIT 编译管线                            │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  1. 解析 (Parsing)                                       │
│     └─ 源码 → AST                                        │
│                                                          │
│  2. 字节码生成 (Bytecode Generation)                      │
│     └─ AST → 字节码                                      │
│                                                          │
│  3. IR 生成                                              │
│     └─ 字节码 → SSA IR                                   │
│                                                          │
│  4. 优化遍 (Optimization Passes)                         │
│     ├─ 常量折叠 (Constant Folding)                        │
│     ├─ 死代码消除 (Dead Code Elimination)                │
│     ├─ 公共子表达式消除 (CSE)                             │
│     ├─ 内联 (Inlining)                                   │
│     ├─ 循环优化 (Loop Optimization)                      │
│     ├─ 逃逸分析 (Escape Analysis)                        │
│     └─ 寄存器分配 (Register Allocation)                  │
│                                                          │
│  5. 代码生成 (Code Generation)                           │
│     └─ 优化 IR → 机器码                                  │
│                                                          │
│  6. 代码安装 (Code Installation)                         │
│     └─ 替换解释器入口                                    │
│                                                          │
└──────────────────────────────────────────────────────────┘

2.4.2 核心优化技术

常量折叠(Constant Folding)

// 优化前
int x = 3 + 4;       // 编译时可计算
int y = x * 2;       // x 已知为常量

// 优化后
int y = 14;           // 直接计算结果

死代码消除(Dead Code Elimination)

// 优化前
int compute(int x) {
    int a = x * 2;     // a 被使用
    int b = x * 3;     // b 未被使用
    int c = a + 1;     // c 被使用
    return c;
}

// 优化后
int compute(int x) {
    int a = x * 2;
    return a + 1;
}

公共子表达式消除(CSE)

// 优化前
int a = x * y + z;
int b = x * y - z;

// 优化后
int temp = x * y;
int a = temp + z;
int b = temp - z;

内联(Inlining)

// 优化前
public int square(int x) {
    return multiply(x, x);
}

public int multiply(int a, int b) {
    return a * b;
}

// 优化后 (multiply 被内联)
public int square(int x) {
    return x * x;
}

2.4.3 Graal 编译管线示例

// 使用 GraalVM 的 Ideal Graph Visualizer 查看 IR
// 启动参数: -Dgraal.Dump=:1

public class GraalPipelineDemo {
    
    public static int compute(int n) {
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += i * 2;
        }
        return sum;
    }
    
    public static void main(String[] args) {
        // 预热以触发 JIT 编译
        for (int i = 0; i < 100_000; i++) {
            compute(1000);
        }
    }
}

// 编译管线会执行以下主要优化:
// 1. 构建 Sea of Nodes IR
// 2. 高阶图嵌入 (High-tier Graph Embedding)
// 3. 框架图 (Framework Graph)
// 4. 中阶优化 (Mid-tier Optimizations)
//    - Partial Escape Analysis
//    - Loop Predication
// 5. 低阶优化 (Low-tier Optimizations)
//    - Register Allocation
//    - Instruction Selection
// 6. 机器码生成

2.5 热点代码检测

2.5.1 热点检测策略

策略说明优点缺点
计数器统计方法调用次数简单、低开销难以区分循环热点
回边计数器统计循环回边执行次数精准检测循环只能检测循环
采样定期采样 PC开销极低不够精确
Trace记录执行轨迹捕获跨函数热点实现复杂

2.5.2 HotSpot 的热点检测

// HotSpot 使用两种计数器
// 1. 方法调用计数器 (Invocation Counter)
// 2. 回边计数器 (Back Edge Counter)

// -XX:CompileThreshold=10000  方法调用阈值
// -XX:OnStackReplacePercentage=140  OSR 阈值百分比

// 热点检测示例
public class HotspotDetectionDemo {
    
    // 方法级热点:被调用多次
    public void frequentlyCalled() {
        // 这个方法被调用超过 CompileThreshold 次后触发 JIT
    }
    
    // 循环级热点:循环执行多次
    public void loopIntensive() {
        for (int i = 0; i < 1_000_000; i++) {
            // 回边计数器增长,触发 OSR 编译
        }
    }
    
    public static void main(String[] args) {
        HotspotDetectionDemo demo = new HotspotDetectionDemo();
        
        // 触发方法级热点
        for (int i = 0; i < 20_000; i++) {
            demo.frequentlyCalled();
        }
        
        // 触发循环级热点
        demo.loopIntensive();
    }
}
# 查看热点检测
java -XX:+PrintCompilation \
     -XX:+UnlockDiagnosticVMOptions \
     -XX:+LogCompilation \
     HotspotDetectionDemo

# 日志输出示例:
# 1234  42  3  HotspotDetectionDemo::frequentlyCalled (4 bytes)
#   编译层次 3 = C1 编译,带完整 Profile

2.5.3 自定义热点检测器

# Python 实现简单热点检测器
import time
from typing import Dict, Callable
from functools import wraps

class HotspotDetector:
    """基于计数器的热点检测器"""
    
    def __init__(self, threshold: int = 1000):
        self.threshold = threshold
        self.counters: Dict[str, int] = {}
        self.hot_functions: set = set()
    
    def track(self, func: Callable) -> Callable:
        """装饰器:跟踪函数调用次数"""
        name = func.__qualname__
        self.counters[name] = 0
        
        @wraps(func)
        def wrapper(*args, **kwargs):
            self.counters[name] += 1
            
            # 检查是否达到阈值
            if (self.counters[name] >= self.threshold and 
                name not in self.hot_functions):
                self.hot_functions.add(name)
                self._on_hotspot_detected(name)
            
            return func(*args, **kwargs)
        
        return wrapper
    
    def _on_hotspot_detected(self, name: str):
        """热点回调"""
        print(f"[Hotspot] {name} 已被标记为热点 (调用次数: {self.counters[name]})")
        # 在这里可以触发 JIT 编译
    
    def get_stats(self) -> dict:
        """获取统计信息"""
        return {
            'total_functions': len(self.counters),
            'hot_functions': len(self.hot_functions),
            'counters': dict(self.counters),
        }

# 使用示例
detector = HotspotDetector(threshold=500)

@detector.track
def compute_sum(n: int) -> int:
    return sum(range(n))

@detector.track
def process_data(data: list) -> list:
    return [x * 2 for x in data]

# 模拟热点函数
for i in range(1000):
    compute_sum(100)
    process_data([1, 2, 3])

print(detector.get_stats())

2.6 去优化(Deoptimization)

2.6.1 什么是去优化

去优化(Deoptimization)是 JIT 编译的逆过程,当优化后的代码假设不再成立时,回退到解释执行。

正常流程:
  字节码 → [JIT编译] → 优化机器码 → 执行

去优化流程:
  优化机器码 → [假设失败] → 恢复解释器状态 → 解释执行 → [可能重新编译]

2.6.2 触发去优化的常见原因

原因说明示例
类型假设失败优化假设的类型改变int 变为 string
分支预测失败未预测到的分支执行罕见的异常路径
类层次变化加载新类破坏假设新子类加载
栈上替换失败OSR 编译假设不成立循环条件改变

2.6.3 V8 去优化示例

// V8 去优化详细示例

// 场景1:类型改变
function add(a, b) {
    return a + b;
}

// 初始调用:整数
for (let i = 0; i < 10000; i++) {
    add(i, i);  // JIT 编译为整数加法
}

// 突然传入字符串
add("hello", "world");  // 去优化!

// 场景2:对象形状改变
function getX(obj) {
    return obj.x;
}

// 对象形状一致
let obj1 = { x: 1, y: 2 };
let obj2 = { x: 3, y: 4 };
for (let i = 0; i < 10000; i++) {
    getX(obj1);
    getX(obj2);
}

// 形状改变
let obj3 = { x: 5, z: 6 };  // 不同的属性布局
getX(obj3);  // 可能触发去优化
# 查看 V8 去优化日志
node --trace-opt --trace-deopt demo.js

# 输出示例:
# [marking 0x... for optimization to TURBOFAN]
# [compiling method 0x... using TurboFan]
# [deoptimizing (DEOPT eager): begin 0x... (opt #1)]
#   ;; deopt reason: wrong map
#   ;; deopt location: demo.js:3

2.6.4 Java HotSpot 去优化

// Java 去优化示例
public class DeoptDemo {
    
    // 初始假设:obj 总是 A 类型
    public int process(Object obj) {
        if (obj instanceof A) {
            return ((A) obj).getValue();
        }
        return 0;
    }
    
    static class A {
        int getValue() { return 1; }
    }
    
    static class B extends A {
        @Override
        int getValue() { return 2; }
    }
    
    public static void main(String[] args) {
        DeoptDemo demo = new DeoptDemo();
        A a = new A();
        
        // 预热:只使用 A 类型
        for (int i = 0; i < 100_000; i++) {
            demo.process(a);
        }
        
        // JIT 假设:obj 总是 A 类型
        // 类型检查被优化掉
        
        // 加载 B 类后,假设可能失效
        // 使用 B 类型
        B b = new B();
        demo.process(b);  // 可能触发去优化
    }
}

// 查看去优化
// java -XX:+PrintCompilation -XX:+TraceDeoptimization DeoptDemo

2.6.5 减少去优化的最佳实践

// JavaScript - 保持类型一致性

// ❌ 不好的做法:类型不一致
function process(value) {
    if (typeof value === 'number') {
        return value * 2;
    }
    return value.toUpperCase();  // 字符串分支
}

// 交替调用不同类型
process(42);
process("hello");
process(100);
process("world");

// ✅ 好的做法:分离类型
function processNumber(value) {
    return value * 2;
}

function processString(value) {
    return value.toUpperCase();
}
// Java - 避免类层次假设被破坏

// ❌ 不好的做法
// JIT 假设只有 A 类型
public void process(A obj) {
    obj.doSomething();
}

// 后期加载的子类可能破坏假设

// ✅ 好的做法:使用 final 或 sealed
public final class A {
    public void doSomething() { }
}

// 或使用 sealed class (Java 17+)
public sealed class Shape permits Circle, Rectangle {
    public double area() { return 0; }
}

2.7 内联缓存(Inline Cache)

2.7.1 内联缓存原理

内联缓存是一种加速动态语言属性访问的技术,在调用点缓存类型信息。

第一次调用:
  obj.x  →  查找属性 x (慢速路径)
  缓存: obj 的形状(Shape) → x 的偏移量

后续调用:
  obj.x  →  检查形状是否匹配
  如果匹配 → 直接访问偏移量 (快速路径)
  如果不匹配 → 重新查找 (慢速路径)

2.7.2 V8 内联缓存状态

┌─────────────────────────────────────────────────────────┐
│              V8 内联缓存 (Inline Cache) 状态             │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  UNINITIALIZED  →  初始状态,未执行过                     │
│       │                                                 │
│       ▼                                                 │
│  MONOMORPHIC    →  只见过一种形状(最快)                  │
│       │                                                 │
│       ▼ (见到第二种形状)                                  │
│  POLYMORPHIC    →  见过2-4种形状(较快)                  │
│       │                                                 │
│       ▼ (见到超过4种形状)                                 │
│  MEGAMORPHIC    →  退化为通用路径(最慢)                  │
│                                                         │
└─────────────────────────────────────────────────────────┘

2.7.3 内联缓存实现示例

// 简化版内联缓存实现
typedef struct {
    Shape* cached_shape;    // 缓存的对象形状
    int property_offset;    // 属性在对象中的偏移
} InlineCache;

typedef struct {
    InlineCache ic;
    // ... 其他字段
} PropertyAccessSite;

// 属性访问
Value ic_load_property(PropertyAccessSite* site, Object* obj) {
    // 快速路径:形状匹配
    if (obj->shape == site->ic.cached_shape) {
        return obj->properties[site->ic.property_offset];
    }
    
    // 慢速路径:查找属性
    int offset = lookup_property(obj, "propertyName");
    
    // 更新缓存
    site->ic.cached_shape = obj->shape;
    site->ic.property_offset = offset;
    
    return obj->properties[offset];
}

2.8 逃逸分析

2.8.1 什么是逃逸分析

逃逸分析(Escape Analysis)判断对象是否逃逸出当前方法或线程,指导优化决策。

// 逃逸分析示例
public class EscapeAnalysisDemo {
    
    // 对象不逃逸:可以标量替换
    public int compute() {
        Point p = new Point(1, 2);  // p 不逃逸
        return p.x + p.y;
    }
    
    // 对象逃逸:无法优化
    private Point lastPoint;
    public void save(int x, int y) {
        lastPoint = new Point(x, y);  // 逃逸到实例字段
    }
    
    // 部分逃逸:可以进行部分优化
    public int partialEscape(boolean flag) {
        Point p = new Point(1, 2);
        if (flag) {
            return p.x;  // 只在部分路径使用
        }
        return 0;  // 另一条路径不使用 p
    }
}

2.8.2 逃逸分析的优化效果

优化条件效果
标量替换对象不逃逸用基本类型替代对象分配
栈上分配对象不逃逸出线程在栈上分配而非堆
锁消除对象不逃逸出线程移除同步操作
部分逃逸对象仅在部分路径使用条件分配
// 优化前
public int sum() {
    Point p = new Point(3, 4);
    return p.x + p.y;
}

// 优化后 (标量替换)
public int sum() {
    int x = 3;
    int y = 4;
    return x + y;  // 无对象分配
}

2.9 循环优化

2.9.1 常见循环优化技术

public class LoopOptimizations {
    
    // 1. 循环不变量外提 (Loop-Invariant Code Motion)
    public void hoist(int[] arr, int x) {
        // 优化前
        for (int i = 0; i < arr.length; i++) {
            arr[i] = x * 10;  // x * 10 是循环不变量
        }
        
        // 优化后
        int temp = x * 10;
        for (int i = 0; i < arr.length; i++) {
            arr[i] = temp;
        }
    }
    
    // 2. 循环展开 (Loop Unrolling)
    public void unroll(int[] arr) {
        // 优化前
        for (int i = 0; i < arr.length; i++) {
            arr[i] = arr[i] * 2;
        }
        
        // 优化后 (4x展开)
        int i = 0;
        for (; i + 3 < arr.length; i += 4) {
            arr[i] = arr[i] * 2;
            arr[i+1] = arr[i+1] * 2;
            arr[i+2] = arr[i+2] * 2;
            arr[i+3] = arr[i+3] * 2;
        }
        for (; i < arr.length; i++) {
            arr[i] = arr[i] * 2;
        }
    }
    
    // 3. 循环向量化 (Vectorization)
    public void vectorize(int[] a, int[] b, int[] c) {
        // 利用 SIMD 指令
        for (int i = 0; i < a.length; i++) {
            c[i] = a[i] + b[i];
        }
    }
}

2.10 本章小结

核心概念

┌─────────────────────────────────────────────────────────┐
│                 JIT 工作原理要点                          │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  1. 字节码是 JIT 的起点                                  │
│     └─ 紧凑、跨平台、易于解释执行                         │
│                                                         │
│  2. IR 是优化的核心                                      │
│     └─ SSA 形式便于分析和优化                            │
│                                                         │
│  3. 热点检测触发编译                                     │
│     └─ 计数器、采样、Trace                               │
│                                                         │
│  4. 多趟优化提升性能                                     │
│     └─ 常量折叠、内联、逃逸分析、向量化                   │
│                                                         │
│  5. 去优化保证正确性                                     │
│     └─ 类型假设失败时回退到解释执行                       │
│                                                         │
│  6. 内联缓存加速动态语言                                 │
│     └─ 缓存类型信息,避免重复查找                         │
│                                                         │
└─────────────────────────────────────────────────────────┘

2.11 扩展阅读

推荐资源

  • “Compiler Design in C” by Allen Holub - 编译器设计经典
  • “Engineering a Compiler” by Cooper & Torczon - 现代编译器工程
  • LLVM Tutorial - 实现简单 JIT 编译器
  • GraalVM Documentation - 深入理解现代 JIT 编译器

在线工具


上一章: 第1章 - JIT 编译概述 下一章: 第3章 - LuaJIT