四语言 Hello World 背后:编译模型与运行时对比

栏目:02 · 后端开发 | 2 月 · 后端语言入门系列(对比专题开篇)

上周为了给这个多语言对比系列攒素材,我把四个内部小工具的部署镜像重新打了一遍------C#、Go、Rust、Python 各一个,功能都只是读 CSV 出报表。结果最重的 Python 镜像 130MB 出头,最轻的 Go 镜像塞进 scratch 里不到 3MB,差了四十多倍。组里新来的同学问我:不都是"编译"吗,差这么多?

这个问题我两年前也答不好,当时我的理解是"编译型快、解释型慢",仅此而已。这次借着写对比专题开篇的机会,把四个 Hello World 从源码到二进制完整走了一遍,才发现这套二分法在 2026 年已经基本失效了。

先看四份 Hello World 和它们的产物

代码本身没有信息量,信息量在编译命令和产物上。

rust 复制代码
// hello.rs --- rustc 1.90.0(edition 2024)
// 编译:rustc -O hello.rs -o hello
fn main() {
    println!("hello, world");
}
go 复制代码
// hello.go --- Go 1.24(linux/amd64)
// 编译:go build -o hello_go hello.go
package main

import "fmt"

func main() {
    fmt.Println("hello, world")
}
csharp 复制代码
// Program.cs --- .NET SDK 8.0.4xx,Top-level statement 单文件
Console.WriteLine("hello, world");
python 复制代码
# hello.py --- CPython 3.13.1
print("hello, world")

在 Ubuntu 22.04(x64)上,四个产物体积如下:

语言 产物 体积 依赖
Rust rustc -O 出的 ELF 365KB(strip 后 242KB) glibc 动态链接
Go go build 出的 ELF 1.9MB 无(静态链接)
C#(JIT) framework-dependent 发布 6KB 左右的 App.dll 需要 .NET 8 Runtime
C#(NativeAOT) PublishAot=true 出的 ELF 1.1MB 左右 glibc 动态链接
Python .py 源码本身 18 字节 需要 CPython 解释器

启动速度用 hyperfine(v1.18,--warmup 3)测了一轮:

bash 复制代码
Rust hello          0.6 ms ± 0.1 ms
Go hello            1.2 ms ± 0.2 ms
C# NativeAOT hello  2.1 ms ± 0.3 ms
C# JIT hello       58 ms ± 4 ms
CPython 3.13 hello 24 ms ± 1 ms

数字先摆这里:Rust 和 Go 都是毫秒级,但 Go 比 Rust 大五倍;C# 同一份代码,JIT 启动 58ms,NativeAOT 只要 2.1ms,差 27 倍;Python 的 24ms 里一大半是解释器自身初始化,真正执行那行 print 的时间可以忽略。

我原来想错了什么

我以前把四门语言排成一条线:Rust/Go 在"编译"那头,Python 在"解释"那头,C# 居中。写这篇的时候越写越别扭,这条线对不上事实。

Python 3.13 自带实验性 JIT(默认关闭);.NET 8 一个 PublishAot=true 就能把 IL 提前编成原生机器码;Go 本身是 AOT 编译,却静态打包了一整套带 GC 和调度器的运行时。四门语言没有一个"纯解释",也没有一个能归进同一个框。

后来我换了一个维度看,就通了:不看"有没有编译",看"运行时什么时候介入"。

  • Rust:运行时介入最少,边界检查和所有权的清理代码在编译期生成,标准库极薄。
  • Go:运行时在编译期全部打包进去。GC、调度器、panic 栈回溯,哪怕你一行并发代码没写,1.9MB 里大部分是它们------这是 Go 二进制比 Rust 大的原因,也是它部署不用装任何东西的原因。
  • C#:介入方式可选。默认 JIT,启动时才把 IL 翻译成机器码,换来运行期深度优化;NativeAOT 把翻译提前到编译期,代价是砍掉大部分反射,启动从 58ms 掉到 2.1ms。
  • Python:介入最深。字节码由 PVM 逐条解释执行,对象模型、引用计数 GC 全在运行期,它的"编译"只到 .pyc 为止。

说白了,差异不是"编译与不编译",而是"运行时替你兜多少底"。兜得越多,写起来越省心,产物越重、启动越慢。Rust 把底兜到了编译期,Go 把底装进了二进制,C# 让你选,Python 把底全留到运行期。

踩坑实录:Go 镜像在 scratch 里起不来

上面说 Go 静态链接,其实有个前提,而这个前提坑了我一晚上。

