性能
AOT 编译到 LLVM、零分配的热路径、arena 内存、单线程事件循环,让 SuperJ 进入与已发表最快框架同一档次 — 而且延迟曲线保持平坦,因为没有回收器会停顿。
HTTP
一个跑在内置 HTTP 协议栈上的 Web 服务器,编译成单一原生二进制,在回环节点上加压测试。服务器钉在一个核上,压力发生器钉在另一个核上。它的响应速度比 wrk 发请求的速度还快。
| 压力发生器 | 吞吐 | p50 | p99 |
|---|---|---|---|
| wrk(1 进程) | 406,762 req/s | 156µs | 167µs |
| SuperJ WebClient(流水线 ×16) | 515,000 req/s | — | — |
| wrk 上限(同一台机器) | ~423,000 req/s | 瓶颈是 wrk,不是服务器 | |
Ryzen 9 9950X3D · 服务器在 CPU 4,客户端在 CPU 3 · 回环 TCP · 100 条 keep-alive 连接
class HelloHandler implements HttpRequestHandler {
private final ByteArray response;
HelloHandler() {
String body = "Hello from SuperJ!\n";
this.response = new ByteArray(
"HTTP/1.1 200 OK\r\nContent-Length: "
+ body.length() + "\r\n\r\n" + body);
}
public void handle(HttpRequest r, SessionWriter w) {
this.response.rewind(); // 复用 — 零分配
w.write(this.response);
}
}
通过 LLVM 编译为原生代码。从第一个请求起就是全速 — 没有 profile,没有反优化。
arena 内存确定性地回收。这就是负载下延迟曲线保持平坦的原因。
响应缓冲构建一次,每次请求 rewind 复用。稳态服务零分配。
一个事件循环,一个线程 — 热路径上没有锁、没有上下文切换、没有跨核缓存流量。
superj_web
同一套协议栈,包成一个完整的边缘 Web 服务器,带 TLS 终止、路由、静态文件缓存和反向代理。尽管多了这些开销,它在同一台机器上、在每种文件大小下、在静态服务和反向代理上都击败 nginx 和 Caddy。
| 工作负载 | superj_web | nginx | Caddy | SDK 参照 |
|---|---|---|---|---|
| 小文件(59 字节) | 405,229 req/s | 218,948 req/s | 70,248 req/s (128B) | 407,094 req/s |
| 1 KB | 286,710 req/s | — | 59,974 req/s | — |
| 16 KB | 254,602 req/s | — | 59,479 req/s | — |
| 64 KB | 146,959 req/s | — | 56,634 req/s | — |
| 大文件(10MB / 1MB) | 1,510 req/s (14.75 GB/s) / 15,229 req/s | 1,066 req/s (10.41 GB/s) | 10,971 req/s (1MB) | — |
Ryzen 9 9950X3D · 服务器一核,wrk 另一核 · 回环 TCP · 3 轮平均。Caddy v2.8.4,superj_web 用 --release --enterprise 构建。nginx:1.24.0,单 worker,sendfile on,access_log off。
| 文件 | Caddy req/s | superj_web req/s | 加速 |
|---|---|---|---|
| 128B | 34,677 | 106,817 | 快 3.1× |
| 1 KB | 33,008 | 106,622 | 快 3.2× |
| 16 KB | 24,456 | 88,605 | 快 3.6× |
| 64 KB | 20,022 | 43,242 | 快 2.2× |
100 条 keep-alive 连接 · 前端在 CPU 14,后端在 CPU 15 · 每个 upstream 128 条池化 keep-alive 连接 · 原始响应转发(不重建)。
为什么它更快。 静态热路径零分配:预构建的响应、rewind() 复用、带惰性 mtime 失效的内存文件缓存。代理热路径再加上连接池(每个 upstream 128 条 keep-alive 连接)和带读取端 prepareForReuse() 的原始响应转发 — 两条路径都没有每请求分配。单线程事件循环在一个核上处理 288K req/s(静态 128B)和 107K req/s(代理 128B);与 nginx/Caddy 的差距是算法层面的,不是线程层面的。
SEDA
SEDA sequencer 是替代共享可变状态的全序事件流。在 M4 Max 进程内测得 88 ns/轮 — 比裸跨进程 mmap 自旋(152 ns/轮)还快,因为它从不在核间穿越,不付缓存一致性代价。
应用 — xpe_db
xpe_db 是一个嵌入式文档数据库,带 HNSW 向量索引、倒排索引、哈希索引,以及 JWT/认证子系统 — 从 Java 移植到 SuperJ。这个二进制不带 JVM、不带 GC、没有 JIT 预热地运行。这些是应用层面的数字,是用户真正能感受到的。
字节量化(uint8)L2 距离,M=16,maxM0=32,ef=200,k=10,dims=128,n=1000。相同种子的 RNG,相同工作负载。精确 20K 查询 nanoTime 基准。
| 指标 | SuperJ | C++ hnswlib | 结果 |
|---|---|---|---|
| 搜索吞吐 | 24.8K q/s | 23.7K q/s | SuperJ 快约 5% |
| 插入吞吐 | ~25K v/s | 25.6K v/s | 持平 |
| Recall@1 | 100% | 100% | 相同 |
| 基准 | Java | SuperJ | 加速 |
|---|---|---|---|
| InvertedIndexer.search(1024) TotalHits | 37 ns | 6 ns | 快 6.2× |
| InvertedIndexer.search(1) TotalHits | 35 ns | 3 ns | 快 11.7× |
| InvertedIndexer.build(10 万条记录) | 755 ns | 118 ns | 快 6.4× |
| HashIndexer.search(10 万次查找) | 22 ns | 17 ns | 快 1.3× |
SuperJ:enterprise SDK,-O3(边界检查开启,无 SIMD)。Java:JDK 17,冷 JIT。macOS arm64。
| 操作 | Java | SuperJ | 加速 |
|---|---|---|---|
| JWT.sign | 858 ns | 177 ns | 快 4.8× |
| JWT.verify | 801 ns | 181 ns | 快 4.4× |
| JWT.encryptAndSign | 1,238 ns | 364 ns | 快 3.4× |
| JWT.verifyAndDecrypt | 1,237 ns | 342 ns | 快 3.6× |
案例研究 — superK
superK 是一个用 SuperJ 从零构建的时序数据库 — 完全兼容 q 语言与 q-IPC 线协议:连接 kdb+ 的 q 客户端无需修改即可连接 superK,在 kdb+ 上运行的 q 脚本也能在 superK 上运行。相同查询语言、相同数据模型、相同客户端协议。没有 JVM、没有 JIT、没有垃圾回收器;设计上单线程,靠进程而非线程扩展。
基准说明。论文 "Benchmarking Specialized Databases for High-frequency Data"(arXiv:2301.12561)在加密货币交易所数据 — 成交与订单簿 — 上定义了 14 项查询基准,涵盖成交量聚合、VWAP、市场深度、买卖价差、NBBO、对数收益率与波动率。它旨在比较 kdb+ 与 ClickHouse、InfluxDB、TimescaleDB。kdb+ 胜出。我们用同样的 14 项基准、同样的数据形状、同样的查询 — 两台引擎在同一台机器、同一个核、同一份数据上,用同样的方式计时。paper_verify.q 在计时前逐值证明两个数据库一致;两引擎都通过各自 REPL 跑相同 q 文本,所以时钟覆盖用户命中的完整路径(解析、求值、格式化、打印),而非只是孤立内核。
| ID | 查询 | superK (µs) | kdb+ (µs) | 胜者 | 比例 |
|---|---|---|---|---|---|
| T-V1 | 成交量,按符号 | 7,495 | 14,303 | superK | 1.9× |
| T-V2 | 成交量,按符号 × 交易所 | 227,712 | 222,269 | kdb+ | 1.02× |
| T-VWAP | 成交量加权均价 | 2,089 | 1,794 | superK | 1.2× |
| O-T | 订单簿最优档 | 4.8 | 402 | superK | 84× |
| O-B1 | 最优买价 | 1,111 | 2,556 | superK | 2.3× |
| O-B2 | 最优买价 & 卖价 | 5,256 | 14,208 | superK | 2.7× |
| O-S | 价差 | 709 | 827 | superK | 1.2× |
| O-V1 | 订单簿成交量,按符号 | 40,294 | 61,012 | superK | 1.5× |
| O-V2 | 订单簿成交量,按符号 × 交易所 | 164,769 | 513,127 | superK | 3.1× |
| O-NBBO | 全国最优买卖价 | 420 | 859 | superK | 2.0× |
| C-R | 对数收益率 | 5,571 | 5,774 | superK | 1.04× |
| C-VT | 波动率,成交 | 2,868 | 3,048 | superK | 1.06× |
| C-VO1 | 波动率,订单簿(买) | 5,522 | 5,735 | superK | 1.04× |
| C-VO2 | 波动率,订单簿(买 & 卖) | 32,097 | 37,053 | superK | 1.2× |
AMD Ryzen 9 9950X3D · 30 GB 内存 · NVMe · 两引擎均 taskset -c 2(隔离核)、热缓存 · 10 轮迭代取均值 · 数据:30 个分区 + 1 个 NBBO 日,约 20M 成交行、约 33M 订单簿行,39 GB,3 符号 / 2 方向 / 5 交易所 · superK commit 2c84085,superj build --release(AVX2,无越界检查,-O3)· kdb+ 5.0
superK 14 项胜 13 项。唯一一负 — T-V2 的 1.02× — 在测量噪声之内;两引擎实质打平。胜幅从 1.04×(C-R)到 84×(O-T)。胜在存储与查询引擎本身,而非不同的数据形状:superK 实现了与 kdb+ 相同的 splayed、parted、p# 索引列式模型,通过 LLVM AOT 编译,热路径零分配。
账本
我们在同一台机器上、相同工作负载、相同迭代次数下跑了 54 项与 C 正面交锋的基准(以及相关时与 C++ simdjson/hnswlib、Rust、Java 的对比)。下面每条柱按速度比例切分:琥珀色是 SuperJ,蓝色是 C。50/50 是平局;如果 SuperJ 快 2×,它的段就长一倍。我们展示每一项基准,包括我们输的。
柱子是怎么算出来的。 每项基准在同一台机器上、相同工作负载、相同迭代次数下让 SuperJ 与 C(或标注处的 C++ simdjson / C++ hnswlib / Rust)对决。SuperJ 用 clang -O3 链接编译;C 用 clang -O3(C++ 基准用 g++ -O3 -mcpu=native)。3 次热运行取最优。每条柱的切分点是速度比例:如果 SuperJ 用的时间是 C 的 r×,SuperJ 的段就是 1/(1+r),C 的段是 r/(1+r) — 所以快 2× 时 SuperJ 的段是 2:1,平局是 50/50,输了 SuperJ 的段就缩短。快 5% 以上算胜,±5% 以内算平,慢 5% 以上算负。没有任何基准被挑选 — 这 54 套覆盖算术、数组扫描、字符串操作、对象分配/访问、方法分派、网络、整数解析、哈希表(Object 键、3 种键分布)、SHA-256、BitSet、JSON(4 种文档大小)、宽整数算术(i128/i256/i512)、secp256k1 ECDSA、8 个 Benchmarksgame 移植、Base64、Brainfuck、SIMD 矩阵乘法,以及一个完整的 HNSW 向量索引。完整结果表和方法论在源码树的 benchmarks/RESULTS.md 和 doc/hnsw_performance.md 中。
普通对象 put/get 与 C 持平;在对抗性键上比 tidwall C 快最多 7×;散布 int 键上快 4×。
i256/i512 一般除法比 C 快 3-10×;缓存分块的 SIMD-tile GEMM 在每种矩阵尺寸上比自动向量化的 C 快 1.15-2.57×。
索引器和 JWT 上快 3.4-11.7× — 热路径零分配、原生加密内联函数、无 GC。
在分配受限路径上慢 1.25-1.63× — SuperJ 分配真实的堆 String;C 写一个一次性的栈缓冲。charAt 持平。
慢 1.10× — stride-3/4 模式上每元素指针重载 + 边界检查。#2376 之后编码已持平。--no-bounds-check 能补上大部分剩余差距。
慢 1.13× — 64B 以下文档的逐字节标量尾部。SuperJ 在 7B 和 22B 文档上比 simdjson 快,5.7KB 上只差 9%。
BitSet 扫描、对象字段访问、reverse-complement — 每次 access 都有 C 不带的检查。--no-bounds-check 能补上大部分。
复现
HTTP 服务器和压力发生器随 SuperJ 发布在 demo/sj/demo/WebServer.sj 和 demo/sj/demo/WebClient.sj。边缘服务器的基准脚本在 superj_web 仓库中。
# HTTP 服务器 + wrk
superj compile "$SJ_HOME/demo/sj/demo/WebServer.sj" --sdk-path "$SJ_HOME/sdk" --link --output webserver
./webserver 8080 &
wrk -t2 -c100 -d30s --latency http://127.0.0.1:8080/
# 流水线客户端(当 wrk 是瓶颈时)
superj compile "$SJ_HOME/demo/sj/demo/WebClient.sj" --sdk-path "$SJ_HOME/sdk" --link --output webclient
./webclient 8080 64 16 15
# 跨核扩展 — 同一个二进制,多跑几份
for i in 1 2 3 4; do ./webserver 8080 & done