Tối Ưu Hoá Không Gian Cho Gói Sprite Ejoy2d
Trong hệ thống ejoy2d, tôi lưu trữ thông tin cấu trúc của các sprite trong một cấu trúc gọi là “gói sprite”. Cấu trúc này bao gồm dữ liệu frame của hoạt ảnh, các thành phần tạo nên sprite, ma trận biến đổi của từng phần, mã hoá và toạ độ texture tương ứng… Những dữ liệu này thường không chiếm quá lớn, vì vậy tôi đề xuất giải pháp nạp toàn bộ vào bộ nhớ một lần và không xóa chúng ra khỏi RAM. Các đối tượng sprite được tạo động sẽ tham chiếu trực tiếp đến các dữ liệu này mà không cần đếm số lần tham chiếu. Việc tham chiếu chéo giữa các dữ liệu (tương tự như lắp ghép lego, dùng nhiều mảnh ghép nhỏ để tạo thành sprite phức tạp) cũng không cần ghi nhận thêm thông tin.
Trong dự án Trang Viên Xúc Động của chúng tôi, lượng dữ liệu này đã chiếm đến hàng chục MB bộ nhớ. Vài ngày trước, một đồng nghiệp đề cập vấn đề này, tôi quyết định thực hiện một số tối ưu hoá đơn giản và bất ngờ tiết kiệm được hàng chục megabyte.
Giải pháp thực ra rất đơn giản: Trên nền tảng 64bit, tôi chuyển toàn bộ con trỏ trong cấu trúc gói sprite thành giá trị offset 32bit tương đối so với địa chỉ đầu gói. Ngay từ đầu, tôi đã thiết kế gói sprite nằm gọn trong một khối bộ nhớ liên tục. Để thực hiện điều này, tôi xây dựng một bộ cấp phát “bump allocator” đơn giản: Khi đóng gói tài nguyên, chúng tôi thống kê tổng lượng bộ nhớ cần thiết, sau đó cấp phát toàn bộ khối nhớ này tại thời điểm tải gói. Các đối tượng nhỏ được lưu trữ kế tiếp nhau, giúp tiết kiệm overhead của header các khối nhớ nhỏ, đồng thời loại bỏ hoàn toàn tình trạng phân mảnh bộ nhớ vốn gặp phải ở các phương pháp quản lý bộ nhớ thông thường.
Vì toàn bộ dữ liệu đã được lưu liên tục, việc thay thế con trỏ bằng offset không yêu cầu sửa đổi nhiều mã nguồn. Khi giảm kích thước con trỏ xuống còn một nửa, cấu trúc dữ liệu đồng thời trở nên gọn gàng hơn do hiệu ứng alignment. Ba dự án trong công ty thực hiện so sánh thực tế đều ghi nhận việc tối ưu này giúp giảm khoảng 40% dung lượng bộ nhớ sử dụng cho gói sprite.
Việc sửa đổi này còn mang lại một lợi ích bất ngờ: Gói sprite hoàn toàn không chứa con trỏ, giúp khối dữ liệu trở nên độc lập với địa chỉ, có thể di chuyển tự do trong bộ nhớ. Chúng tôi có thể trực tiếp dump khối nhớ của gói sprite ra file khi đóng gói, và khi tải lên có thể bỏ qua quá trình import. Điều này hứa hẹn tốc độ tải nhanh hơn đáng kể (chưa kiểm tra thực tế). Ngay cả khi hiệu năng không cải thiện nhiều, quá trình tải tài nguyên sẽ tỷ lệ thuận với kích thước file, giúp dự đoán chính xác thời gian tải thông qua dung lượng tài nguyên. Tính toán theo cách này cho phép chúng tôi xây dựng thanh loading đều đặn và chính xác. Đây chắc chắn là một cải tiến đáng kể cho trải nghiệm người dùng.