Một Số Suy Nghĩ Và Thực Nghiệm Về Bộ Phân Bổ Bộ Nhớ Lua
Từ cuối tuần trước, tôi luôn bận rộn với một ý tưởng thú vị: thiết kế một bộ phân bổ bộ nhớ tùy biến cho các dịch vụ Lua trong Skynet. Mục tiêu không phải tối ưu hiệu năng, mà là khả năng tạo snapshot bộ nhớ riêng cho từng máy ảo Lua thông qua việc sử dụng mmap ánh xạ bộ nhớ ảo. Đây là nền tảng cho ba ứng dụng quan trọng:
-
Tối ưu hóa tuần tự hóa dữ liệu (serialization): Snapshot cho phép thực hiện tuần tự hóa dữ liệu mà không ảnh hưởng đến tiến trình chính. Đây là giải pháp lý tưởng cho các thao tác lưu trữ định kỳ, tránh tình trạng dịch vụ bị treo do chi phí tính toán cao.
-
Giám sát rò rỉ bộ nhớ (memory leak): Việc so sánh các snapshot định kỳ giúp phát hiện chính xác các đối tượng tích lũy bất thường. Đây không phải ý tưởng mới - trước đây tôi đã xây dựng công cụ tương tự.
-
Gỡ lỗi an toàn: Các snapshot có thể sử dụng làm “bản sao ảo” để gỡ lỗi mà không can thiệp vào tiến trình chính, đảm bảo tính ổn định hệ thống.
Tại sao cần phân bổ riêng rẽ?
Nếu thực hiện fork toàn bộ tiến trình Skynet để tạo snapshot, việc các VM chia sẻ cùng một pool bộ nhớ sẽ khiến việc unmap (gỡ bỏ) các vùng nhớ không cần thiết trở nên bất khả thi. Cơ chế Copy-on-Write (COW) của hệ thống khiến chi phí bộ nhớ tăng nhanh khi tiến trình con hoạt động kéo dài. May mắn thay, Lua hỗ trợ tùy biến bộ phân bổ trực tiếp, khiến việc triển khai khả thi với thời gian phát triển ngắn ngủi (chỉ mất nửa ngày).
Quy trình kiểm thử nghiêm ngặt
Để kiểm tra, tôi thêm logging vào các hàm phân bổ hiện có, ghi lại toàn bộ thao tác phân bổ/phát hành bộ nhớ của hàng trăm VM vào file tạm. Quy trình này tạo ra hàng triệu log từ vài nghìn đến vài chục triệu dòng, đủ để mô phỏng chính xác quy trình phân bổ thực tế. Kỹ thuật “kết đệm” (padding) với string đặc biệt và kiểm tra byte-to-byte khi phát hành giúp phát hiện hầu hết lỗi tiềm ẩn, miễn là mẫu kiểm thử đủ đa dạng.
Hiệu năng ấn tượng
Kết quả so sánh với các giải pháp phổ biến rất khả quan (dữ liệu kiểm thử 3 triệu dòng):
|
|
Do không cần xử lý đồng thời (thread-safe), bộ phân bổ tùy biến nhanh hơn jemalloc và glibc malloc 30-40%. Tuy nhiên, vấn đề phân mảnh (fragmentation) vẫn tồn tại do chính sách quản lý ba cấp độ:
- Cấp nhỏ (<256B): Danh sách liên kết tái sử dụng các object nhỏ
- Cấp trung (27-32KB): Phân trang 32KB, sử dụng hàng đợi vòng đôi chiều
- Cấp lớn (>32KB): Gọi trực tiếp mmap (hiếm gặp trong thực tế)
Giải pháp tiềm năng cho phân mảnh
Tôi đã thử nghiệm thuật toán bạn bè (buddy system) để quản lý cấp trung, nhưng gặp phải hai thách thức lớn:
-
Vấn đề cookie quản lý: Với đơn vị phân bổ tối thiểu 256B, việc thêm 8B cookie quản lý khiến yêu cầu 512B phải sử dụng block 1024B. Giải pháp tiềm năng là tạo vùng đệm 64KB khi mmap, sau đó unmap phần dư thừa để đảm bảo căn chỉnh 32KB.
-
Phân mảnh nội bộ: Lua thường yêu cầu các kích thước không phải lũy thừa 2, gây lãng phí lên đến 50%. Điều này cũng xảy ra với jemalloc, nhưng trở nên nghiêm trọng hơn khi hàng nghìn VM độc lập sử dụng pool riêng.
Triển vọng tương lai
Hiện tôi đang hoàn thiện phiên bản buddy system với một số cải tiến, đồng thời cân nhắc các giải pháp khác như hợp nhất các trang bộ nhớ định kỳ. Việc kiểm thử với hàng nghìn VM sẽ giúp đánh giá chính xác hiệu quả thực tế. Nếu có tiến triển mới, tôi sẽ tiếp tục chia sẻ trong các bài viết tiếp theo.