我們今天會沿用 Day 25 的設定, 來看看改用 Ninja 能夠節省多少開發的時間!
Ninja 是一個 build tool, 但是主打 快速
這邊需要聲明一下, 這裡指的 "快速" 是指 從執行 build tool 指令開始到真的開始 build 的時間
由於 build tool 在開始 build targets 之前, 還需要做幾件事情
所以上面所指的 快速 是從 1 到 5 所需要的時間
道理我都懂, 但是, 為什麼 Ninja 說自己很快呢?
Make
根據 作者所說 (2011), 在他們將 Chrome 從 Windows porting 到 Linux 時, 用 Scons 需要 40s 才會真的開始 build code, 當時的 scale 是 1 個 executable, 30,000 個 source files (想嘗試的話可以用 Day 25 提供的 scripts 模擬~)
然後改動一個檔案後, 在 Windows re-built 就花了 8 分鐘😱
所以作者在分析後決定朝兩個方向優化
make
需要的時間作者在 strace
後決定把 make
的一些功能拔掉, 加上用比較快的 linker 和 compiler, 最終成功從 8 分鐘壓到剩下 6 秒!
所以可以將 Ninja 看做是輕量版的 Make
除此之外, Ninja 會將 dependency files (deps
) 壓縮後存在自己的 db (builddir/.ninja_deps
) 中, 加快讀取速度
我們直接將 generator 改成 Ninja, 並看看效能有多少提升
cmake -S . -B build -G "Ninja"
可以看到確實比 Make 快了不少, 我們來用 strace
比較一下兩者用了哪些 system call
我們一樣繼續用 Day 25 的設定, 來看看 build debug 版本和 release 版本的效能差異
real | user | system | |
---|---|---|---|
make | 0m10.708s | 0m6.479s | 0m3.810s |
ninja | 0m8.173s | 0m7.831s | 0m3.901s |
加速了 23.6%, 感覺是在誤差範圍內, 怎麼和想象中的效果不一樣呢?
我們再 build 一次試試
real | user | system | |
---|---|---|---|
make | 0m0.203s | 0m0.114s | 0m0.087s |
ninja | 0m0.075s | 0m0.053s | 0m0.017s |
可以發現, 第二次 build 的結果中, Ninja 快了 63%❗❗❗
這是相當顯著的提升了
我們來 strace
一下
可以看到第一次因為要 parsing build scripts, 建立 dependency graph 等等, wait4
花費的時間相差不大
Make
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
99.97 4.154351 1038587 4 wait4
0.01 0.000268 5 47 29 newfstatat
0.01 0.000216 54 4 clone3
0.00 0.000140 5 25 20 openat
0.00 0.000080 6 13 mmap
0.00 0.000065 13 5 munmap
0.00 0.000057 6 9 rt_sigaction
0.00 0.000052 26 2 readlink
0.00 0.000047 2 21 rt_sigprocmask
0.00 0.000036 9 4 read
0.00 0.000035 11 3 mprotect
0.00 0.000034 6 5 1 access
0.00 0.000029 4 6 close
0.00 0.000023 11 2 getdents64
0.00 0.000018 3 6 ioctl
0.00 0.000014 2 7 fcntl
0.00 0.000012 3 4 brk
0.00 0.000009 2 4 pread64
0.00 0.000008 2 4 geteuid
0.00 0.000007 1 4 getuid
0.00 0.000007 1 4 getegid
0.00 0.000006 1 4 getgid
0.00 0.000005 5 1 chdir
0.00 0.000004 4 1 getcwd
0.00 0.000004 4 1 getrandom
0.00 0.000003 1 2 1 arch_prctl
0.00 0.000002 2 1 set_tid_address
0.00 0.000002 2 1 set_robust_list
0.00 0.000002 2 1 prlimit64
0.00 0.000002 2 1 rseq
0.00 0.000000 0 1 execve
------ ----------- ----------- --------- --------- ----------------
100.00 4.155538 21094 197 51 total
Ninja
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ------------------
96.81 3.817308 12195 313 wait4
0.46 0.018043 10 1665 write
0.35 0.013789 44 313 clone3
0.34 0.013244 43 307 unlink
0.31 0.012116 5 2192 prlimit64
0.31 0.012109 8 1391 321 stat
0.20 0.007690 8 938 read
0.18 0.007202 7 940 close
0.16 0.006128 19 311 ppoll
0.14 0.005615 16 338 22 openat
0.11 0.004463 14 315 munmap
0.10 0.004069 6 628 ioctl
0.10 0.003855 12 313 pipe2
0.10 0.003780 6 630 fcntl
0.09 0.003626 5 628 rt_sigprocmask
0.08 0.002960 8 336 mmap
0.07 0.002627 7 339 20 newfstatat
0.07 0.002576 8 309 fstat
0.04 0.001726 5 311 rt_sigpending
0.00 0.000035 3 9 brk
0.00 0.000012 3 4 lseek
0.00 0.000011 1 6 rt_sigaction
0.00 0.000000 0 7 mprotect
0.00 0.000000 0 4 pread64
0.00 0.000000 0 1 1 access
0.00 0.000000 0 1 getpid
0.00 0.000000 0 1 execve
0.00 0.000000 0 2 1 arch_prctl
0.00 0.000000 0 1 sched_getaffinity
0.00 0.000000 0 1 set_tid_address
0.00 0.000000 0 1 set_robust_list
0.00 0.000000 0 1 getrandom
0.00 0.000000 0 1 rseq
------ ----------- ----------- --------- --------- ------------------
100.00 3.942984 314 12557 365 total
我們改動 main.cpp
的實作, 多呼叫一個 foo49_1()
的 function 後再 build 一次看看
這次差距就相當明顯了, Ninja wait4
所花費的時間是 0.033s
而 Make 則是 0.082s, 提升了 60% 的速度❗
可見 Ninja 作為輕量化的 Make 確實能處理的更快速
Make
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00 0.082306 20576 4 wait4
0.00 0.000000 0 4 read
0.00 0.000000 0 6 close
0.00 0.000000 0 13 mmap
0.00 0.000000 0 3 mprotect
0.00 0.000000 0 5 munmap
0.00 0.000000 0 4 brk
0.00 0.000000 0 9 rt_sigaction
0.00 0.000000 0 21 rt_sigprocmask
0.00 0.000000 0 6 ioctl
0.00 0.000000 0 4 pread64
0.00 0.000000 0 5 1 access
0.00 0.000000 0 1 execve
0.00 0.000000 0 7 fcntl
0.00 0.000000 0 1 getcwd
0.00 0.000000 0 1 chdir
0.00 0.000000 0 2 readlink
0.00 0.000000 0 4 getuid
0.00 0.000000 0 4 getgid
0.00 0.000000 0 4 geteuid
0.00 0.000000 0 4 getegid
0.00 0.000000 0 2 1 arch_prctl
0.00 0.000000 0 2 getdents64
0.00 0.000000 0 1 set_tid_address
0.00 0.000000 0 25 20 openat
0.00 0.000000 0 47 29 newfstatat
0.00 0.000000 0 1 set_robust_list
0.00 0.000000 0 1 prlimit64
0.00 0.000000 0 1 getrandom
0.00 0.000000 0 1 rseq
0.00 0.000000 0 4 clone3
------ ----------- ----------- --------- --------- ----------------
100.00 0.082306 417 197 51 total
Ninja
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ------------------
87.61 0.032997 16498 2 wait4
6.27 0.002360 4 515 8 stat
2.27 0.000856 1 728 lseek
0.60 0.000227 7 32 20 openat
0.59 0.000221 7 28 read
0.50 0.000190 7 25 mmap
0.31 0.000118 59 2 clone3
0.30 0.000112 3 35 20 newfstatat
0.23 0.000086 10 8 write
0.23 0.000085 6 14 close
0.14 0.000051 7 7 mprotect
0.13 0.000049 3 15 prlimit64
0.11 0.000043 10 4 munmap
0.10 0.000038 19 2 ppoll
0.08 0.000032 32 1 unlink
0.08 0.000032 16 2 pipe2
0.07 0.000026 4 6 ioctl
0.06 0.000024 4 6 rt_sigprocmask
0.06 0.000022 2 9 brk
0.06 0.000021 2 8 fcntl
0.05 0.000017 2 6 rt_sigaction
0.04 0.000014 4 3 fstat
0.03 0.000011 2 4 pread64
0.02 0.000009 4 2 rt_sigpending
0.02 0.000008 8 1 1 access
0.01 0.000003 3 1 getrandom
0.01 0.000002 2 1 getpid
0.01 0.000002 2 1 sched_getaffinity
0.01 0.000002 2 1 set_tid_address
0.01 0.000002 2 1 set_robust_list
0.01 0.000002 2 1 rseq
0.00 0.000001 0 2 1 arch_prctl
0.00 0.000000 0 1 execve
------ ----------- ----------- --------- --------- ------------------
100.00 0.037663 25 1474 50 total
可以看到, 我們除了在 configure 階段將 generator 設定為 Ninja 以外, 沒有做任何改動
然而, 單單這樣就提升了 60% 的速度!
Ninja 的介紹就到這裡, 下一篇要來準備 cross compile Windows 的環境啦!