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

QuickJS 嵌入式 JavaScript 引擎完全教程 / 08 - 性能优化

性能优化

8.1 QuickJS 性能特性

QuickJS 使用纯解释执行(字节码),没有 JIT 编译器。这意味着它的执行速度比 V8 慢约 20-50 倍,但启动速度极快,内存占用极低。

性能定位

                 执行速度
                   ↑
                   │  V8 (Node.js)
                   │  ★ JIT 优化后
                   │
                   │  LuaJIT
                   │  ★
                   │
                   │  V8 (首次运行)
                   │  ★ JIT 预热前
                   │
                   │  Lua (PUC-Rio)
                   │  ★
                   │
                   │  QuickJS
                   │  ★ 字节码解释
                   │
                   │  Duktape
                   │  ★ ES5.1 解释
                   └──────────────────→ 启动速度
                   快 ←              → 慢

基准测试概览

测试项 QuickJS V8 (Node 20) Lua 5.4 Duktape
fib(35) 递归 3.5s 0.08s 1.5s 8.0s
数组排序 (10K) 12ms 0.5ms 8ms 35ms
字符串拼接 (10K) 5ms 0.3ms 3ms 15ms
JSON 解析 (1MB) 45ms 8ms N/A 120ms
启动时间 0.5ms 30ms 0.2ms 0.3ms
内存 (Hello World) 200KB 8MB 50KB 150KB

结论: QuickJS 不适合计算密集型任务,但非常适合轻量级脚本执行、配置解析和事件处理。


8.2 字节码优化

预编译字节码

# 将 JavaScript 预编译为字节码
./qjsc -o app.qjsc -m app.js

# 生成 C 嵌入文件(编译时嵌入,零加载时间)
./qjsc -c -o app_bytecode.h -m app.js

字节码 vs 源码加载性能

// benchmark_bytecode.c
#include "quickjs-libc.h"
#include <stdio.h>
#include <time.h>

const char *js_source = R"(
    function fibonacci(n) {
        if (n <= 1) return n;
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
    fibonacci(20);
)";

// 预编译的字节码(通过 qjsc -c 生成)
// #include "bytecode.h"

void benchmark_source(JSContext *ctx, int iterations) {
    clock_t start = clock();
    for (int i = 0; i < iterations; i++) {
        JSValue result = JS_Eval(ctx, js_source, strlen(js_source),
                                  "<bench>", 0);
        JS_FreeValue(ctx, result);
    }
    double elapsed = (double)(clock() - start) / CLOCKS_PER_SEC;
    printf("Source: %.3f ms total, %.3f ms per execution\n",
           elapsed * 1000, elapsed * 1000 / iterations);
}

void benchmark_bytecode(JSContext *ctx, int iterations) {
    // 第一次编译
    JSValue obj = JS_Eval(ctx, js_source, strlen(js_source),
                           "<bench>", JS_EVAL_FLAG_COMPILE_ONLY);
    // 获取字节码
    size_t bytecode_len;
    uint8_t *bytecode = JS_WriteObject(ctx, &bytecode_len, obj,
                                        JS_WRITE_OBJ_BYTECODE);
    JS_FreeValue(ctx, obj);

    clock_t start = clock();
    for (int i = 0; i < iterations; i++) {
        // 从字节码加载并执行
        JSValue bc_obj = JS_ReadObject(ctx, bytecode, bytecode_len,
                                        JS_READ_OBJ_BYTECODE);
        JSValue result = JS_EvalFunction(ctx, bc_obj);
        JS_FreeValue(ctx, result);
    }
    double elapsed = (double)(clock() - start) / CLOCKS_PER_SEC;
    printf("Bytecode: %.3f ms total, %.3f ms per execution\n",
           elapsed * 1000, elapsed * 1000 / iterations);

    js_free(ctx, bytecode);
}

编译时嵌入字节码

// embedded_bytecode.c — 最快的加载方式
// 通过 qjsc -c -o bytecode.h 生成

// #include "bytecode.h"
// 会生成类似如下的数组:
// static const uint8_t app_bytecode[] = { 0x02, 0x01, ... };
// static const uint32_t app_bytecode_size = sizeof(app_bytecode);

