C++、Node.js、Python 写最小 HTTP 服务谁快?我实测了 6 万次请求

前端时间掘金首页挂了个热门话题:「实测:C++ vs Go vs Rust,谁的 Web 性能更强?」 评论区一如既往地打起来了------有人说 Go 天下第一,有人说 Rust 不安全,有人说 C++ 老古董。

吵归吵,我注意到一个现象:绝大多数「性能对比」文章,比的都是重型框架 + 花式压测工具,最后谁也说服不了谁。 很少有人回答一个最朴素的问题:

如果只写一个「能返回 hello 的最简 HTTP 服务」,三四种语言差多少?

这个问题的答案其实很有价值------它决定了你「从 0 搭一个内部小工具」时的下限,而不是框架调优后的上限。今天我就用 C++、Node.js、Python 三个最朴素的实现,在同一台机器上实测一把。

先说结论(免得你看到最后):

  • 吞吐量:C++ > Python > Node.js,但差距没有传说中「10 倍吊打」那么夸张;
  • 延迟稳定性:反而 Python 的 P99 最稳,C++ 的尾部延迟波动最大;
  • 真实差距 30% 左右,远小于语言社区嘴炮的差距。

下面看完整过程。


一、实验设计:怎么才算「公平」?

为了避免「你用 FastAPI 比人家裸 C++」这种不公平,我在三个实现里做了同样的约束:

  1. 都用各自最朴素的写法 :C++ 用 POSIX socket + 线程,Node 用原生 http 模块,Python 用标准库 socket + 线程,三个都不引入任何 Web 框架;
  2. 同一个响应 :统一返回固定文本,HTTP/1.1,Connection: close
  3. 同一台机器、同一压测脚本:每个服务跑 20000 个请求,并发 50 和 100 两档,各跑两轮取均值;
  4. 压测客户端统一:用自写的 Python 脚本多线程打请求(不用 ab/wrk,避免各语言工具差异)。

三个服务的核心代码(各 30 行上下):

C++(POSIX socket + 每连接一线程):

cpp 复制代码
static const char RESP_HEAD[] =
    "HTTP/1.1 200 OK\r\n"
    "Content-Type: text/plain\r\n"
    "Content-Length: 17\r\n"
    "Connection: close\r\n\r\n";
static const char BODY[] = "hello from cpp! \n";

void* worker(void* arg) {
    int fd = (int)(long)arg;
    char buf[2048];
    read(fd, buf, sizeof(buf));
    write(fd, RESP_HEAD, sizeof(RESP_HEAD) - 1);
    write(fd, BODY, sizeof(BODY) - 1);
    close(fd);
    return nullptr;
}

int main() {
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in addr{}; /* 绑定 127.0.0.1:8081 */
    bind(sfd, (sockaddr*)&addr, sizeof(addr));
    listen(sfd, 1024);
    for (;;) {
        int cfd = accept(sfd, nullptr, nullptr);
        pthread_t th;
        pthread_create(&th, nullptr, worker, (void*)(long)cfd);
        pthread_detach(th);
    }
}

Node.js(原生 http):

javascript 复制代码
const http = require("http");

const server = http.createServer((req, res) => {
    res.writeHead(200, { "Content-Type": "text/plain" });
    res.end("hello from node! \n");
});

server.listen(8082, "127.0.0.1");

Python(标准库 socket + 线程):

python 复制代码
def handle(conn):
    conn.recv(2048)
    conn.sendall(RESP + BODY)   # 固定 200 响应
    conn.close()

while True:
    conn, _ = s.accept()        # 127.0.0.1:8083
    threading.Thread(target=handle, args=(conn,), daemon=True).start()

压测脚本核心逻辑(并发线程打满 N 个请求,统计分位延迟):

python 复制代码
def once(url):
    t0 = time.perf_counter()
    with request.urlopen(url, timeout=10) as r:
        r.read()
    return (time.perf_counter() - t0) * 1000   # 毫秒

with ThreadPoolExecutor(max_workers=concurrency) as ex:
    lat = list(ex.map(lambda _: once(URL), range(TOTAL)))

qps = TOTAL / (time.perf_counter() - t0)
p50 = sorted(lat)[int(TOTAL * 0.5)]
p99 = sorted(lat)[int(TOTAL * 0.99)]

二、实测数据:差距比你想的小

先上完整数据表(两轮取均值,总请求数 20000/轮):

实现 并发 QPS 平均延迟 P50 P95 P99 错误数
C++ (g++ -O2) 50 3245 14.6ms 12.0ms 39.3ms 57.9ms 0
C++ (g++ -O2) 100 3303 28.6ms 20.5ms 90.2ms 132.3ms 0
Node.js 18 50 2619 18.6ms 17.0ms 39.6ms 62.6ms 0
Node.js 18 100 2628 35.7ms 31.1ms 81.7ms 116.5ms 0
Python 3.12 50 2836 16.9ms 15.3ms 31.5ms 46.4ms 0
Python 3.12 100 2923 32.1ms 30.5ms 60.7ms 90.3ms 0

