插件系统是一种性能税
你安装了 VS Code。很快。你添加了 15 个扩展。现在启动要 4 秒,Extension Host 占用 800 MB 内存。发生了什么?
这种模式到处重复:WordPress、Eclipse、Chrome、Figma、Slack。应用发布时很快。插件让它变慢。没人再感到惊讶了 —— 我们已经接受它作为可扩展性的代价。
但插件系统不仅仅是性能问题。它们是设计哲学问题。行业将"可扩展性"与"运行时动态性"混为一谈,而往往更好的答案是编译时组合。唯一高性能的插件是那些在编译时不再是插件的。
可扩展性的性能谱系
不是所有的可扩展性代价都相同。从零开销到最大开销有一个谱系,而行业大部分选择了昂贵的一端:
- 静态链接 / 编译时模块 — 零开销。C 库、Rust crate、Go 包。模块边界在最终二进制文件中完全消失。
- 启动时加载的共享库 — 接近零。nginx 模块,Linux 内核模块。加载时一次性开销,之后是直接函数调用。
- 通过接口 / vtable 的动态分派 — 小开销。C++ 游戏引擎插件。每次调用一个指针间接寻址。
- 同进程解释型插件 — 中等开销。WordPress PHP 插件,Eclipse OSGi 包。
- 通过 IPC 的独立进程插件 — 显著开销。VS Code 扩展,Chrome 扩展。
- 通过序列化 IPC 的沙盒插件 — 重量级。Figma 插件,浏览器扩展内容脚本。
现实世界的损害
WordPress
每个插件都挂钩到请求生命周期。30 个插件意味着每次页面加载都有 30 层函数调用。结果:缓存插件的存在只是为了减轻其他插件造成的损害。性能插件来修复插件造成的性能问题。这种元讽刺不言自明。
VS Code
扩展在单独进程中共享一个 Node.js 事件循环。一个行为不当的扩展会阻塞所有其他扩展。Extension Host 经常是开发者机器上最高的 CPU 消耗者。微软已经构建了分析工具、二分命令和激活事件系统 —— 整套基础设施来管理扩展造成的问题。
Eclipse
警示故事。OSGi 包解析、类加载开销、庞大的依赖图。曾经最受欢迎的 IDE,现在已被主流开发者基本抛弃。本应是其最大优势的插件架构成了其决定性弱点。
Electron
平台级别的插件问题。每个 Electron 应用都携带完整的 Chromium + Node.js 运行时。VS Code 是 Electron。Slack 是 Electron。Discord 是 Electron。每个独立消耗 300-500 MB 内存来渲染本质上只是一个聊天窗口或文本编辑器。
为什么行业仍然选择插件
如果插件这么昂贵,为什么每个人都还在构建它们?原因主要是组织性的,而非技术性的。
替代方案:编译时组合
如果可扩展性发生在构建时而不是运行时呢?
这不是假设。在系统语言中有经过验证的先例:
这对 TypeScript 意味着什么
TypeScript 是构建可扩展工具最流行的语言 —— 也是运行时性能最差的。整个 TypeScript 生态系统运行在 Node.js 上,而 Node.js 运行在 V8 上,V8 JIT 编译 JavaScript。
这就是 Perry 的用武之地。Perry 将 TypeScript 直接编译为原生二进制文件。没有 V8,没有 JIT 预热,没有垃圾收集暂停,没有 IPC 边界。
# Your app, your dependencies, your "plugins" — one binary
$ perry compile server.ts -o server
Compiling server.ts + 43 modules...
✓ Built executable: server (1.8 MB, 0.7s)
$ ./server
Listening on port 3000
你真正需要的可扩展性
反对意见很明显:"但我需要运行时可扩展性。用户需要在不重新编译的情况下安装插件。"
真的吗?对于大多数应用程序,扩展集在构建时就已知。
前进的道路
行业对插件架构的依赖是接受运行时开销不可避免的症状。但事实并非如此。编译器可以完成这项工作。构建时组合提供了无需付出代价的可扩展性。
最快的插件系统是在运行时不存在的那个。