int main() {
    JSRuntime *rt = JS_NewRuntime();
    JSContext *ctx = JS_NewContext(rt);

    // 直接从内存中的字节码加载(零文件 I/O)
    JSValue obj = JS_ReadObject(ctx, app_bytecode, app_bytecode_size,
                                 JS_READ_OBJ_BYTECODE);
    JSValue result = JS_EvalFunction(ctx, obj);

    // ...
}

8.3 内存优化

内存使用监控

// memory_monitor.c — 详细内存监控
#include "quickjs-libc.h"
#include <stdio.h>

void print_detailed_memory(JSRuntime *rt) {
    JSMemoryUsage usage;
    JS_ComputeMemoryUsage(rt, &usage);

    printf("╔══════════════════════════════════════╗\n");
    printf("║        Memory Usage Report           ║\n");
    printf("╠══════════════════════════════════════╣\n");
    printf("║ Malloc size:    %12zu bytes ║\n", (size_t)usage.malloc_size);
    printf("║ Malloc limit:   %12zu bytes ║\n", (size_t)usage.malloc_limit);
    printf("║ Memory used:    %12zu bytes ║\n", (size_t)usage.memory_used_size);
    printf("║ Memory limit:   %12zu bytes ║\n", (size_t)usage.memory_limit);
    printf("║ Object count:   %12zu       ║\n", (size_t)usage.obj_count);
    printf("║ String count:   %12zu       ║\n", (size_t)usage.str_count);
    printf("║ Object size:    %12zu bytes ║\n", (size_t)usage.obj_size);
    printf("║ String size:    %12zu bytes ║\n", (size_t)usage.str_size);
    printf("║ GC count:       %12zu       ║\n", (size_t)usage.gc_count);
    printf("╚══════════════════════════════════════╝\n");
}

内存优化策略

// memory_strategies.c — 内存优化技巧

// 策略 1:及时释放不再需要的上下文
void strategy_separate_contexts(JSRuntime *rt) {
    // 初始化阶段使用一个 Context
    JSContext *init_ctx = JS_NewContext(rt);
    JSValue init_result = JS_Eval(init_ctx, "...", 10, "init.js", 0);
    // 提取需要的数据...
    JS_FreeContext(init_ctx); // 释放初始化 Context

    // 运行阶段使用另一个 Context(更小内存占用)
    JSContext *run_ctx = JS_NewContext(rt);
    // ...
}

// 策略 2:定期调用 GC
void strategy_gc(JSRuntime *rt) {
    // 手动触发垃圾回收
    JS_RunGC(rt);
}

// 策略 3:限制字符串缓存
void strategy_string_limits(JSRuntime *rt) {
    // QuickJS 会缓存一些内部字符串,设置合理的内存限制
    JS_SetMemoryLimit(rt, 16 * 1024 * 1024);
}

8.4 启动时间优化

启动时间对比

// startup_benchmark.c — 测量启动时间
#include "quickjs-libc.h"
#include <stdio.h>
#include <time.h>

int main() {
    // 测量创建运行时的时间
    clock_t start = clock();

    for (int i = 0; i < 1000; i++) {
        JSRuntime *rt = JS_NewRuntime();
        JSContext *ctx = JS_NewContext(rt);

        JSValue result = JS_Eval(ctx, "1+1", 3, "<test>", 0);
        JS_FreeValue(ctx, result);

        JS_FreeContext(ctx);
        JS_FreeRuntime(rt);
    }

    double elapsed = (double)(clock() - start) / CLOCKS_PER_SEC;
    printf("1000 iterations: %.3f ms total\n", elapsed * 1000);
    printf("Average per iteration: %.3f ms\n", elapsed * 1000 / 1000);
    printf("Per iteration (μs): %.1f μs\n", elapsed * 1000000 / 1000);

    return 0;
}

典型结果:每次初始化 ~100-500 μs

最小化启动时间

// fast_startup.c — 最小化启动时间
#include "quickjs-libc.h"
#include <stdio.h>

// 预编译的初始化脚本字节码
// 通过 qjsc -c -o init_bytecode.h init.js 生成
// #include "init_bytecode.h"

JSContext* fast_create_context(JSRuntime *rt) {
    JSContext *ctx = JS_NewContext(rt);

    // 方式 1:不加载标准库(最快)
    // 适合只做计算,不需要 I/O 的场景

    // 方式 2:加载预编译字节码
    /*
    JSValue obj = JS_ReadObject(ctx, init_bytecode, init_bytecode_size,
                                 JS_READ_OBJ_BYTECODE);
    JSValue result = JS_EvalFunction(ctx, obj);
    JS_FreeValue(ctx, result);
    */

    return ctx;
}

