So sánh chi tiết các thuật toán cấp phát bộ nhớ malloc(): Từ dlmalloc đến jemalloc, mimalloc và snmalloc

Công nghệ15 tháng 9, 2026·8 phút đọc

Bài viết phân tích quá trình tiến hóa của các thuật toán cấp phát bộ nhớ động (malloc) từ dlmalloc thời kỳ đầu đến các giải pháp hiện đại như jemalloc, tcmalloc, mimalloc và snmalloc. Đặc biệt tập trung vào kiến trúc arena, cơ chế đồng bộ hóa không khóa (lock-free), khả năng nhận biết NUMA và các tiêu chí lựa chọn allocator phù hợp với từng loại tải công việc.

So sánh chi tiết các thuật toán cấp phát bộ nhớ malloc(): Từ dlmalloc đến jemalloc, mimalloc và snmalloc

So sánh chi tiết các thuật toán cấp phát bộ nhớ malloc(): Từ dlmalloc đến jemalloc, mimalloc và snmalloc

Cấp phát bộ nhớ động (malloc) tưởng chừng là một chủ đề khô khan nhưng lại là nền tảng của mọi chương trình phần mềm. Bài viết này phân tích quá trình tiến hóa của các thuật toán cấp phát bộ nhớ, từ những thiết kế đơn giản thời kỳ đầu đến các giải pháp hiện đại được tối ưu cho đa luồng, kiến trúc NUMA và hiệu năng cao.

Điểm cốt lõi cần nhớ: malloc() là API cấp phát bộ nhớ tệ nhất để sử dụng trong các chương trình đa luồng hiệu năng cao. Khi nhiều luồng đồng thời cấp phát hoặc giải phóng bộ nhớ, allocator sẽ tuần tự hóa chúng, khiến chương trình chậm đi thay vì nhanh hơn khi tăng số lõi CPU.

Minh họa cấu trúc cây quyết định của mallocMinh họa cấu trúc cây quyết định của malloc

Vấn đề cốt lõi: Heap là nút thắt cổ chai

Trong các chương trình đa luồng, heap thường trở thành điểm nghẽn cổ chai nghiêm trọng. Khi nhiều luồng cùng truy cập allocator, cơ chế khóa nội bộ sẽ buộc chúng phải chờ nhau. Điều này đặc biệt nghiêm trọng trong các hệ thống xử lý mạng, nơi mỗi gói tin đến đều kích hoạt cấp phát bộ nhớ.

Trong nhân Linux, các bản vá kernel/driver cho phép tái sử dụng skbuff (bộ nhớ kernel lưu gói tin vào/ra). Sử dụng PF_RING để sao chép gói tin từ NIC vào circular buffer mà không cần cấp phát bộ nhớ giúp tăng hiệu năng bắt gói khoảng 10% và giảm tắc nghẽn.

Nguyên tắc vàng: hạn chế tối đa việc cấp phát và giải phóng bộ nhớ thường xuyên, đặc biệt là trong các vòng lặp xử lý sự kiện hoặc mỗi khi nhận gói tin.

Quá trình tiến hóa thiết kế malloc()

Thiết kế cơ bản của malloc() là pre-allocate một vùng nhớ từ hệ điều hành, sau đó ứng dụng lấy ra những phần nhỏ hơn. Đây là API chuẩn với nhiều thuật toán cấp phát khác nhau, giúp giảm thiểu các lời gọi hệ thống tốn kém (thường chỉ diễn ra khi khởi tạo chương trình).

Sự phát triển của frontend

Frontend quản lý bộ nhớ được cấp cho ứng dụng. Quá trình tiến hóa diễn ra theo thứ tự:

  • Danh sách liên kết không gian trống (link-list free space)
  • Phân lớp kích thước theo heap-bucket (loại bỏ phần header đối tượng)
  • Mã hóa chủ sở hữu tiến trình (owner encoding)
  • Bộ đệm cấp phát cục bộ theo lõi đơn (CLABs)
  • Mã hóa epoch
  • Khối bộ nhớ lớp lớn bằng mmap() trực tiếp
  • Con trỏ nguy hiểm (hazard pointers) — thu hồi bộ nhớ an toàn cho đối tượng lock-free (M.M. Michael, 2004)
  • Arena memory pool (riêng biệt theo CPU/core và thread)
  • Bộ đệm cấp phát cục bộ theo luồng (TLABs)

Sự phát triển của backend

Backend quản lý việc trả các khối bộ nhớ trống, phân mảnh hoặc không còn dùng về hệ điều hành (giảm RSS):

  • Ngữ nghĩa pool với mã hóa free-list từ xa dùng Treiber stack (R.K. Treiber, 1986)
  • Thuật toán buddy và binary buddy
  • Bảng BIPOP (allocator dựa trên span) (S. Schneider, 2006)
  • Segment queue với quasi-linearizability (Y. Afek, 2010)
  • Multi-core distributed queue (A. Haas, 2013)
  • k-FIFO queue (T.A. Henzinger, 2013)

So sánh các allocator phổ biến

Dưới đây là bảng so sánh các allocator theo thứ tự từ tốt nhất đến khá tốt:

AllocatorNguồn gốcThread SafePer-Thread CacheMulti-ArenaLock-Free Fast PathNhận biết NUMAKiểm soát phân mảnh
dlmallocDoug LeaKhôngKhôngKhôngKhôngKhôngThấp
ptmalloc2/3glibcHạn chếKhôngKhôngTrung bình
jemallocFreeBSD/MetaMột phầnMột phầnCao
tcmallocGoogleMột phầnHạn chếTrung bình-Cao
mimallocMicrosoftKhôngCao
HoardEmery BergerMột phầnKhôngTrung bình
libumemSolarisTrung bình-Cao
snmallocMicrosoft ResearchCao

dlmalloc của Doug Lea là nền tảng cho nhiều allocator sau này nhưng chỉ dùng một heap toàn cục, không an toàn cho đa luồng.

ptmalloc là mặc định của glibc, nhưng khóa arena gây ra tranh chấp đáng kể khi có nhiều luồng.

jemalloc nổi bật với cơ chế arena-theo-luồng (thread-arena affinity) giúp giảm tranh chấp CAS. Đây là lựa chọn tuyệt vời cho hầu hết tình huống.

tcmalloc của Google được tối ưu cho cấp phát đối tượng nhỏ với bộ đệm per-CPU.

mimalloc của Microsoft được thiết kế để giảm thiểu tối đa các thao tác atomic và false sharing, mang lại độ trễ có thể dự đoán được.

snmalloc của Microsoft Research theo đuổi mô hình truyền thông điệp (message-passing) để tránh hoàn toàn CAS chia sẻ, đồng thời hỗ trợ NUMA hàng đầu.

Đặc điểm tranh chấp CAS và atomic

Một khía cạnh quan trọng thường bị bỏ qua là số lượng thao tác atomic trên mỗi lần cấp phát/giải phóng:

  • dlmalloc: Số atomic cao, rủi ro cacheline chia sẻ cao, độ nhạy CAS rất cao do cấu trúc toàn cục
  • jemalloc: Số atomic thấp, metadata cục bộ theo arena, ít CAS chia sẻ
  • mimalloc: Số atomic rất thấp, rủi ro cacheline rất thấp
  • snmalloc: Số atomic rất thấp nhờ mô hình truyền thông điệp

Đây là lý do tại sao các allocator hiện đại tập trung vào việc giảm chia sẻ trạng thái giữa các lõi CPU.

Đặc điểm NUMA và cục bộ bộ nhớ

Trên các máy chủ đa socket, truy cập bộ nhớ cross-node rất tốn kém. Các allocator xử lý vấn đề này khác nhau:

  • snmalloclibumem: Hỗ trợ NUMA rõ ràng, bảo toàn cục bộ xuất sắc
  • jemalloc: Hỗ trợ NUMA một phần qua tinh chỉnh arena
  • mimalloc: Không hỗ trợ NUMA rõ ràng nhưng có cục bộ luồng rất mạnh
  • dlmalloc: Không hỗ trợ NUMA, heap đơn trải khắp các node

