diff --git a/README.md b/README.md index 6666cea..1eda3c5 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,32 @@ resource.GetDBTable(user, req.WithPermission(req.ResAll)) .Select("name", "cost_price") ``` +## 这个库能做什么 + +核心就五个概念,覆盖九成用法: + +| | | 入口 | +| --- | --- | --- | +| **Engine** | 引擎。管编译缓存和配置,一个进程一个 | `jscriptx.New(...)` | +| **Script** | 一份编译好的脚本 + 它的 VM 池,热更新时整体顶替 | `e.Script(name)` | +| **Instance** | 独占一个 VM 的实例,脚本里 `this` 上的状态跨调用保持 | `s.New(ctx, args...)` | +| **Scope** | 作用域。让同一个 ctx 下的多个脚本共享 Go 侧对象 | `jscriptx.WithScope(ctx, ...)` | +| **Extension** | 扩展。给脚本添一个全局对象,并附上 TS 类型 | 实现 `Extension` 接口 | + +用不到就不用看的: + +| | | 入口 | +| --- | --- | --- | +| 脚本从别处来 | 默认从目录加载。要从数据库、配置中心取就实现 `Loader`;多来源叠加、后面盖前面用 `Overlay` | `Loader` / `Tag` / `Overlay` | +| 自己打包 | 只有一段源码、不走目录时用 | `Bundle` / `FinalizeBundle` | +| 自定义调用约定 | 本库不预设脚本回调该长什么样。要按形参个数分派、要传 Go 闭包当 next,用它 | `WithCall` / `Caller` | +| 观测与排错 | VM 池统计;错误带齐「哪个脚本、哪个函数、脚本里哪一行」 | `Stats` / `Error` | +| 装第三方库 | 拉依赖树、摊平成单文件,**目标机器不需要 node** | `esm.Install` / `esm.Vendor` | + +明确**不做**的:不代管实例生命周期(没有按 key 复用、没有空闲回收)、没有事件循环 +(定时器、网络、真正的异步都没有)、不提供沙箱隔离(脚本能拿到你放行的 Go 对象, +它们的方法是真能调的)。 + ## 文档 | | | @@ -685,22 +711,25 @@ Apple M4 Pro,`go test -run XXX -bench . -benchmem`。每行都标了对应的 | 场景 | 基准 | 耗时 | 分配 | | --- | --- | --- | --- | -| 纯 Go 基准线 | `BenchmarkNative` | 96 ns | 1 | -| `Instance.Call`(状态在 JS 实例里) | `BenchmarkInstanceCall` | 454 ns | 10 | -| `Script.Call`(同脚本同参数,架构对照) | `BenchmarkCall_同脚本对照` | 417 ns | 10 | -| `Instance.Call` + 作用域扩展 | `BenchmarkStoreExtension` | 1.05 μs | 29 | -| `Script.Call` + 传 Go 对象当参数 | `BenchmarkCall` | 1.76 μs | 57 | -| 同上,但传可取消的 context | `BenchmarkCall_带可取消context` | 4.33 μs | 63 | -| `Script.New`(建一个实例) | `BenchmarkInstanceNew` | 3.49 μs | 110 | -| 重新编译 + 建全新 VM | `BenchmarkVM新建` | 224 μs | 1687 | +| 纯 Go 基准线 | `BenchmarkNative` | 39 ns | 1 | +| `Instance.Call`(状态在 JS 实例里) | `BenchmarkInstanceCall` | 300 ns | 11 | +| `Script.Call`(同脚本同参数,架构对照) | `BenchmarkCall_同脚本对照` | 342 ns | 11 | +| `Instance.Call` + 作用域扩展 | `BenchmarkStoreExtension` | 935 ns | 29 | +| `Script.Call` + 传 Go 对象当参数 | `BenchmarkCall` | 1.91 μs | 57 | +| 同上,但传可取消的 context | `BenchmarkCall_带可取消context` | 3.47 μs | 63 | +| `Script.New`(建一个实例) | `BenchmarkInstanceNew` | 3.00 μs | 110 | +| 重新编译 + 建全新 VM | `BenchmarkVM新建` | 213 μs | 1687 | + +> 这些是多轮取**最小值**。微基准在有别的负载时能飘 30%,中位数会被离群值带偏—— +> 拿它对比改动前后时务必同机器同口径跑两遍。 几点说明: -- **池化和独占 VM 的架构开销几乎一样**(417 vs 454 ns)。差别大的是状态放哪:放 JS 实例 - 只要 0.45 μs,放作用域扩展 1.05 μs,靠参数把 Go 对象反射包装过去要 1.76 μs—— +- **池化和独占 VM 的架构开销几乎一样**(342 vs 300 ns)。差别大的是状态放哪:放 JS 实例 + 只要 0.3 μs,放作用域扩展 0.94 μs,靠参数把 Go 对象反射包装过去要 1.9 μs—— 那个反射往返才是大头。 - 打包和编译只在**加载和热更新**时发生,不在调用路径上。 -- 传可取消的 `context` 明显变贵(1.76 → 4.33 μs):哨兵要起 goroutine 盯 `ctx.Done()`; +- 传可取消的 `context` 明显变贵(1.91 → 3.47 μs):哨兵要起 goroutine 盯 `ctx.Done()`; 只有超时限制时走定时器快路径。MQTT 那种高频路径建议传 `context.Background()`, 靠 `WithTimeout` 兜底。 diff --git a/doc.go b/doc.go index 885ecba..85f50ae 100644 --- a/doc.go +++ b/doc.go @@ -11,6 +11,33 @@ // .Where(db.C("name").Eq("测试产品")) // .Select("name", "cost_price") // +// # 这个库能做什么 +// +// 核心就五个概念,覆盖九成用法: +// +// Engine 引擎。管编译缓存和配置,一个进程一个 +// Script 一份编译好的脚本 + 它的 VM 池。热更新时整体顶替 +// Instance 独占一个 VM 的实例,脚本里 this 上的状态跨调用保持 +// Scope 作用域。让同一个 ctx 下的多个脚本共享 Go 侧对象 +// Extension 扩展。给脚本添一个全局对象,并附上 TS 类型 +// +// 用不到就不用看的(按需要翻): +// +// 脚本从哪来 Loader / LoaderFunc / Prepared / Versioned / Tag / Overlay +// 默认从目录加载(esm 子包)。要从数据库、配置中心取,实现 Loader; +// 多来源叠加、后面盖前面,用 Overlay +// 自己打包 Bundle / FinalizeBundle / BundleOption +// 只有一段源码、不走目录时用 +// 自定义调用约定 Caller / Result / WithCall +// 本库不预设脚本回调该长什么样。要按形参个数分派、要传 Go 闭包 +// 当 next,用它 +// 观测与排错 Stats / Error / Kind / Frame +// VM 池统计;错误带齐「哪个脚本、哪个函数、脚本里哪一行」 +// +// 明确**不做**的:不代管实例生命周期(没有按 key 复用、没有空闲回收)、 +// 没有事件循环(定时器、网络、真正的异步都没有)、不提供沙箱隔离 +// (脚本能拿到你放行的 Go 对象,它们的方法是真能调的)。 +// // # 基本用法 // // loader, err := esm.NewLoader("app/src")