- Cài đặt gói của Bun hoạt động với tốc độ rất nhanh so với các trình quản lý gói hiện có
- Cốt lõi của tốc độ cài đặt cao nằm ở cách tiếp cận theo góc nhìn lập trình hệ thống và giảm thiểu system call
- Hiệu năng được cải thiện nhờ áp dụng các chiến lược tinh chỉnh như lập trình native bằng ngôn ngữ Zig, sử dụng bộ nhớ đệm nhị phân và tối ưu hóa theo từng hệ điều hành
- Ngay cả trong quá trình giải nén tarball và sao chép tệp, Bun cũng áp dụng các phương pháp hiệu năng cao tận dụng đặc tính phần cứng
- Thông qua tối ưu hóa cấu trúc dữ liệu như đồ thị phụ thuộc và lockfile, Bun nâng cao hiệu quả cache CPU và khả năng truy cập bộ nhớ
Vì sao Bun Install nhanh
bun install của Bun trung bình mang lại hiệu năng cài đặt gói nhanh hơn npm 7 lần, pnpm 4 lần và yarn 17 lần
- Đây không chỉ là khác biệt trong benchmark, mà còn đến từ việc tiếp cận bài toán cài đặt gói từ góc nhìn lập trình hệ thống thay vì JavaScript
- Bun tích cực áp dụng tối ưu hiệu năng ở nhiều tầng như giảm số lượng system call, lưu cache nhị phân cho manifest, tối ưu giải nén tarball và sao chép tệp native theo hệ điều hành
Giới hạn của Node.js và kiến trúc trình quản lý gói
- Từ sau khi Node.js ra mắt năm 2009, mô hình IO bất đồng bộ dựa trên event loop và thread pool cũng được lan sang các trình quản lý gói
- Vào thời điểm đó, do hạn chế phần cứng như ổ đĩa chậm và mạng chậm, chiến lược IO bất đồng bộ với tần suất system call cao là hợp lý
- Tuy nhiên, trên các hệ thống hiện đại, SSD NVMe, mạng nhanh và CPU hiệu năng cao đã trở nên phổ biến, và nút thắt thực sự không còn là IO mà là overhead của system call
Chi phí của system call và chuyển đổi chế độ
- Khi chương trình yêu cầu thực hiện tác vụ như đọc tệp, nó phải chuyển từ user mode sang kernel mode, và quá trình này tiêu tốn một lượng chu kỳ CPU đắt đỏ (1000~1500 cycles)
- Về bản chất, cài đặt gói cần tới hàng chục nghìn đến hàng trăm nghìn system call, nên chỉ riêng chi phí chuyển đổi tác vụ thực tế cũng có thể tiêu tốn vài giây thời gian CPU
- Ví dụ, khi cài đặt React và các phụ thuộc, npm sử dụng khoảng 1 triệu system call, yarn 4 triệu, pnpm 500 nghìn, còn bun là 160 nghìn
Khác biệt trong cách tiếp cận giữa các trình quản lý gói hiện có và Bun
- npm, pnpm và yarn đều dựa trên Node.js, nên JavaScript phải chạy qua nhiều lớp trừu tượng như libuv, event loop, thread pool và tầng trung gian của system call
- Trong quá trình đó, việc chuyển đổi đối số, hàng đợi worker pool, phân nhánh công việc trong event loop và các system call futex (đồng bộ khóa) bị cộng dồn, khiến việc quản lý system call thậm chí còn chậm hơn cả IO
- Các trình quản lý gói viết bằng Node.js khó đạt được hiệu năng tiệm cận native do các giới hạn mang tính cấu trúc này
Bun: engine cài đặt native được triển khai bằng Zig
- Bun gọi trực tiếp system call bằng ngôn ngữ Zig, bỏ qua hoàn toàn JavaScript engine và các lớp trừu tượng
- Ví dụ, việc đọc tệp được thực hiện bằng cách gọi trực tiếp system call
openat() trong mã Zig để trả về dữ liệu ngay lập tức
- Vì vậy, quá trình đọc hàng chục nghìn tệp có thể chạy cực nhanh mà không cần thread pool, event loop hay bước chuyển đổi dữ liệu riêng biệt
- Theo benchmark, Bun có thể đọc 146.057 file
package.json mỗi giây, trong khi Node.js chậm hơn hơn 2 lần ở mức khoảng 60 nghìn file
Tối ưu quản lý phụ thuộc và DNS
- Khi chạy
bun install, Bun sẽ phân tích phụ thuộc đồng thời kích hoạt DNS prefetch một cách bất đồng bộ
- Chẳng hạn trên macOS, Bun sử dụng API DNS bất đồng bộ không chính thức của Apple (
getaddrinfo_async_start()), hỗ trợ xử lý đồng thời tác vụ mạng mà không chặn thread
- Các trình quản lý gói hiện có dựa trên thread pool của libuv, nên trên thực tế vẫn chạy mã blocking bên trong và gây lãng phí tài nguyên
Lưu cache nhị phân cho manifest gói
- npm và các công cụ tương tự lưu cache manifest ở dạng JSON, còn Bun sau khi parse một lần sẽ chuyển kết quả đó sang dạng nhị phân (
.npm) để lưu trữ
- Giảm thiểu trùng lặp chuỗi và overhead parse, đồng thời trong bộ nhớ thực tế có thể truy cập giá trị ngay bằng cách tính offset mà không cần tạo object mới, parse lại hay garbage collection
- Bun dùng header ETag và
If-None-Match để chỉ kiểm tra phần thay đổi, giúp xác minh tính cập nhật mà không cần parse dữ liệu không cần thiết
- Theo benchmark, cài đặt từ cache của Bun còn nhanh hơn cả fresh install của npm
Hiệu năng xử lý tarball (tệp nén)
- Các trình quản lý gói thông thường nhận tarball dưới dạng stream, và mỗi khi bộ nhớ đệm không đủ sẽ liên tục phát sinh cấp phát lại, sao chép và thay đổi kích thước
- Bun nhận toàn bộ tarball rồi mới giải nén, đồng thời dùng 4 byte cuối của gzip để biết trước kích thước sau giải nén → chỉ cấp phát bộ nhớ đúng một lần
- Bun cũng tận dụng
libdeflate để giải nén nhanh, đồng thời loại bỏ hoàn toàn việc sao chép dư thừa và thay đổi kích thước không cần thiết
Tối ưu đồ thị phụ thuộc và cấu trúc dữ liệu
- Các trình quản lý gói hiện có tạo cây phụ thuộc dựa trên object/pointer JavaScript, dẫn tới bộ nhớ bị phân tán ngẫu nhiên và thường xuyên xảy ra cache miss của CPU (vấn đề pointer chasing)
- Bun áp dụng mô hình Structure of Arrays (SoA), lưu toàn bộ package, chuỗi và phụ thuộc trong các khối bộ nhớ liên tục lớn
- Truy cập dựa trên offset/độ dài cho phép CPU đọc nhiều package cùng lúc theo đơn vị cache line (cấu trúc thân thiện với cache)
- Lockfile cũng được lưu theo mô hình SoA thay vì JSON/YAML, giúp loại bỏ trùng lặp chuỗi và thuận lợi cho truy cập bộ nhớ tuần tự
- Bun từng thử nghiệm định dạng lockfile nhị phân (
bun.lockb), nhưng sau đó chuyển sang định dạng văn bản dễ đọc hơn do làm giảm khả năng cộng tác với Git
Tối ưu sao chép tệp theo từng hệ điều hành
macOS
- Dùng
clonefile: sao chép toàn bộ thư mục bằng cơ chế Copy-On-Write chỉ với một system call
- Giảm tối đa việc dùng trùng lặp dung lượng đĩa và tăng tốc độ cài đặt lên mức cao nhất
- Nếu
clonefile thất bại, Bun sẽ fallback theo từng bước từ cloning theo từng thư mục sang copyfile
Linux
- Ưu tiên thử hard link: không tạo tệp mới mà chỉ tạo tham chiếu mới tới tệp hiện có (không cần di chuyển dữ liệu trên đĩa)
- Nếu không thể dùng hard link, trên Btrfs/XFS Bun sẽ áp dụng Copy-On-Write bằng
ioctl_ficlone
- Sau đó mới fallback lần lượt sang
copy_file_range, sendfile, và cuối cùng là cách copyfile thông thường
Tổng kết
- Bun đã vượt qua giới hạn hiệu năng truyền thống của trình quản lý gói nhờ giảm thiểu system call, cấu trúc nhị phân, tối ưu theo hệ điều hành và cải tiến cấu trúc dữ liệu
- Nhờ đó, Bun không chỉ cài đặt cực nhanh mà còn cải thiện cả hiệu quả bộ nhớ và CPU
- So với các trình quản lý dựa trên Node.js hiện có, Bun có thể được áp dụng vào dự án mà không cần thay runtime riêng biệt (vẫn giữ khả năng tương thích)
- Trong các codebase lớn ngoài thực tế, Bun mang lại trải nghiệm rút ngắn quá trình cài đặt từ vài phút xuống còn vài mili giây đến vài giây
- Đây là một ví dụ xuất sắc về tối ưu hóa được thiết kế sát với cấp độ hệ thống, phần cứng và hệ điều hành, rất đáng để nghiên cứu và tham khảo
Chưa có bình luận nào.