返回资讯中心
外部精选
软件工程
#开发者实时流

实测对比:Blazeio 在吞吐量与延迟上全面胜过裸 Node.js

来源标题:We Tried to Make Node.js Beat Python Blazeio. It Didn't Work

同一台机器、相同负载的基准测试显示,Python 框架 Blazeio 的吞吐量是裸 node:http 的 2.5–3 倍,尾部延迟低 4–40 倍,且在 1 万并发下 Python 无连接错误而 Node.js 出现掉线。测试方称此前常见的 Node.js 快于 Python 说法多是比较 Express 与 FastAPI 等框架而非运行时本身。

Hacker News 最新anonyxbiz2026/11/7 · 7 天前24 分钟阅读

以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

Python Nodejs 异步编程 零拷贝 坚如磐石 阅读时长 6 分钟· 刚刚

我们试图让 Node.js 击败 Python Blazeio。结果失败了。

同一台机器、同一工作负载下 Blazeio(Python)与裸 node:http 的基准测试。Node 在吞吐量上落后 2.5–3 倍,在尾延迟上落后 4–40 倍,并且在 1 万并发时出现连接丢失,而 Python 一个错误都没有。

搜索“Node.js vs Python 性能”,你会找到数百篇结构雷同的文章:Express vs FastAPI、Next.js vs Django,配一张显示 Node 快 3–10 倍的图表,评论区满是“Python 太慢,用 Node 吧”。

这个说法是对的,但毫无意义。这是两个框架之间的比较,不是两个运行时之间的比较。Express 只是 Node 的 C++ HTTP 解析器之上的一层薄壳。FastAPI 则是 Pydantic + Starlette + 一路到底的 ASGI 栈。Node 赢是理所当然的。

真正有意思的问题是没人问的那个:当你把两边都剥到裸 socket,让运行时正面硬刚,会发生什么?

于是我们做了。同一台机器。同一工作负载。同一个客户端。一个专门为填补这一差距而构建的 Python 框架 Blazeio——对阵裸 Node.js http 模块,并且把我们能找到的每一个性能开关都调到了极致。没有 Express,没有 FastAPI,没有中间件,没有借口。

我们本以为 Node 会赢。我在运行基准测试之前就白纸黑字写下了这句话:“我预计 Node 在 / 上会赢 20–40%,在 /my/ip 上则是五五开。”

我们错了。不是错了一点点。是错了 2.5–3 倍。

硬件:Google Colab 免费层(共享 vCPU、吵闹的邻居、无隔离)。这不是一个干净的基准测试环境,而这正是重点的一部分,因为两个服务器运行在完全相同的噪声之下。

· GET / → 返回预生成的 1024 字节负载 · GET /my/ip → 返回客户端 IP 地址(一个自托管的 ipify)

客户端:wrk -t1 -d10s --timeout 60s,分别在 -c100、-c1000 和 -c10000 并发连接下,对每个服务器上的每个端点进行测试。同一台机器、同一进程,背靠背运行。

pip3 install uvloop git+https://github.com/anonyxbiz/Blazeio.git
apt install wrk
nohup python3 -m Blazeio.Bench > /dev/null 2>&1 &

Blazeio 是一个单线程 asyncio 框架,使用 uvloop、零拷贝缓冲区、支持 SO_REUSEPORT,而关键在于——预构建的响应缓冲区。它不按请求分配内存。它不按请求生成头部。它把一个缓冲区写入 socket,然后返回事件循环。

Node 侧——裸 node:http,全面调优

const http = require('http');
const PAYLOAD = Buffer.alloc(1024, '.');
const server = http.createServer((req, res) => {
 const path = req.url.split('?')[0];
if (path === '/') {
 res.writeHead(200, {
 'Content-Type': 'text/plain',
 'Content-Length': PAYLOAD.length,
 'Server': 'NodeJS',
 'Connection': 'keepalive'
 });
 res.end(PAYLOAD);
 } else if (path === '/my/ip') {
 let ip = req.socket.remoteAddress || '';
 if (ip.startsWith('::ffff:')) ip = ip.slice(7);
 const body = Buffer.from(ip, 'utf8');
 res.writeHead(200, {
 'Content-Type': 'text/plain',
 'Content-Length': body.length,
 'Server': 'NodeJS',
 'Connection': 'keepalive'
 });
 res.end(body);
 } else {
 res.writeHead(404, { 'Content-Length': 0 });
 res.end();
});
server.keepAliveTimeout = 65000;
server.headersTimeout = 66000;
server.maxRequestsPerSocket = 0;
server.listen(8006, '0.0.0.0');

没有 Express。没有依赖。预分配负载。keepalive 调优。每 socket 无限请求。这几乎是不彻底抛弃 http 模块所能达到的最快速度。

并发数 Blazeio RPS Node RPS Blazeio / Node 100 33,294 12,883 2.58x 1,000 34,800 13,199 2.64x 10,000 27,061 9,381(256 次读取错误) 2.89x 并发数 Blazeio RPS Node RPS Blazeio / Node 100 31,604 11,814 2.67x 1,000 30,435 10,576 2.88x 10,000 36,029 11,305(254 次读取错误) 3.19x

Node 不只是输了。在 1 万并发下,它每次测试大约丢失 250 个连接——wrk 输出中的 Socket errors: read 256。Blazeio 一个都没丢。

看看 Blazeio 的平均延迟随并发上升的变化:

并发数 Blazeio / 延迟 Node / 延迟 100 11.27 ms 13.67 ms 1,000 4.83 ms 388.50 ms 10,000 76 µs 254.07 ms

Blazeio 的延迟随着并发上升反而下降——从 11ms 到 4.8ms 再到 76 微秒。Node 的延迟则一路上升,从 13ms 到 388ms,再也没有恢复。

这种反转就是整个故事的缩影。它是两种完全不同的、在负载下表现迥异的事件循环架构的标志。

我曾预测 Node 会赢。以下是我错在哪里,以及为什么。

  1. Node 按请求分配头部。Blazeio 不。

res.writeHead({...}) 会分配一个 JavaScript 对象、遍历它、序列化头部字符串,然后交给 llhttp——每一个请求都如此。在一个 keepalive 连接上,正文是 1024 字节,但 90% 的工作量在头部。

Blazeio 则写出一个预构建的字节缓冲区,其中包含状态行、头部和正文,作为一次 send() 调用。每个请求不分配、不格式化、不序列化任何东西。响应是一个常量。

这是 2.5 倍差距的最主要原因,而且它不是 Python vs JS 的问题。它是架构问题。Node 的 http 模块本可以提供一个“预构建响应”模式。它没有。Blazeio 把这种模式作为默认行为。

  1. Node 发送 Date 头。Blazeio 不。

看看 curl 的输出。Node 的响应包含 Date: Sat, 07 Nov 2026 15:12:19 GMT。Blazeio 的没有。Node 每个连接每秒都会重新生成并缓存一个格式化日期字符串。听起来不费事。其实不然——它在热路径上,是一种随并发数扩展的每连接开销。

  1. Node 每个响应都要跨越微任务边界。Blazeio 不。

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →