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

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 解释
                   └──────────────────→ 启动速度
                   快 ←              → 慢

基准测试概览

测试项QuickJSV8 (Node 20)Lua 5.4Duktape
fib(35) 递归3.5s0.08s1.5s8.0s
数组排序 (10K)12ms0.5ms8ms35ms
字符串拼接 (10K)5ms0.3ms3ms15ms
JSON 解析 (1MB)45ms8msN/A120ms
启动时间0.5ms30ms0.2ms0.3ms
内存 (Hello World)200KB8MB50KB150KB

结论: 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 的详细对比

不同场景的性能特征

场景QuickJSV8推荐
计算密集(循环、数学)慢 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

扩展阅读