Chia Sẻ Chuỗi Ngắn Giữa Các Máy Ảo Lua
Trong Lua, các chuỗi dưới 40 byte sẽ được nội hóa (interned) vào một bảng đặc biệt nằm trong cấu trúc global state. Điều này có nghĩa là các chuỗi ngắn giống nhau trong cùng một máy ảo Lua sẽ chỉ tồn tại duy nhất một bản sao.
Trong hệ thống Skynet, việc sử dụng hàng trăm máy ảo Lua là điều phổ biến. Các máy ảo này thường tải cùng một bộ mã nguồn Lua, dẫn đến việc tạo ra nhiều bản sao của các đối tượng Proto không cần thiết. Trước đây, mình đã tiến hành cải tiến máy ảo Lua để chia sẻ Proto giữa các máy ảo, điều này giúp tăng tốc độ khởi tạo máy ảo và giảm đáng kể việc sử dụng bộ nhớ.
Tuy nhiên, việc chia sẻ Proto mới chỉ giải quyết một phần vấn đề. Trong mã nguồn Lua, một phần lớn dung lượng thực tế đến từ các hằng chuỗi (string constants). Những hằng số này không thể chia sẻ thông qua cơ chế Proto. Giải pháp trước đây là sao chép các chuỗi hằng khi clone function, nhưng rõ ràng điều này chưa tối ưu.
Hướng tiếp cận mới: Bảng chuỗi ngắn toàn cục
Mình đã nghiên cứu một phương án mới: tạo một bảng chuỗi ngắn chung cho tất cả các máy ảo Lua. Điều này đòi hỏi giải quyết hai vấn đề chính:
- Xung đột đa luồng
- Quản lý thu gom rác (garbage collection)
Giải quyết tranh chấp đồng thời
Chọn cấu trúc bảng băm mở (open hash table) với 131072 (128K) bucket được cấp phát trước. Mỗi bucket sẽ được bảo vệ bằng một khóa đọc/ghi riêng biệt. Trong thực tế, phần lớn chuỗi ngắn được tạo ra khi tải mã nguồn (một lần duy nhất), nên tần suất ghi vào bảng là cực thấp. Kết quả thử nghiệm cho thấy hiệu năng không bị ảnh hưởng đáng kể.
Cơ chế thu gom rác thông minh
Vì các chuỗi được chia sẻ toàn cục nên không thể dùng cơ chế đếm tham chiếu thông thường. Mình đã thiết kế một hệ thống đánh dấu dựa trên phiên bản (versioning):
- Thêm một biến toàn cục
global_version
để theo dõi trạng thái chia sẻ - Khi máy ảo thực hiện GC phase mark, các chuỗi được sử dụng sẽ được gắn nhãn phiên bản hiện tại
- Các chuỗi từ Proto chia sẻ sẽ được đánh dấu “không bao giờ thu gom”
Quy trình dọn dẹp:
- Tăng
global_version
- Yêu cầu service launcher khởi động full GC trên tất cả máy ảo
- Xóa tất cả chuỗi có phiên bản nhỏ hơn
global_version - 1
Bạn có thể xem chi tiết implement tại nhánh sstring
trên GitHub. Các lợi ích chính bao gồm:
- Giảm 10-15% bộ nhớ tiêu thụ trên mỗi agent
- Tăng 20-30% tốc độ khởi tạo máy ảo
⚠️ Lưu ý: Patch này thay đổi cốt lõi của Lua, nên cần chạy
make cleanall
trước khi rebuild.
Cải tiến mới: Giới hạn thông minh (8/21 cập nhật)
Sau khi thảo luận nội bộ, mình đã phát triển cơ chế mới đơn giản hơn nhiều. Thay vì quản lý phức tạp, ta có thể:
- Cho phép chia sẻ chuỗi trong giai đoạn khởi động
- Đặt ngưỡng giới hạn bảng chia sẻ (ví dụ 500K chuỗi)
- Tự động tắt cơ chế chia sẻ khi đạt tới ngưỡng
Quy trình chi tiết tại nhánh shrtbl
:
|
|
Kết quả thử nghiệm thực tế:
- Tiết kiệm 200-700KB RAM mỗi máy ảo
- Giảm 30% thời gian khởi tạo 1000 máy ảo đồng thời
- Giảm 40% chu kỳ GC trên mỗi máy ảo
Cơ chế này đặc biệt hiệu quả với các hệ thống có chu kỳ sống dài (trên 1 tuần) vì ngăn chặn hiện tượng “bloat” do các chuỗi tạm thời tạo ra trong runtime.