性能数据
同样的编译方式下对比 ILP32 和 LP64:节省的是内存,代价是几个百分点的速度。
OpenWrt One 实机
OpenWrt One(MT7981B,双核 Cortex-A53,最高 1.3 GHz,1 GB)。两个镜像来自同一份源代码, 软件包(LuCI、python3-light、dnsmasq)与内核配置都相同,也都用 GCC 14.3 编译,只有用户空间的 ABI 不同 (两个内核由同一份源代码、分别构建的工具链编译)。负载:200 条防火墙规则、50 条静态 DHCP、 15 万个域名的 dnsmasq 拦截列表、LuCI 请求,以及一个持有 10 万条 dict 的 Python 进程。每种各启动 3 次,取中位数。
| 内存 | LP64 | ILP32 | 差异 |
|---|---|---|---|
| 用户空间内存(所有进程 PSS 合计) | 38.3 MB | 28.9 MB | -24.6% |
| 匿名内存 | 30.3 MB | 20.9 MB | -31% |
| 可用内存(MemAvailable) | 863.6 MB | 872.2 MB | +8.6 MB |
| python3(10 万条 dict) | 17.9 MB | 12.4 MB | -31% |
| dnsmasq(15 万条拦截列表) | 12.0 MB | 8.8 MB | -26% |
| hostapd / wpa_supplicant | -11% / -9% | ||
| 内核(Slab) | 28.0 MB | 27.9 MB | -0.6% |
| 耗时(秒,越低越好;用 GCC 编译) | LP64 | ILP32 | 差异 |
|---|---|---|---|
| 重新加载 200 条防火墙规则 | 2.58 | 2.84 | +10% |
| dnsmasq 加载 15 万条 | 2.96 | 3.03 | +2% |
| LuCI 登录页 20 次请求 | 1.73 | 1.81 | +5% |
| python3 构建 10 万条 dict | 0.38 | 0.42 | +11% |
两个镜像的内核都是 LP64,所以内核内存不变;节省的是程序本身的内存,数据越多省得越多。
空闲的路由器只省几百 KB。多出的耗时主要来自在 64 位代码中处理 32 位指针,在为 64 位 long
编写了快速路径的代码中会更慢。把最常用的软件包(ucode、lucihttp、json-c)改用 clang 编译后,
防火墙重新加载为 2.63 秒(+2%),LuCI 请求为 1.76 秒(+2%),内存不变(同一台 OpenWrt One,各启动 3 次取中位数)。只有 ILP32 镜像的这些软件包改用 clang;LP64 镜像只用 GCC 编译,
LP64 用 clang 编译的版本尚未测量,所以部分提升可能来自 clang 本身。
同样的测试在 QEMU(armsr,256 MB)上的结果:用户空间内存 -26.0%,可用内存 +9.6 MB。
Cortex-A35 与 Cortex-A53 板子上的 CPython
LP64 和 ILP32 使用相同的 CPython 编译方式:ILP32 内存占用减少约 30%, 在 Cortex-A53 上慢 2.6%,在 Cortex-A35 上慢 5.7%。 编译方式、参数和各项数据:llvm-ilp32 docs/benchmarks.md。
我们如何对比
- 同一份源代码、相同的编译器和参数、相同的软件包,只有 ABI 不同。
- 多次运行,取中位数。QEMU 的脚本在 test-ilp32 的
openwrt/; 实机测试的脚本和原始数据之后会补上。 - 速度和内存都公开,包括 ILP32 更慢的地方。