Hiệu năng thực tế theo benchmark

AllocatorThông lượng cấp phát nhỏThông lượng cấp phát lớnĐộ trễ khi tranh chấpOverhead bộ nhớRủi ro phân mảnh
dlmallocThấpTrung bìnhKémThấpCao
ptmallocTrung bìnhTrung bìnhKémTrung bìnhTrung bình
jemallocCaoCaoRất tốtTrung bình-ThấpThấp
tcmallocRất caoTrung bìnhTốtTrung bìnhTrung bình
mimallocCaoCaoXuất sắcThấpThấp
snmallocCaoCaoXuất sắcThấpRất thấp

Khuyến nghị chọn allocator theo loại tải công việc

Việc chọn đúng allocator phụ thuộc vào đặc điểm tải công việc cụ thể:

  • Đa luồng tranh chấp cao: Chọn jemalloc (multi-arena + thread affinity giảm thiểu CAS chia sẻ) hoặc mimalloc. Tránh dlmalloc và ptmalloc.
  • Độ trễ thấp, nhạy cảm đuôi (tail latency): Chọn mimalloc nhờ số atomic rất thấp và đường dẫn nhanh có thể dự đoán. Thay thế: snmalloc.
  • Máy chủ NUMA đa socket: Chọn snmalloc với nhận biết NUMA rõ ràng. Thay thế: jemalloc có tinh chỉnh NUMA.
  • Tải đối tượng nhỏ (RPC/Web): Chọn tcmalloc với bộ đệm per-CPU tối ưu cho cấp phát nhỏ. Thay thế: jemalloc.
  • Đối tượng lớn kích thước hỗn hợp: Chọn jemalloc với kiểm soát phân mảnh xuất sắc. Thay thế: mimalloc.
  • Bảo mật, chống khai thác use-after-free: Chọn snmalloc với cô lập và bất biến bảo mật. Thay thế: mimalloc bản secure.
  • Debug và phát hiện rò rỉ bộ nhớ: Chọn libumem với chẩn đoán runtime mạnh. Thay thế: jemalloc bản profiling.
  • Nhúng, bộ nhớ hạn chế: Chọn dlmalloc — nhỏ và đơn giản nếu đơn luồng.
  • Thời gian thực, độ trễ xác định: Chọn mimalloc với đường dẫn nhanh biến thiên thấp. Thay thế: snmalloc.
  • HPC/Khoa học NUMA: Chọn snmalloc với thiết kế NUMA-first và lưu lượng CAS thấp.
  • Tương thích cũ/legacy: Chọn glibc malloc làm mặc định an toàn nhất.

Kết luận cho lập trình viên Việt Nam

Đối với các dự án phần mềm tại Việt Nam, đặc biệt là các hệ thống backend, microservices và xử lý dữ liệu lớn, việc chọn allocator phù hợp có thể mang lại cải thiện hiệu năng đáng kể. Trong nhiều trường hợp, chỉ cần thay đổi từ glibc malloc mặc định sang jemalloc hoặc mimalloc thông qua biến môi trường LD_PRELOAD có thể giảm độ trễ và tăng khả năng mở rộng mà không cần thay đổi mã nguồn.

Với các hệ thống cloud/container chạy trên hạ tầng nhiều lõi, các allocator hiện đại như jemalloc và mimalloc gần như là lựa chọn bắt buộc để tránh nút thắt cổ chai từ heap.

Cấp phát bộ nhớ tưởng chừng là chi tiết nhỏ nhưng lại ảnh hưởng trực tiếp đến khả năng mở rộng của toàn bộ hệ thống. Hiểu rõ các đặc điểm của từng allocator giúp kỹ sư đưa ra quyết định đúng đắn ngay từ giai đoạn thiết kế kiến trúc.

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