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 |