8.5 与 V8 的详细对比

不同场景的性能特征

场景 QuickJS V8 推荐
计算密集(循环、数学) 慢 20-50x 基准 V8
JSON 解析/序列化 慢 5-10x 基准 V8 (大文件) / QuickJS (小文件)
字符串操作 慢 10-20x 基准 V8
函数调用开销 慢 5-8x 基准 V8
启动时间 快 50-100x 基准 QuickJS
内存占用 低 10-50x 基准 QuickJS
二进制体积 小 50x 基准 QuickJS
热路径(循环执行同一代码) 慢 30x+ 基准 V8 (JIT 优化)
冷路径(执行一次) 接近或快于 基准 QuickJS

V8 vs QuickJS 选择决策树

你的应用需要什么?
├── 高性能计算 → V8
├── 大量数据处理 → V8
├── 长时间运行的服务 → V8 (Node.js)
├── 最小嵌入体积 → QuickJS
├── 最低内存占用 → QuickJS
├── 最快启动速度 → QuickJS
├── 安全沙箱执行 → QuickJS (更简单的隔离)
├── 嵌入式设备 → QuickJS
├── 游戏脚本 → 取决于脚本复杂度
│   ├── 简单逻辑 → QuickJS
│   └── 复杂 AI/物理 → V8 或 LuaJIT
└── 配置文件解析 → QuickJS

8.6 代码优化技巧

JavaScript 层面优化

// optimization_tips.js — QuickJS 中的 JavaScript 优化

// 1. 避免 eval(阻止优化)
// ❌ 慢
const result = eval("someExpression");

// ✅ 快
const result = someExpression;

// 2. 使用 for 循环代替高阶函数(在 QuickJS 中差距更大)
// ❌ 较慢
const doubled = arr.map(x => x * 2).filter(x => x > 10);

// ✅ 较快
const doubled = [];
for (let i = 0; i < arr.length; i++) {
    const val = arr[i] * 2;
    if (val > 10) doubled.push(val);
}

// 3. 字符串拼接使用数组 join
// ❌ 慢(大量字符串创建)
let s = "";
for (let i = 0; i < 10000; i++) s += "a";

// ✅ 快
const parts = [];
for (let i = 0; i < 10000; i++) parts.push("a");
const s = parts.join("");

// 4. 缓存属性访问
// ❌ 慢
for (let i = 0; i < arr.length; i++) {
    process(arr[i].x, arr[i].y, arr[i].z);
}

// ✅ 快
for (let i = 0; i < arr.length; i++) {
    const item = arr[i];
    process(item.x, item.y, item.z);
}

// 5. 避免创建临时对象(减少 GC 压力)
// ❌ 每次循环创建新对象
for (let i = 0; i < 10000; i++) {
    const obj = { x: i, y: i * 2 };
    process(obj);
}

// ✅ 重用对象
const obj = { x: 0, y: 0 };
for (let i = 0; i < 10000; i++) {
    obj.x = i;
    obj.y = i * 2;
    process(obj);
}

C 层面优化

// c_optimization.c — 嵌入层面的优化

// 1. 重复使用 JSContext
void optimize_context_reuse() {
    JSRuntime *rt = JS_NewRuntime();
    JSContext *ctx = JS_NewContext(rt);

    // 预加载函数
    JS_Eval(ctx, R"(
        function processData(input) {
            return input.filter(x => x > 0).map(x => x * 2);
        }
    )", 80, "preload.js", 0);

    // 多次调用同一函数(避免重复解析)
    for (int i = 0; i < 100; i++) {
        // 获取函数引用并调用
        JSValue global = JS_GetGlobalObject(ctx);
        JSValue func = JS_GetPropertyStr(ctx, global, "processData");
        // ... 调用函数
        JS_FreeValue(ctx, func);
        JS_FreeValue(ctx, global);
    }

    JS_FreeContext(ctx);
    JS_FreeRuntime(rt);
}

// 2. 批量操作
void optimize_batch(JSContext *ctx) {
    // 一次 eval 多个操作,而不是多次 eval
    JSValue result = JS_Eval(ctx, R"(
        const a = compute1();
        const b = compute2(a);
        const c = compute3(b);
        c;
    )", 70, "<batch>", 0);
    // ...
    JS_FreeValue(ctx, result);
}

