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

JIT 编译与业务结合实战教程 / 第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 不同语言的字节码格式

语言 字节码格式 特点
Java JVM Bytecode 栈式架构,类型丰富
Python CPython Bytecode 栈式架构,动态类型
C# CIL (Common Intermediate Language) 栈式架构,面向对象
Lua Lua Bytecode 寄存器式架构
JavaScript V8 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 简单、易优化
SSA LLVM、HotSpot 静态单赋值,优化友好
Sea of Nodes V8 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