效能數據
同樣的編法下比較 ILP32 和 LP64:省下的是記憶體,代價是幾個百分點的速度。
OpenWrt One 實機
OpenWrt One(MT7981B,雙核 Cortex-A53,最高 1.3 GHz,1 GB)。兩個 image 來自同一份原始碼, 套件(LuCI、python3-light、dnsmasq)和 kernel 設定都相同,也都用 GCC 14.3 編譯,只差使用者空間的 ABI (兩個 kernel 是用同一份原始碼、分別建立的 toolchain 編出來的)。負載: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% | ||
| kernel(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% |
兩個 image 的 kernel 都是 LP64,所以 kernel 記憶體不變;省下的是程式本身的記憶體,資料越多省越多。
閒置的路由器只省幾百 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 比較慢的地方。