先看吞吐:

再看延迟分位:

几个有意思的观察:

  1. C++ 吞吐确实第一,但只领先 15%~25%,不是传说中数量级的差距。原因很简单:这个场景下瓶颈在「连接建立 + 线程调度 + 客户端侧」,不在语言本身跑得快不快。C++ 快的部分(循环、内存拷贝)在这个 30 行服务里几乎没有戏份。
  2. Python 竟然比 Node 快 。为什么?因为 Python 这个实现是「每连接一个线程」,请求处理全部阻塞在 IO 上,GIL 几乎没有竞争;而 Node 单线程事件循环在这个纯 short-request 场景里,优势发挥不出来,反而是 Connection: close 的握手开销摊薄了它。
  3. 最扎眼的:C++ 的 P99 反而最差(并发 50 时 57.9ms,并发 100 时冲到 132ms) 。原因是每连接一个线程------高并发下 pthread_create 的创建开销、调度抖动,全部体现在尾部延迟上。这就是典型的「平均快、尾巴长」。

三、为什么你的场景里,测试结果跟你体感不一样?

实测归实测,我得泼一盆冷水:这个结论不能外推到真实业务。 真实场景跟这个实验有四个关键差异:

  1. 响应体大小 。我们的响应是 17 字节。一旦响应变成几十 KB、几百 KB,C++ 的 write 效率优势会被放大,Python 反而可能因为每字节开销翻车。
  2. 连接复用Connection: close 让握手成本占了大头。真实业务用 keep-alive + 连接池,服务端压力会集中在「处理逻辑」而不是「建连」,差距还会重新洗牌。
  3. 框架 vs 裸实现。真实业务没人敢用裸 socket------你用的是什么框架,往往比语言本身更决定性能(比如 Python 的 FastAPI/Uvicorn 与裸 socket 的差距,大于 Python 与 C++ 的差距)。
  4. 压测客户端瓶颈 。我的压测客户端是 Python 多线程,它本身可能就是吞吐上限的瓶颈。所以绝对数字不要信,相对排序可以看------因为三个服务用同一个客户端,误差是均匀的。

一句话总结这一段:这个实验测的是「最小可用实现的下限」,不是「这个语言的上限」。


四、那到底该选谁?我的建议

不动脑地抄答案没意思,我按场景给三条建议:

  1. 内部小工具、脚本服务、给同事用的 CRUD :Python。不是因为快,是因为开发速度最快、改起来最爽、出问题好查。性能不够了再换,不要一开始就用 C++ 写内部工具------你修 bug 的时间比省下的 0.3ms 宝贵得多。
  2. 对延迟敏感、要对接高并发网关的核心服务 :C++ 或 Rust 都行,但我建议用 Rust 别用 C++ (如果你控得住 borrow checker)。实测里 C++ 的 P99 已经给你示范了:手写线程模型很容易在尾部延迟上翻车,Rust 的 tokio 生态至少让你别亲手埋这个雷。
  3. 省事、生态好、前后端同构 :Node.js。它的性能在这个量级完全够用,选它永远不是因为性能,而是因为生态和心智负担

最后的最后,留一句话给准备在评论区开战的各位:

你选的不是「最快的语言」,你选的是「最适合你项目阶段的语言」。 一个 17 字节响应的 hello world 测不出你系统的未来------它只能测出,谁在杠精博主排行榜上最闲。


实验环境:Linux 沙箱(x86_64),g++ 12 / Node 18.19.1 / Python 3.12.3,均未开任何调优;压测客户端为 Python 3.12 多线程脚本,20000 请求/轮 × 2 轮均值。完整代码(服务端 + 压测脚本)见文章内代码块。

相关推荐
console.log('npc')3 小时前
03 — 核心框架:App 与中间件
后端·node.js·express
云浪3 小时前
从 0 手写一个 MCP Server:让 Copilot 调用 Tool 完成四则运算
javascript·node.js·mcp
FungLeo16 小时前
成为全栈·Node 后端篇·点赞系统:幂等点赞与计数原子增减
node.js·成为全栈·点赞系统·幂等点赞·计数原子增减
故作春风18 小时前
elpis-core 核心从入门到理解
后端·架构·node.js
万敏18 小时前
Vue3 全栈实战第九周:Node.js + Express 后端从零搭建实战记录
vue.js·node.js·全栈
爱吃红星柚1 天前
【学习】Elpis 抽离与发布 npm 包
前端·前端框架·node.js
梦帮科技1 天前
Next.js 16 模块化单体实战:App Router、领域模块与 Route Handler 分层
css·数据结构·链表·正则表达式·node.js·json·html5
梦帮科技1 天前
从 Guest 到 Platinum:Prompt 配额、API Key 与访问控制的安全闭环
javascript·git·架构·node.js·reactjs·html5·visual studio
秋秋小事1 天前
node prisma+postgreSQL中的查询与过滤
node.js