打 Go 镜像我习惯用两段式:golang:1.24 构建,产物丢进 scratch。第一个工具一切正常,第二个工具多了个功能------把统计结果 POST 给监控接口,我顺手 import "net/http",构建照样通过,容器一起就炸:

bash 复制代码
standard_init_linux.go:228: exec user process caused: no such file or directory

这个报错特别误导人,我第一反应是二进制架构不对(amd64/arm64 搞混),对着 uname -m 查了十分钟,方向完全错了。真实原因:默认开 CGO 的 Linux 环境里,net 包走 cgo 的 DNS 解析实现,链接器随之把二进制编成动态链接 ------它需要 glibc 的动态加载器,而 scratch 里什么都没有。ldd 一看便知:

bash 复制代码
$ ldd hello_go
        linux-vdso.so.1
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
        /lib64/ld-linux-x86-64.so.2

修复方式是构建时把 CGO 关掉,再 ldd 就变成 not a dynamic executable,丢进 scratch 直接跑:

bash 复制代码
CGO_ENABLED=0 GOOS=linux go build -o hello_go hello.go

后来我把这个变量写进了所有 Go 项目的 Dockerfile。别学我,这个坑应该在第一次写 Go Dockerfile 时就踩掉。

Rust 有个对称的坑:默认产物也是 glibc 动态链接的,直接丢 scratch 一样起不来,要么换 x86_64-unknown-linux-musl 目标做全静态,要么底镜像用 debian:bookworm-slim。

代价清单:每个模型都要付钱

没有免费的模型,只有不同的账单,按部署视角整理:

模型 付什么 换来什么
Rust 编译期与编译器搏斗,真实项目编译分钟级 产物小、启动快、无运行时依赖
Go 二进制恒定 2MB 起步 单文件部署、并发开箱即用
C# JIT 启动 58ms,要装 Runtime(官方镜像约 90MB) 运行期持续优化、R2R 可调
C# NativeAOT 反射受限、部分 NuGet 库不兼容 启动 2.1ms、单文件 1.1MB
Python 要解释器、镜像 130MB 级、CPU 密集慢 改完即跑,迭代最快

我的偏好很明确:内部 CLI 和要塞进 scratch/alpine 的服务选 Go 或 NativeAOT 的 C#;频繁改逻辑的运营脚本用 Python;对启动延迟和内存有硬指标的热路径服务上 Rust。这不是技术高低,是账单往哪开的问题。

补一句测量教训:我一开始用 time 测启动,Python 测出 40ms,hyperfine 测出 24ms------time 把 shell fork 的开销也算进去了。

留个尾巴

写到结尾我发现自己留下一个更值得追的问题:既然 NativeAOT 能把启动压到 2ms 级,"JIT 阵营"存在的理由还剩多少?运行期优化(分支预测友好、去虚化)是 JIT 独有的牌,但 ASP.NET Core 官方都在往 AOT 迁移 Minimal API 了,我打算 6 月写框架实战时用真实服务再验证一次。下一篇(选题 14)聊四门语言的类型系统设计哲学。代码都推在 GitHub 上,仓库地址在系列导航页。

(如果你有更好的镜像瘦身方案,欢迎评论区打我。)

相关推荐
Pin_Qing1 小时前
账单没超,请求先被拒了
大数据·开发语言·人工智能
2601_962885721 小时前
如何用 Python 计算 VaR 和 CVaR(在险价值)?
开发语言·python
郝亚军2 小时前
manifest 中 `sha256` 与 `signValue` 的作用
java·开发语言·数据结构
譕痕2 小时前
stripe 实例
java·开发语言·stripe·checksession
墨子夕2 小时前
设计模式——Java × Python 双语言精讲 · 23 个经典模式 + 现代演进(前言篇)
java·开发语言·设计模式
_菜鸟果果2 小时前
高德地图 JS API 2.0 在国产 Linux/ARM 电脑上渲染失败 的排查与解决
linux·开发语言·javascript
昂好的day3 小时前
VisionMaster(VM) 标定工具详解(上):为什么必须标定 + 九点标定手眼标定
开发语言·数码相机·c#
严谨的麻辣烫3 小时前
静态代理 IP 的出口一致性如何验证?Python 请求检测与异常排查
开发语言·python·http·网络安全
原子延迟3 小时前
视频太大怎么压缩发微信?先查限制再压的3种方法(2026)
开发语言·图像处理·人工智能·计算机视觉·音视频