Bộ cấp phát bộ nhớ Musl gây ra hiện tượng giảm hiệu suất 700 lần trong các ứng dụng đa luồng

Nhóm Cộng đồng BigGo
Bộ cấp phát bộ nhớ Musl gây ra hiện tượng giảm hiệu suất 700 lần trong các ứng dụng đa luồng

Một cuộc điều tra gần đây về các vấn đề hiệu suất với musl libc đã tiết lộ những kết quả gây sốc có thể ảnh hưởng đến vô số ứng dụng. Thư viện C musl , thường được sử dụng trong các container Alpine Linux và các bản build tĩnh, bao gồm một bộ cấp phát bộ nhớ có thể gây ra sự chậm lại đáng kể trong các chương trình đa luồng. Trong những trường hợp cực đoan, các ứng dụng có thể chậm đi tới 700 lần so với mong đợi.

Tác Động Hiệu Suất Nghiêm Trọng Theo Số Lượng Lõi

Máy 6 lõi: chậm hơn 7 lần Máy 8 lõi: chậm hơn 4 lần
Máy 48 lõi: chậm hơn 700 lần

Sự suy giảm hiệu suất tăng lên một cách đáng kể khi có nhiều lõi CPU hơn, do nhiều luồng cạnh tranh cho một khóa phân bổ bộ nhớ duy nhất. Các máy chủ đám mây hiện đại có thể có 192+ lõi, khiến vấn đề này trở nên nghiêm trọng hơn đối với các ứng dụng có khả năng mở rộng.

Nguyên nhân gốc rễ: Xung đột khóa

Vấn đề hiệu suất xuất phát từ thiết kế bộ cấp phát của musl , sử dụng một khóa chia sẻ duy nhất cho tất cả các thao tác bộ nhớ. Khi nhiều luồng cố gắng cấp phát hoặc giải phóng bộ nhớ cùng lúc, chúng phải chờ đợi lẫn nhau, tạo ra một nút thắt cổ chai. Thiết kế này hoạt động tốt cho các chương trình đơn luồng nhưng trở thành vấn đề lớn khi có nhiều lõi CPU và luồng tham gia.

Cộng đồng đã xác định rằng bộ cấp phát của musl dựa vào một heap duy nhất với cơ chế khóa để hỗ trợ nhiều luồng, có nghĩa là mỗi thao tác bộ nhớ phải lấy khóa này. Các giải pháp thay thế hiện đại như mimalloc sử dụng heap theo từng luồng thay vào đó, trong đó mỗi luồng quản lý không gian bộ nhớ riêng của mình. Cách tiếp cận này hoạt động đặc biệt tốt với các ngôn ngữ lập trình như Rust , nơi các đối tượng hiếm khi di chuyển giữa các luồng.

Tác động thực tế trên các dự án

Vấn đề này không phải là mới, nhưng nó tiếp tục làm các nhà phát triển bất ngờ. Nhiều dự án đã ghi nhận các vấn đề tương tự, với mức độ chậm lại từ 2 đến 20 lần trong các ứng dụng thông thường. Sự biến đổi phụ thuộc vào số lượng luồng cạnh tranh cho bộ nhớ và tần suất chúng cấp phát bộ nhớ mới.

Một số dự án lớn đã chuyển sang không sử dụng bộ cấp phát mặc định của musl hoặc từ bỏ musl hoàn toàn. Tác động hiệu suất trở nên nghiêm trọng hơn trên các hệ thống đa lõi hiện đại, nơi các ứng dụng tự nhiên sử dụng nhiều luồng hơn để tận dụng sức mạnh xử lý có sẵn.

So sánh hiệu suất: glibc so với musl

