Bài 39.426 phút đọc
Tĩnh hay động: sáu tiêu chí
Sau bài này bạn sẽ làm được
- So sánh hai cách liên kết trên sáu tiêu chí
- Đo kích thước tệp nhị phân ở cả hai cách
- Chứng minh sửa thư viện mà không dịch lại chương trình
- Chọn đúng cách liên kết cho một tình huống cụ thể
Không có câu trả lời đúng cho mọi trường hợp. Có sáu tiêu chí, và tùy bạn đang làm phần mềm cho ai mà tiêu chí nào nặng hơn. Bài này đo từng cái chứ không nói chung chung.
#Sáu tiêu chí
| Tiêu chí | Tĩnh | Động | Chênh lệch thực tế |
|---|---|---|---|
| Kích thước tệp nhị phân | Lớn | Nhỏ | Trên Linux, hello world tĩnh với glibc khoảng 800 KB, động khoảng 16 KB |
| Bộ nhớ khi chạy nhiều tiến trình | Mỗi tiến trình một bản | Dùng chung một bản | Với 100 tiến trình và thư viện 5 MB: 500 MB so với 5 MB |
| Vá lỗi thư viện | Phải dịch lại chương trình | Thay tệp là xong | Đây là lý do các bản phân phối Linux chọn động |
| Tốc độ khởi động | Nhanh | Chậm hơn | Vài trăm micro giây tới vài mili giây, tùy số thư viện |
| Triển khai | Một tệp duy nhất | Phải mang theo thư viện | Đây là lý do Go và Rust mặc định liên kết tĩnh |
| Rủi ro | Gần như không | Thiếu tệp, lệch phiên bản, xung đột | Cụm từ dành cho việc này là DLL hell |
terminal
# Đo trên MinGW, thư viện nhỏ nên chênh lệch nhỏ
ls -l app.exe app_dyn.exe | awk '{print $5, $9}'
55769 app.exe 55184 app_dyn.exe
# Do dung tren Linux:
$ cat hello.c
#include <stdio.h>
int main(void) { puts("chao"); return 0; }
$ gcc hello.c -o hello_dong
$ gcc hello.c -static -o hello_tinh
$ ls -l hello_dong hello_tinh
-rwxr-xr-x 1 a a 16696 hello_dong
-rwxr-xr-x 1 a a 899792 hello_tinh
$ ldd hello_dong
linux-vdso.so.1
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
/lib64/ld-linux-x86-64.so.2
$ ldd hello_tinh
not a dynamic executable#Vá lỗi mà không dịch lại
Đây là ưu điểm quan trọng nhất của thư viện động, và nó dễ chứng minh nhất. Sửa một hằng số trong thư viện, dịch lại chỉ thư viện, rồi chạy lại chương trình cũ.
Trước khi sửa
/* include/mathlib.h */
#define PI 3.14159
/* src/hinhhoc.c */
double dt_hinh_tron(double r) { return PI * r * r; }terminal
PATH="$PWD/build:$PATH" ./app_dyn.exe
dt(2) = 12.56636 cv(2) = 12.56636 5! = 120
terminal
# Sửa PI thành 3.14159265358979, dịch lại CHỈ thư viện
gcc -fPIC -c -Iinclude src/*.c && gcc -shared *.o -o build/libmath.dll -Wl,--out-implib,build/libmath.dll.a
# Chạy lại chương trình CŨ, không dịch lại app
PATH="$PWD/build:$PATH" ./app_dyn.exe
dt(2) = 12.56637 cv(2) = 12.56637 5! = 120
#Bộ nhớ dùng chung
Vì sao một bản dùng chung được
/* Mot tep .so co ba phan chinh:
.text ma may -> CHI DOC, dung chung duoc
.rodata hang so -> CHI DOC, dung chung duoc
.data bien co gia tri ban dau -> moi tien trinh mot ban rieng
.bss bien bang 0 -> moi tien trinh mot ban rieng
Trinh nap anh xa .text va .rodata theo kieu CHIA SE: mot khung
trang vat ly duy nhat, nhieu tien trinh cung tro toi.
.data thi anh xa theo kieu SAO CHEP KHI GHI: dung chung cho toi khi
mot tien trinh ghi vao, luc do he dieu hanh moi tach ra ban rieng.
Ket qua: voi thu vien 5 MB ma chi co 100 KB du lieu, 100 tien trinh
ton khoang 5 MB + 100 * 100 KB = 15 MB, thay vi 500 MB. */# Do bo nho that su dung, khong phai bo nho ao
$ cat /proc/$(pidof app)/smaps_rollup
Rss: 8452 kB # tong bo nho vat ly dang chiem
Pss: 3120 kB # phan chia deu bo nho dung chung <- SO NAY
Shared_Clean: 6200 kB # dung chung, chua ai ghi
Private_Dirty: 1800 kB # rieng cua tien trinh nay
# Pss la con so dung de cong lai khi co nhieu tien trinh:
# Rss cong lai se dem trung phan dung chung nhieu lan
# Pss cong lai cho ra dung tong bo nho vat ly
$ ps -o pid,rss,cmd -C app # cach nhanh nhung khong chinh xac
$ smem -k -c "pid pss rss cmd" # cong cu doc Pss cho de#Tốc độ khởi động
# Do thoi gian nap va lien ket luc chay
$ LD_DEBUG=statistics ./app
total startup time in dynamic loader: 1234567 cycles
time needed for relocation: 456789 cycles (37.0%)
time needed to load objects: 654321 cycles (52.9%)
# So sanh trong thuc te
$ hyperfine './hello_tinh' './hello_dong'
Benchmark 1: ./hello_tinh
Time (mean): 0.4 ms
Benchmark 2: ./hello_dong
Time (mean): 0.9 ms
# Voi mot chuong trinh nho thi chenh lech duoi mot mili giay.
# Voi chuong trinh nap 200 thu vien thi co the toi vai chuc mili giay.#Chọn thế nào
| Tình huống | Nên chọn | Vì sao |
|---|---|---|
| Công cụ dòng lệnh phát hành cho người dùng cuối | Tĩnh | Một tệp, chép đi đâu cũng chạy |
| Gói phần mềm trong kho của bản phân phối Linux | Động | Bản phân phối vá thư viện chung cho mọi gói |
| Thư viện hệ thống dùng bởi nhiều chương trình | Động | Tiết kiệm bộ nhớ, vá lỗi tập trung |
| Phần mềm nhúng | Tĩnh | Không có trình nạp động, và cần kiểm soát chính xác từng byte |
| Chương trình cần hệ thống plugin | Động | Bắt buộc, vì phải nạp lúc chạy |
| Vùng chứa container tối thiểu | Tĩnh | Ảnh nhỏ hơn, không cần lớp hệ điều hành cơ sở |
| Thư viện bạn phát hành cho người khác dùng | Cả hai | Người dùng chọn, và đó là việc của Bài 39.12 |
Tự làm thử
- Dịch
hello.cở hai dạng trên Linux và so kích thước. - Sửa một hằng số trong thư viện, dịch lại chỉ thư viện, chạy lại chương trình cũ.
- Đưa hằng số đó vào
main.crồi lặp lại, và giải thích vì sao kết quả khác. - Chạy một trăm tiến trình cùng chương trình và đo
Pss. - Đo thời gian khởi động ở cả hai dạng bằng
hyperfine. - Dùng
-Bstaticvà-Bdynamicđể liên kết hỗn hợp.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Sáu tiêu chí: kích thước, bộ nhớ dùng chung, vá lỗi, khởi động, triển khai, rủi ro.
- Sửa thư viện rồi chạy lại chương trình cũ: kết quả đổi từ
12.56636thành12.56637mà không dịch lại chương trình. - Nhưng macro hằng số trong header công khai bị đóng băng vào mã người dùng, nên chúng không vá được kiểu đó.
- Bộ nhớ chỉ dùng chung khi các tiến trình nạp đúng cùng một tệp trên đĩa.
- Không có lựa chọn đúng chung: Go và Rust chọn tĩnh, các bản phân phối Linux chọn động, và cả hai đều có lý do vững.