为什么「语言速度」是伪命题:I/O、并发、内存与内核

在现代环境中,单纯比较语言的“执行速度”远远不够。一方面,现代 CPU 执行指令已经极快,各语言在“单纯执行同一条指令”的层面差异很小(纳秒级),难以成为系统瓶颈。另一方面,就像在拥挤的城市街道上比较两辆赛车的极速,意义有限——真正决定系统表现的是 I/O 如何被处理、并发如何利用多核、内存如何与内核交互,以及运行时与生态的取舍。本文从技术内因(I/O、并发、内存与系统调用)、运行时成本(VM 与 AOT)以及非技术因素三方面梳理,并辅以 Linux 内核与用户态代码示例。

1. 为什么「语言速度」是伪命题?(技术内因)

1.1 I/O 是天花板

绝大多数时间 CPU 在等 I/O:网络往返或磁盘读写是毫秒级,而一条加法指令是纳秒级,语言层面的“谁更快”会被 I/O 等待完全淹没。真正的差异在于:语言/框架如何做 I/O——阻塞还是非阻塞?是否用好操作系统提供的异步接口(如 epollio_uring)?

epoll:一次系统调用可监听大量 fd,就绪时再处理,避免“每个连接问一次”的轮询。Linux 内核实现见 fs/eventpoll.c,入口为 epoll_create1epoll_ctlepoll_wait 等系统调用12

// 用户态:epoll 一次 wait 可返回多个就绪 fd,减少 syscall 次数
int epfd = epoll_create1(0);
struct epoll_event ev = { .events = EPOLLIN, .data.fd = sockfd };
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);

#define MAX_EVENTS 64
struct epoll_event events[MAX_EVENTS];
for (;;) {
    int n = epoll_wait(epfd, events, MAX_EVENTS, -1);  /* 一次 syscall,多 fd */
    for (int i = 0; i < n; i++)
        handle(events[i].data.fd);
}

io_uring:更现代的异步 I/O 接口,提交与完成通过共享 ring buffer 与内核交互,进一步减少系统调用与拷贝。内核实现见 io_uring/io_uring.c,如 SYSCALL_DEFINE2(io_uring_setup, ...)34

1.2 并发模型与多核利用

多核时代,并发模型决定能多“轻松”地压榨多核与掩盖 I/O 等待:

差别不在“单线程谁更快”,而在于能否用低成本抽象把并发写出来

1.3 内存管理与内核的博弈

语言如何从内核要内存、何时释放,对延迟和常驻内存影响很大:

// 释放堆上未用内存回内核,降低 RSS(glibc)
#include <malloc.h>
void release_unused_heap(void) {
    malloc_trim(0);   /* 将 free list 中的空闲页归还内核 */
}

内核侧:用户态堆扩展通过 brk/ mmap 与 VMA 管理,物理页按需分配(缺页时再给)。本博客在《栈为什么比堆快》中已有梳理7

1.4 用户态与内核态的壁垒

每次系统调用都是一次模式切换,成本远高于用户态几条指令。因此:

语言“跑得快”若伴随大量 syscall,实际表现可能反而不如“跑得慢一点但少进内核”的实现。


2. 运行时的“隐藏成本”:VM 与 AOT

因此“谁更快”还要看启动与常驻成本是否在你的场景里被放大。

语言有适用场景,某些场景下某类语言根本不可用。例如带 VM 的语言(Java、C# 等)无法用于内核开发:内核是跑在裸机上的第一层软件,没有“操作系统”为其提供进程、虚拟内存或系统调用;VM 依赖的运行时、GC、线程调度等都假设已有内核,内核自身不能依赖这些。因此内核必须用 C、Rust(no_std)等无传统 VM、可直接控制内存与硬件的语言。反之,内核、嵌入式、实时系统等会排除 VM 语言;企业后端、CRUD、大数据等则常首选 VM 语言以换取生态与开发效率。本博客《内核开发中的语言选择:C、C++ 与 Rust》对内核场景下各语言的约束有专门讨论8


3. 非技术因素的“一票否决权”

在工程选型中,非技术因素往往权重更高:


总结

选语言不是在选“谁跑得快”,而是在选谁的运行时哲学和生态,最匹配你的业务场景和团队能力

语言速度只是众多维度之一;I/O、并发、内存与内核的交互方式,以及 VM/AOT 与生态,往往更能决定实际表现与可维护性。 反过来看:一门语言在某个领域取得成功,一定是因为它解决了该领域的实际需求(性能、生态、开发效率、团队能力等),而不是技术品味或“谁更优雅”的问题。


扩展阅读(内核与接口)


References

  1. Linux 内核 fs/eventpoll.c:epoll 实现,SYSCALL_DEFINE1(epoll_create1,...)epoll_ctlepoll_wait 等。Bootlin - eventpoll.c  2

  2. epoll(7) - Linux 手册:epoll 概述与 API 

  3. Linux 内核 io_uring/io_uring.c:io_uring 实现,SYSCALL_DEFINE2(io_uring_setup,...) 等。Bootlin - io_uring.cio_uring 文档  2

  4. Efficient IO with io_uring - Jens Axboe,io_uring 设计说明(PDF) 

  5. Stop-The-World(STW):GC 暂停所有应用线程以独占堆访问,导致延迟尖刺。Oracle Java GC Tuning - Introduction 介绍各 GC 与停顿;A Guide to the Go Garbage Collector 说明 Go 的并发 GC 与 STW 阶段 

  6. malloc_trim(3) - 将 free 列表中的空闲页归还内核 

  7. 本博客 栈为什么比堆快:从分配方式到「批发-零售」链条 - brk/mmap、VMA、缺页与零页  2 3

  8. 本博客 内核开发中的语言选择:C、C++ 与 Rust 的运行时与标准库 - 内核为何不能用 VM、C++/Rust 的约束与取舍 

My Github Page: https://github.com/liweinan

Powered by Jekyll and Theme by solid

If you have any question want to ask or find bugs regarding with my blog posts, please report it here:
https://github.com/liweinan/liweinan.github.io/issues