Chỉ số glibc musl Sự khác biệt
Thời gian người dùng (giây) 1.31 2.72 Chậm hơn 2.1 lần
Thời gian hệ thống (giây) 0.32 6.13 Chậm hơn 19.2 lần
Thời gian trôi qua (giây) 0.17 1.18 Chậm hơn 7 lần
Chuyển đổi ngữ cảnh tự nguyện 1,196 159,786 Nhiều hơn 167 lần
Tỷ lệ sử dụng CPU 943% 745% Kém hiệu quả hơn 21%

Bộ cấp phát mới không giúp ích

Nhiều nhà phát triển hy vọng rằng bộ cấp phát mallocng mới hơn của musl , được giới thiệu trong phiên bản 1.2.1, sẽ giải quyết các vấn đề hiệu suất này. Thật không may, việc thử nghiệm cho thấy nó không tạo ra sự khác biệt đáng kể cho các ứng dụng đa luồng. Nhóm phát triển musl đã thiết kế bộ cấp phát mới để ưu tiên việc sử dụng bộ nhớ thấp và bảo mật hơn là hiệu suất thô.

Bộ cấp phát mallocng được thiết kế để ưu tiên chi phí bộ nhớ rất thấp, chi phí phân mảnh trong trường hợp xấu nhất thấp, và tăng cường bảo mật hơn là hiệu suất.

Lựa chọn thiết kế này phản ánh triết lý của musl trong việc cung cấp các mặc định an toàn trong khi cho phép các ứng dụng lựa chọn các bộ cấp phát nhanh hơn khi cần thiết.

Các giải pháp đơn giản có sẵn

May mắn thay, việc khắc phục vấn đề này khá đơn giản đối với hầu hết các ứng dụng. Các nhà phát triển có thể dễ dàng thay thế bằng các bộ cấp phát thay thế như mimalloc hoặc jemalloc , xử lý các khối lượng công việc đa luồng tốt hơn nhiều. Đối với các dự án Rust , việc thêm một vài dòng vào tệp cấu hình có thể giải quyết hoàn toàn vấn đề.

Cách khắc phục bao gồm việc sử dụng có điều kiện một bộ cấp phát khác chỉ khi build với musl , vì vậy nó không ảnh hưởng đến các nền tảng khác. Cách tiếp cận này mang lại cho các nhà phát triển những lợi ích của kích thước nhỏ và khả năng tương thích đa nền tảng của musl mà không bị phạt hiệu suất.

Giải pháp nhanh cho các dự án Rust

Thêm những dòng này vào tệp Cargo.toml của bạn để tránh các vấn đề về hiệu suất của bộ cấp phát musl:

 Tránh bộ cấp phát mặc định của musl do xung đột khóa
[target.'cfg(target_env = "musl")'.dependencies]
mimalloc = "1.4.0"

Các bộ cấp phát thay thế:

  • mimalloc: Thiết kế heap hiện đại, theo từng luồng
  • jemalloc: Giải pháp thay thế trưởng thành, được sử dụng rộng rãi
  • tcmalloc: Malloc có bộ nhớ đệm luồng của Google

Khi hiệu suất quan trọng

Một số nhà phát triển có kinh nghiệm cho rằng việc đạt đến giới hạn hiệu suất của bộ cấp phát cho thấy thiết kế chương trình kém. Họ đề xuất rằng mã được viết tốt nên giảm thiểu việc cấp phát bộ nhớ trong các phần quan trọng về hiệu suất. Mặc dù lời khuyên này có giá trị, nó không tính đến thực tế rằng nhiều ứng dụng cần hoạt động tốt với các thực hành mã hóa hợp lý, không chỉ những cái được tối ưu hóa hoàn hảo.

Cuộc thảo luận cộng đồng tiết lộ sự chia rẽ giữa những người coi đây là một lỗ hổng cơ bản và những người coi đây là một sự đánh đổi có thể chấp nhận được cho các lợi ích khác của musl . Đối với các ứng dụng không cấp phát bộ nhớ thường xuyên hoặc sử dụng ít luồng hơn, tác động hiệu suất có thể không đáng kể.

Tham khảo: Default musl allocator considered harmful (to performance)