// 3. 使用 JS_Eval 的编译+执行分离
void optimize_compile_once(JSContext *ctx, const char *code, size_t len) {
    // 编译一次
    JSValue compiled = JS_Eval(ctx, code, len, "<code>",
                                JS_EVAL_FLAG_COMPILE_ONLY);

    // 转为字节码
    size_t bc_len;
    uint8_t *bc = JS_WriteObject(ctx, &bc_len, compiled,
                                  JS_WRITE_OBJ_BYTECODE);

    // 多次执行
    for (int i = 0; i < 100; i++) {
        JSValue obj = JS_ReadObject(ctx, bc, bc_len,
                                     JS_READ_OBJ_BYTECODE);
        JSValue result = JS_EvalFunction(ctx, obj);
        JS_FreeValue(ctx, result);
    }

    js_free(ctx, bc);
    JS_FreeValue(ctx, compiled);
}

8.7 基准测试套件

// benchmark_suite.js — QuickJS 综合基准测试
const benchmarks = {};

function register(name, fn) {
    benchmarks[name] = fn;
}

function runAll() {
    console.log("=== QuickJS Benchmark Suite ===\n");

    for (const [name, fn] of Object.entries(benchmarks)) {
        // 预热
        for (let i = 0; i < 3; i++) fn();

        // 正式测试
        const start = Date.now();
        const iterations = 5;
        for (let i = 0; i < iterations; i++) fn();
        const elapsed = Date.now() - start;

        console.log(`${name}: ${(elapsed / iterations).toFixed(1)}ms avg`);
    }
}

// 测试用例
register("Fibonacci(30)", () => {
    function fib(n) {
        if (n <= 1) return n;
        return fib(n - 1) + fib(n - 2);
    }
    fib(30);
});

register("Array sort (10K)", () => {
    const arr = [];
    for (let i = 0; i < 10000; i++) arr.push(Math.random());
    arr.sort((a, b) => a - b);
});

register("String concat (1K)", () => {
    let s = "";
    for (let i = 0; i < 1000; i++) s += String.fromCharCode(65 + (i % 26));
});

register("JSON parse (10KB)", () => {
    const obj = { items: [] };
    for (let i = 0; i < 100; i++) {
        obj.items.push({ id: i, name: `item-${i}`, value: Math.random() });
    }
    const json = JSON.stringify(obj);
    for (let i = 0; i < 100; i++) JSON.parse(json);
});

register("Object creation (10K)", () => {
    for (let i = 0; i < 10000; i++) {
        const obj = { x: i, y: i * 2, z: i * 3 };
    }
});

register("RegExp match (1K)", () => {
    const re = /(\d{4})-(\d{2})-(\d{2})/;
    for (let i = 0; i < 1000; i++) {
        re.test("2024-01-15");
    }
});

runAll();

8.8 性能监控

// perf_monitor.c — 运行时性能监控
#include "quickjs-libc.h"
#include <stdio.h>
#include <time.h>

typedef struct {
    clock_t eval_start;
    long eval_count;
    double total_eval_time;
    long gc_count;
} PerfStats;

static PerfStats g_stats = {0};

JSValue monitored_eval(JSContext *ctx, const char *code,
                        const char *filename) {
    g_stats.eval_count++;
    clock_t start = clock();

    JSValue result = JS_Eval(ctx, code, strlen(code), filename, 0);

    double elapsed = (double)(clock() - start) / CLOCKS_PER_SEC;
    g_stats.total_eval_time += elapsed;

    return result;
}

void print_perf_stats() {
    printf("=== Performance Stats ===\n");
    printf("Eval count:      %ld\n", g_stats.eval_count);
    printf("Total eval time: %.3f ms\n", g_stats.total_eval_time * 1000);
    printf("Avg eval time:   %.3f ms\n",
           g_stats.eval_count > 0 ?
           g_stats.total_eval_time * 1000 / g_stats.eval_count : 0);
}

8.9 本章小结

要点 说明
设计定位 QuickJS 优化启动速度和内存占用,非执行速度
字节码预编译 使用 qjsc 显著减少加载时间
内存管理 合理设置限制,及时释放 Context
JavaScript 优化 避免 eval、减少临时对象、缓存属性访问
C 层面优化 重用 Context、批量操作、编译执行分离
V8 对比 计算密集选 V8,启动/体积敏感选 QuickJS

扩展阅读