Bỏ qua điều hướng, tới nội dung chính
Học C
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ĐộngChênh lệch thực tế
Kích thước tệp nhị phânLớnNhỏ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ìnhMỗi tiến trình một bảnDùng chung một bảnVới 100 tiến trình và thư viện 5 MB: 500 MB so với 5 MB
Vá lỗi thư việnPhải dịch lại chương trìnhThay tệp là xongĐây là lý do các bản phân phối Linux chọn động
Tốc độ khởi độngNhanhChậm hơnVài trăm micro giây tới vài mili giây, tùy số thư viện
Triển khaiMột tệp duy nhấtPhải mang theo thư việnĐây là lý do Go và Rust mặc định liên kết tĩnh
Rủi roGần như khôngThiếu tệp, lệch phiên bản, xung độtCụ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ốngNên chọnVì sao
Công cụ dòng lệnh phát hành cho người dùng cuốiTĩnhMộ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ĐộngBả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ĐộngTiết kiệm bộ nhớ, vá lỗi tập trung
Phần mềm nhúngTĩnhKhô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ĐộngBắt buộc, vì phải nạp lúc chạy
Vùng chứa container tối thiểuTĩ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ùngCả haiNgười dùng chọn, và đó là việc của Bài 39.12

Tự làm thử

  1. Dịch hello.c ở hai dạng trên Linux và so kích thước.
  2. 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ũ.
  3. Đưa hằng số đó vào main.c rồi lặp lại, và giải thích vì sao kết quả khác.
  4. Chạy một trăm tiến trình cùng chương trình và đo Pss.
  5. Đo thời gian khởi động ở cả hai dạng bằng hyperfine.
  6. Dùng -Bstatic và -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.56636 thành 12.56637 mà 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.