UI sinh rác mỗi frame: StringBuilder đã đủ?
Minh Khoa
Tác giả
Một dòng quen thuộc trong Update():
hpText.text = "HP: " + currentHp + "/" + maxHp;
Máu không đổi, nhưng chuỗi vẫn được dựng lại mỗi frame.
Theo ví dụ của Unity, 1 KB cấp phát mỗi frame ở 60 FPS tương đương khoảng 3,6 MB mỗi phút. Đây là tổng lượng cấp phát, không phải GC đợi đủ một phút mới dọn. Làm liên tục vẫn tạo thêm công việc cho bộ thu gom rác. Unity Manual.
Vậy đổi sang StringBuilder là xong?
StringBuilder giảm rác, chưa chắc hết rác
string là immutable: không thể sửa nội dung của một chuỗi đã tạo. Khi ghép nội dung thay đổi, mình thường phải tạo chuỗi kết quả mới.
StringBuilder cho phép dựng nội dung trong buffer có thể tái sử dụng. Nhưng đoạn này vẫn có vấn đề:
// _builder được tạo một lần và dùng lại.
_builder.Clear();
_builder.Append("HP: ").Append(currentHp);
hpText.text = _builder.ToString();
Với nội dung không rỗng, ToString() vẫn tạo chuỗi kết quả. Builder giúp giảm chuỗi trung gian khi ghép nhiều phần, nhưng chưa loại bỏ cấp phát ở đầu ra. Khởi tạo builder mới mỗi lần còn phát sinh thêm chi phí. Microsoft — StringBuilder.
Cũng đừng nhầm tạo chuỗi với boxing. Gọi ToString() trực tiếp trên một biến int không đồng nghĩa với boxing; boxing là chuyển giá trị sang dạng object hoặc interface tương ứng. Microsoft — Boxing.
Với TextMeshPro, đi thẳng vào dữ liệu cần hiển thị
Cho HP hoặc timer đơn giản:
hpText.SetText("HP: {0} / {1}", currentHp, maxHp);
Khác với:
hpText.SetText($"HP: {currentHp} / {maxHp}");
Dòng thứ hai đã dựng chuỗi trước khi gọi SetText, nên chỉ đổi tên hàm không giải quyết được chỗ cấp phát đó.
TMP cũng nhận trực tiếp StringBuilder:
hpText.SetText(_builder); // Không cần _builder.ToString()
Các overload này giúp tránh tạo string đầu ra. Lưu ý overload định dạng số trong TMP 3.0 nhận float; số nguyên lớn cần kiểm tra độ chính xác, không nên dùng máy móc cho tiền tệ hoặc điểm số rất lớn. TMP — SetText.