Tối Ưu Hóa Bộ Nhớ Thông Qua Cơ Chế Cache Bảng Cấu Hình Trong Lua - nói dối e blog

Tối Ưu Hóa Bộ Nhớ Thông Qua Cơ Chế Cache Bảng Cấu Hình Trong Lua

Trong quá trình phát triển prototype game mô phỏng kiểu Factorio, tôi nhận ra vấn đề then chốt là làm thế nào để sử dụng bộ nhớ một cách hiệu quả khi triển khai trên thiết bị di động có giới hạn tài nguyên. Khi chơi Factorio với nhiều mod mở rộng, tôi quan sát thấy lượng RAM tiêu thụ thường vượt quá 10GB - phần lớn là dữ liệu logic chứ không phải tài nguyên đồ họa. Chỉ tính riêng các mod phức tạp đã có thể chứa hàng trăm nghìn đến hàng triệu đối tượng game.

Với mục tiêu đưa logic tương tự xuống nền tảng di động, tôi đặt mục tiêu giới hạn bộ nhớ logic dưới 500MB. Để đạt được điều này, ngoài việc thiết kế gameplay hợp lý, cần áp dụng các kỹ thuật tối ưu hóa bộ nhớ tinh vi. Giải pháp ECS (Entity-Component-System) trong C tỏ ra cực kỳ hiệu quả nhờ cách tiếp cận dữ liệu hướng cache.

Kiến trúc ECS và lợi thế bộ nhớ

Trong mô hình ECS, mỗi Entity chỉ lưu trữ dữ liệu cần thiết phải duy trì lâu dài. Nhiều giá trị chỉ dùng tạm thời trong quá trình tính toán từng tick có thể được xử lý thông minh. Ví dụ, công suất thực tế của máy phát điện có thể tính toán dựa trên trạng thái hệ thống tại từng tick, thay vì lưu trữ vĩnh viễn như cách tiếp cận OO truyền thống.

Cơ chế ECS cho phép tập trung các thuộc tính tạm thời vào vùng nhớ chung, có thể tái sử dụng sau khi hoàn thành từng giai đoạn xử lý. Điều này giúp tiết kiệm hàng gigabyte bộ nhớ so với cách làm cũ. Ngoài ra, việc thay thế con trỏ 8 byte (trên hệ thống 64-bit) bằng ID định danh 2-4 byte giúp giảm tải đáng kể khi quản lý hàng triệu đối tượng.

Quản lý quan hệ dữ liệu thông minh

Một ưu điểm nổi bật của ECS là khả năng loại bỏ mối quan hệ hai chiều gây lãng phí. Khi A và B có liên kết, thay vì giữ reference lẫn nhau như mô hình OO, ta có thể xây dựng quan hệ tạm thời khi xử lý tập hợp A, sau đó dọn dẹp ngay khi xong việc. Cách tiếp cận này không chỉ tiết kiệm bộ nhớ mà còn hạn chế lỗi logic do

Vấn đề truy cập dữ liệu Lua-C

Thách thức thực tế phát sinh khi xử lý hệ thống điện năng - nơi dữ liệu thiết bị được lưu trữ hiệu quả trong bộ nhớ C, còn cấu hình bản mẫu (prototype) lại nằm trong Lua để tiện hiệu chỉnh. Với 100,000 thiết bị điện, việc truy xuất lặp lại cấu hình từ Lua qua C sẽ gây nghẽn cổ chai hiệu năng nghiêm trọng.

Giải pháp Cache một chiều

Tôi đã thiết kế module cache đơn giản nhưng hiệu quả với các đặc điểm nổi bật:

  • Cố định kích thước: Sử dụng bảng băm tĩnh 8191 slot, không cần cấp phát heap
  • Hỗ trợ 4 kiểu dữ liệu: int, float, boolean, string
  • Hash function tối ưu: Sử dụng hằng số Mersenne prime để giảm va chạm
  • Cơ chế tái sử dụng thông minh: Khi cache đầy, ưu tiên thay thế bản ghi ít được truy cập

Cấu trúc cache được định nghĩa như sau:

1
2
3
4
5
6
7
struct prototype_cache {
  struct prototype_lua lua;  // Thông tin Lua VM
  struct prototype_field *f; // Danh sách field cần cache
  int stride;                // Kích thước record
  void *data;                // Vùng nhớ lưu dữ liệu
  type_t p[CACHE_SIZE];      // Bảng trạng thái slot
};

Triển khai thực tế

Với hệ thống máy phát nhiệt điện, chỉ cần 3 tham số: công suất, hiệu suất và độ ưu tiên. Ta định nghĩa cấu trúc:

1
2
3
4
5
struct burner_prototype {
  float power;
  float efficiency;
  int priority;
};

Cùng bảng khai báo field tương ứng:

1
2
3
4
5
6
static struct prototype_field burner_s[] = {
  { "power", PROTOTYPE_FLOAT },
  { "efficiency", PROTOTYPE_FLOAT },
  { "priority", PROTOTYPE_INT },
  { NULL, 0 },
};

Quy trình khởi tạo và sử dụng:

1
2
3
4
5
6
7
8
9
struct prototype_cache burner;
struct burner_prototype buffer[CACHE_SIZE];

burner.f = burner_s;
burner.stride = sizeof(struct burner_prototype);
burner.data = buffer;

// Đọc cấu hình bản mẫu
struct burner_prototype *b = prototype_read(&burner, prototype_id);

Hiệu quả đạt được

Giải pháp này giúp:

  • Giảm 90% số lần gọi API Lua-C
  • Giữ nguyên tính linh hoạt của cấu hình Lua
  • Đảm bảo tốc độ truy xuất O(1) cho dữ liệu thường dùng
  • Dễ dàng mở rộng cho các hệ thống khác

Module cache này đã chứng minh hiệu quả trong việc cân bằng giữa hiệu năng và tính maintainable của code, đặc biệt phù hợp với các hệ thống game có lượng đối tượng lớn nhưng cần thường xuyên điều chỉnh cấu hình.

0%