Giải quyết LMK trong trò chơi Unity là một quy trình có hệ thống:

Lấy ảnh chụp nhanh bộ nhớ
Sử dụng Unity Profiler để lấy thông tin tóm tắt nhanh về bộ nhớ do Unity quản lý. Hình 2 cho thấy các lớp quản lý bộ nhớ mà Unity dùng để xử lý bộ nhớ trong trò chơi của bạn.

Bộ nhớ được quản lý
Tính năng quản lý bộ nhớ của Unity triển khai một lớp bộ nhớ được kiểm soát sử dụng một vùng nhớ heap được quản lý và một trình thu gom rác để tự động phân bổ và chỉ định bộ nhớ. Hệ thống bộ nhớ được quản lý là một môi trường tập lệnh C# dựa trên Mono hoặc IL2CPP. Lợi ích của hệ thống bộ nhớ được quản lý là hệ thống này sử dụng trình thu gom rác để tự động giải phóng các hoạt động phân bổ bộ nhớ.
Bộ nhớ không được quản lý C#
Lớp bộ nhớ C# không được quản lý cung cấp quyền truy cập vào lớp bộ nhớ gốc, cho phép kiểm soát chính xác việc phân bổ bộ nhớ trong khi sử dụng mã C#. Bạn có thể truy cập vào lớp quản lý bộ nhớ này thông qua không gian tên Unity.Collections và bằng các hàm như UnsafeUtility.Malloc và UnsafeUtility.Free.
Bộ nhớ gốc
Lõi C/C++ nội bộ của Unity sử dụng một hệ thống bộ nhớ gốc để quản lý các cảnh, tài sản, API đồ hoạ, trình điều khiển, hệ thống con và vùng đệm trình bổ trợ. Mặc dù quyền truy cập trực tiếp bị hạn chế, nhưng bạn có thể thao tác dữ liệu một cách an toàn bằng API C# của Unity và hưởng lợi từ mã gốc hiệu quả. Bộ nhớ gốc hiếm khi yêu cầu tương tác trực tiếp, nhưng bạn có thể theo dõi tác động của bộ nhớ gốc đến hiệu suất bằng Trình phân tích tài nguyên và điều chỉnh chế độ cài đặt để tối ưu hoá hiệu suất.
Bộ nhớ không được chia sẻ giữa C# và mã gốc như minh hoạ trong hình 3. Dữ liệu mà C# yêu cầu được phân bổ trong không gian bộ nhớ được quản lý mỗi khi cần.
Để mã của trò chơi được quản lý (C#) truy cập vào dữ liệu bộ nhớ gốc của công cụ, chẳng hạn như lệnh gọi đến GameObject.transform sẽ thực hiện một lệnh gọi gốc để truy cập vào dữ liệu bộ nhớ trong vùng gốc, sau đó trả về các giá trị cho C# bằng Bindings. Các liên kết đảm bảo quy ước gọi thích hợp cho từng nền tảng và xử lý việc sắp xếp tự động các loại được quản lý thành các loại tương đương gốc.
Điều này chỉ xảy ra vào lần đầu tiên, vì vỏ được quản lý để truy cập vào thuộc tính transform được giữ lại trong mã gốc. Việc lưu vào bộ nhớ đệm thuộc tính biến đổi có thể giảm số lượng lệnh gọi qua lại giữa mã được quản lý và mã gốc, nhưng mức độ hữu ích của việc lưu vào bộ nhớ đệm phụ thuộc vào tần suất sử dụng thuộc tính. Ngoài ra, xin lưu ý rằng Unity không sao chép các phần của bộ nhớ gốc vào bộ nhớ được quản lý khi bạn truy cập vào các API này.

Để tìm hiểu thêm, hãy tham khảo bài viết Giới thiệu về bộ nhớ trong Unity.
Ngoài ra, việc thiết lập ngân sách bộ nhớ là rất quan trọng để đảm bảo trò chơi của bạn chạy mượt mà, đồng thời việc triển khai hệ thống báo cáo hoặc phân tích mức tiêu thụ bộ nhớ đảm bảo rằng mỗi bản phát hành mới không vượt quá ngân sách bộ nhớ. Tích hợp các bài kiểm thử Chế độ phát với quy trình tích hợp liên tục (CI) để xác minh mức tiêu thụ bộ nhớ ở những khu vực cụ thể của trò chơi là một chiến lược khác để hiểu rõ hơn.
Quản lý tài sản
Đây là phần có tác động lớn nhất và có thể hành động được của mức tiêu thụ bộ nhớ. Lập hồ sơ càng sớm càng tốt.
Mức sử dụng bộ nhớ trong các trò chơi Android có thể thay đổi đáng kể tuỳ thuộc vào loại trò chơi, số lượng và loại tài sản cũng như các chiến lược tối ưu hoá bộ nhớ. Tuy nhiên, các yếu tố thường đóng góp vào mức sử dụng bộ nhớ thường bao gồm kết cấu, lưới, tệp âm thanh, chương trình đổ bóng, ảnh động và tập lệnh.
Phát hiện các thành phần trùng lặp
Bước đầu tiên là phát hiện các thành phần được định cấu hình không đúng cách và các thành phần trùng lặp bằng trình phân tích bộ nhớ, công cụ báo cáo bản dựng hoặc Trình kiểm tra dự án.
Hoạ tiết
Phân tích khả năng hỗ trợ thiết bị của trò chơi và quyết định định dạng kết cấu phù hợp. Bạn có thể chia các gói kết cấu cho thiết bị cao cấp và thiết bị cấp thấp bằng cách sử dụng Play Asset Delivery, Addressable hoặc một quy trình thủ công hơn với AssetBundle.
Làm theo các đề xuất nổi tiếng nhất có trong bài viết Tối ưu hoá hiệu suất trò chơi di động và bài đăng thảo luận Tối ưu hoá chế độ cài đặt nhập hoạ tiết Unity. Sau đó, hãy thử các giải pháp sau:
Nén hoạ tiết bằng định dạng ASTC để giảm mức sử dụng bộ nhớ và thử nghiệm với tốc độ khối cao hơn, chẳng hạn như 8x8.
Nếu bắt buộc phải sử dụng ETC2, hãy đóng gói các hoạ tiết của bạn trong Atlas. Việc đặt nhiều hoạ tiết vào một hoạ tiết duy nhất đảm bảo hoạ tiết đó có Lũy thừa của 2 (POT), có thể giảm số lượng lệnh gọi vẽ và có thể tăng tốc độ kết xuất.
Tối ưu hoá định dạng và kích thước hoạ tiết RenderTarget. Tránh dùng hoạ tiết có độ phân giải cao khi không cần thiết. Việc sử dụng các hoạ tiết nhỏ hơn trên thiết bị di động giúp tiết kiệm bộ nhớ.
Sử dụng tính năng Đóng gói kênh kết cấu để tiết kiệm bộ nhớ kết cấu.
Lưới và mô hình
Bắt đầu bằng cách kiểm tra các chế độ cài đặt cơ bản (trang 27) và xác minh các chế độ cài đặt nhập lưới sau:
- Hợp nhất các lưới dư thừa và nhỏ hơn.
- Giảm số lượng đỉnh cho các đối tượng trong cảnh (ví dụ: các đối tượng tĩnh hoặc ở xa).
- Tạo các nhóm Cấp độ chi tiết (LOD) cho các thành phần có hình học cao.
Chất liệu và chương trình đổ bóng
- Loại bỏ các biến thể chương trình đổ bóng không dùng đến theo cách lập trình trong quá trình dựng.
- Hợp nhất các biến thể đổ bóng thường dùng thành các chương trình đổ bóng uber để tránh trùng lặp chương trình đổ bóng.
- Bật tính năng tải trình đổ bóng động để giải quyết vấn đề về mức sử dụng bộ nhớ lớn của các trình đổ bóng được tải sẵn trong VRAM/RAM. Tuy nhiên, hãy chú ý nếu quá trình biên dịch chương trình đổ bóng gây ra hiện tượng giật khung hình.
- Sử dụng tính năng tải chương trình đổ bóng động để ngăn tải tất cả các biến thể. Để biết thêm thông tin, hãy tham khảo bài đăng trên blog Cải thiện thời gian tạo chương trình đổ bóng và mức sử dụng bộ nhớ.
- Sử dụng tính năng tạo thực thể vật liệu đúng cách bằng cách tận dụng
MaterialPropertyBlocks
.
Âm thanh
Bắt đầu bằng cách kiểm tra các chế độ cài đặt cơ bản (trang 41) và xác minh các chế độ cài đặt nhập lưới sau:
- Xoá các tham chiếu
AudioClip
không dùng đến hoặc dư thừa khi sử dụng các công cụ âm thanh của bên thứ ba như FMOD hoặc Wwise. - Tải trước dữ liệu âm thanh. Tắt tính năng tải trước cho những đoạn video không cần thiết ngay trong thời gian chạy hoặc khi khởi động cảnh. Điều này giúp giảm mức sử dụng bộ nhớ trong quá trình khởi tạo cảnh.
Ảnh động
- Điều chỉnh chế độ cài đặt nén ảnh động của Unity để giảm thiểu số lượng khung hình chính và loại bỏ dữ liệu dư thừa.
- Giảm khung hình chính: Tự động xoá các khung hình chính không cần thiết
- Nén quaternion: Nén dữ liệu xoay để giảm mức sử dụng bộ nhớ
Bạn có thể điều chỉnh chế độ cài đặt nén trong phần Animation Import Settings (Chế độ cài đặt nhập ảnh động) trong thẻ Rig (Giàn trang bị) hoặc Animation (Ảnh động).
Sử dụng lại các đoạn ảnh động thay vì sao chép các đoạn ảnh động cho nhiều đối tượng.
Sử dụng Animator Override Controllers để dùng lại Animator Controller và thay thế các đoạn video cụ thể cho nhiều nhân vật.
Tạo ảnh động dựa trên vật lý: Nếu ảnh động của bạn được điều khiển bằng vật lý hoặc theo quy trình, hãy tạo chúng thành các đoạn ảnh động để tránh các phép tính thời gian chạy.
Tối ưu hoá giàn khung xương: Sử dụng ít xương hơn trong giàn để giảm độ phức tạp và mức tiêu thụ bộ nhớ.
- Tránh sử dụng quá nhiều xương cho các đối tượng nhỏ hoặc tĩnh.
- Nếu một số xương không được tạo hiệu ứng hoặc không cần thiết, hãy xoá chúng khỏi giàn xương.
Giảm độ dài của đoạn ảnh động.
- Cắt các đoạn hoạt ảnh để chỉ bao gồm những khung hình cần thiết. Tránh lưu trữ các ảnh động không dùng đến hoặc có thời lượng quá dài.
- Sử dụng ảnh động lặp lại thay vì tạo các đoạn video dài cho các chuyển động lặp lại.
Đảm bảo chỉ có một thành phần ảnh động được đính kèm hoặc kích hoạt. Ví dụ: vô hiệu hoá hoặc xoá các thành phần Legacy animation (Hiệu ứng chuyển động cũ) nếu bạn đang dùng Animator (Trình tạo hiệu ứng chuyển động).
Tránh sử dụng Animator nếu không cần thiết. Đối với hiệu ứng hình ảnh đơn giản, hãy sử dụng các thư viện chuyển động hoặc triển khai hiệu ứng hình ảnh trong một tập lệnh. Hệ thống trình tạo ảnh động có thể tốn nhiều tài nguyên, đặc biệt là trên các thiết bị di động cấp thấp.
Sử dụng Hệ thống công việc cho ảnh động khi xử lý một số lượng lớn ảnh động, vì hệ thống đó đã được thiết kế lại hoàn toàn để tiết kiệm bộ nhớ hơn.
Cảnh
Khi các cảnh mới được tải, chúng sẽ đưa các thành phần vào dưới dạng phần phụ thuộc. Tuy nhiên, nếu không có hoạt động quản lý vòng đời thành phần thích hợp, các bộ đếm tham chiếu sẽ không giám sát những phần phụ thuộc này. Do đó, các thành phần có thể vẫn còn trong bộ nhớ ngay cả sau khi các cảnh không dùng đến đã được gỡ bỏ, gây ra tình trạng phân mảnh bộ nhớ.
- Sử dụng Tính năng nhóm đối tượng của Unity để dùng lại các thực thể GameObject cho các phần tử trò chơi định kỳ vì tính năng nhóm đối tượng sử dụng một ngăn xếp để giữ một tập hợp các thực thể đối tượng để dùng lại và không an toàn cho luồng. Việc giảm thiểu
Instantiate
vàDestroy
giúp cải thiện cả hiệu suất CPU và độ ổn định của bộ nhớ. - Đang huỷ tải thành phần:
- Chiến lược dỡ tải tài sản trong những thời điểm ít quan trọng hơn, chẳng hạn như màn hình chờ hoặc màn hình tải.
- Việc thường xuyên sử dụng
Resources.UnloadUnusedAssets
sẽ khiến quá trình xử lý CPU tăng đột biến do các thao tác giám sát mức độ phụ thuộc nội bộ lớn. - Kiểm tra các mức tăng đột biến lớn về CPU trong điểm đánh dấu hồ sơ GC.MarkDependencies.
Xoá hoặc giảm tần suất thực thi của nó và thay vào đó, hãy huỷ tải các tài nguyên cụ thể theo cách thủ công bằng cách sử dụng Resources.UnloadAsset thay vì dựa vào
Resources.UnloadUnusedAssets()
bao gồm tất cả.
- Tái cấu trúc các cảnh thay vì liên tục sử dụng Resources.UnloadUnusedAssets.
- Việc gọi
Resources.UnloadUnusedAssets()
choAddressables
có thể vô tình huỷ tải các gói được tải động. Quản lý cẩn thận vòng đời của các tài sản được tải động.
Khác
Phân mảnh do chuyển đổi cảnh – Khi phương thức
Resources.UnloadUnusedAssets()
được gọi, Unity sẽ thực hiện những việc sau:- Giải phóng bộ nhớ cho những thành phần không còn được sử dụng
- Chạy một thao tác tương tự như trình thu gom rác để kiểm tra heap đối tượng được quản lý và đối tượng gốc để tìm các thành phần không dùng đến và huỷ tải các thành phần đó
- Dọn dẹp bộ nhớ kết cấu, lưới và tài sản, miễn là không có tệp đối chiếu đang hoạt động
AssetBundle
hoặcAddressable
– việc thực hiện các thay đổi trong lĩnh vực này rất phức tạp và đòi hỏi nỗ lực tập thể của nhóm để triển khai các chiến lược. Tuy nhiên, sau khi nắm vững các chiến lược này, bạn sẽ cải thiện đáng kể mức sử dụng bộ nhớ, giảm kích thước tải xuống và giảm chi phí trên đám mây. Để biết thêm thông tin về cách quản lý tài sản trong Unity, hãy xem phầnAddressables
.Các phần phụ thuộc dùng chung tập trung &mdash: Nhóm các phần phụ thuộc dùng chung (chẳng hạn như chương trình đổ bóng, hoạ tiết và phông chữ) một cách có hệ thống thành các gói hoặc nhóm
Addressable
chuyên dụng. Điều này giúp giảm tình trạng trùng lặp và đảm bảo các thành phần không cần thiết được gỡ một cách hiệu quả.Sử dụng
Addressables
để theo dõi phần phụ thuộc – Addressables giúp đơn giản hoá quá trình tải và dỡ có thể tự động dỡ các phần phụ thuộc không còn được tham chiếu. Việc chuyển sangAddressables
để quản lý nội dung và giải quyết các phần phụ thuộc có thể là một giải pháp khả thi, tuỳ thuộc vào trường hợp cụ thể của trò chơi. Phân tích các chuỗi phần phụ thuộc bằng công cụ Phân tích để xác định các phần phụ thuộc hoặc bản sao không cần thiết. Ngoài ra, hãy tham khảo Unity Data Tools nếu bạn đang sử dụng AssetBundle.TypeTrees
– nếuAddressables
vàAssetBundles
của trò chơi được tạo và triển khai bằng cùng một phiên bản Unity với trình phát và không yêu cầu khả năng tương thích ngược với các bản dựng trình phát khác, hãy cân nhắc tắt tính năng ghiTypeTree
. Việc này sẽ giúp giảm kích thước gói và mức sử dụng bộ nhớ của đối tượng tệp được chuyển đổi tuần tự. Sửa đổi quy trình tạo bản dựng trong chế độ cài đặt gói Addressables (Có thể định địa chỉ) cục bộ ContentBuildFlags thành DisableWriteTypeTree.
Viết mã thân thiện với trình thu gom rác
Unity sử dụng thu gom rác (GC) để quản lý bộ nhớ bằng cách tự động xác định và giải phóng bộ nhớ không dùng đến. Mặc dù GC là cần thiết, nhưng nếu không được xử lý đúng cách, GC có thể gây ra các vấn đề về hiệu suất (ví dụ: tốc độ khung hình tăng đột biến), vì quy trình này có thể tạm dừng trò chơi trong giây lát, dẫn đến các vấn đề về hiệu suất và trải nghiệm người dùng không tối ưu.
Tham khảo sách hướng dẫn của Unity để biết các kỹ thuật hữu ích về việc giảm tần suất phân bổ vùng nhớ heap được quản lý và UnityPerformanceTuningBible, trang 271 để xem các ví dụ.
Giảm số lượng phân bổ của trình thu gom rác:
- Tránh LINQ, lambda và bao đóng, vì chúng phân bổ bộ nhớ heap.
- Sử dụng
StringBuilder
cho các chuỗi có thể thay đổi thay vì nối chuỗi. - Sử dụng lại các tập hợp bằng cách gọi
COLLECTIONS.Clear()
thay vì khởi tạo lại chúng.
Bạn có thể xem thêm thông tin trong sách điện tử Hướng dẫn cơ bản về cách phân tích tài nguyên cho trò chơi Unity.
Quản lý các bản cập nhật canvas giao diện người dùng:
- Các thay đổi linh hoạt đối với thành phần trên giao diện người dùng – Khi các thành phần trên giao diện người dùng như thuộc tính Văn bản, Hình ảnh hoặc
RectTransform
được cập nhật (ví dụ: thay đổi nội dung văn bản, đổi kích thước thành phần hoặc tạo hiệu ứng cho vị trí), công cụ có thể phân bổ bộ nhớ cho các đối tượng tạm thời. - Phân bổ chuỗi – Các phần tử trên giao diện người dùng như Text thường yêu cầu cập nhật chuỗi, vì chuỗi là bất biến trong hầu hết các ngôn ngữ lập trình.
- Canvas chưa hoàn chỉnh – Khi có thay đổi trên canvas (ví dụ: thay đổi kích thước, bật và tắt các phần tử hoặc sửa đổi thuộc tính bố cục), toàn bộ canvas hoặc một phần của canvas có thể được đánh dấu là chưa hoàn chỉnh và được tạo lại. Điều này có thể kích hoạt việc tạo các cấu trúc dữ liệu tạm thời (ví dụ: dữ liệu lưới, vùng đệm đỉnh hoặc tính toán bố cục), làm tăng lượng rác được tạo.
- Cập nhật phức tạp hoặc thường xuyên – Nếu canvas có nhiều phần tử hoặc được cập nhật thường xuyên (ví dụ: mỗi khung hình), thì những lần dựng lại này có thể dẫn đến tình trạng sử dụng bộ nhớ đáng kể.
- Các thay đổi linh hoạt đối với thành phần trên giao diện người dùng – Khi các thành phần trên giao diện người dùng như thuộc tính Văn bản, Hình ảnh hoặc
Bật tính năng GC gia tăng để giảm các mức tăng đột biến của bộ sưu tập lớn bằng cách trải rộng các hoạt động dọn dẹp việc phân bổ trên nhiều khung hình. Hồ sơ để xác minh xem lựa chọn này có cải thiện hiệu suất và dấu vết bộ nhớ của trò chơi hay không.
Nếu trò chơi của bạn yêu cầu một phương pháp có kiểm soát, hãy đặt chế độ thu gom rác thành thủ công. Sau đó, khi thay đổi cấp độ hoặc vào một thời điểm khác mà không có hoạt động chơi trò chơi, hãy gọi quy trình thu gom rác.
Gọi các lệnh gọi thu thập rác theo cách thủ công GC.Collect() cho các quá trình chuyển đổi trạng thái trò chơi (ví dụ: chuyển đổi cấp độ).
Tối ưu hoá mảng bắt đầu từ các phương pháp lập trình đơn giản và nếu cần, bằng cách sử dụng mảng gốc hoặc các vùng chứa gốc khác cho mảng lớn.
Giám sát các đối tượng được quản lý bằng các công cụ như Trình phân tích bộ nhớ Unity để theo dõi các tham chiếu đối tượng không được quản lý vẫn tồn tại sau khi bị huỷ.
Sử dụng Profiler Marker (Điểm đánh dấu trình phân tích tài nguyên) để gửi đến Công cụ báo cáo hiệu suất theo phương pháp tự động.
Tránh rò rỉ và phân mảnh bộ nhớ
Rò rỉ bộ nhớ
Trong mã C#, khi một tham chiếu đến Đối tượng Unity tồn tại sau khi đối tượng bị huỷ, đối tượng trình bao bọc được quản lý (còn gọi là Managed Shell) vẫn nằm trong bộ nhớ. Bộ nhớ gốc được liên kết với tham chiếu sẽ được giải phóng khi cảnh được huỷ tải hoặc khi GameObject mà bộ nhớ được đính kèm hoặc bất kỳ đối tượng gốc nào của bộ nhớ bị huỷ thông qua phương thức Destroy()
. Tuy nhiên, nếu các tham chiếu khác đến Cảnh hoặc GameObject không bị xoá, thì bộ nhớ được quản lý có thể vẫn tồn tại dưới dạng một Đối tượng Vỏ bị rò rỉ. Để biết thêm thông tin chi tiết về Đối tượng Managed Shell, hãy tham khảo hướng dẫn về Đối tượng Managed Shell.
Ngoài ra, rò rỉ bộ nhớ có thể là do các lượt đăng ký sự kiện, lambda và đóng, chuỗi nối và việc quản lý không đúng cách các đối tượng được gộp:
- Để bắt đầu, hãy xem phần Tìm rò rỉ bộ nhớ để so sánh các ảnh chụp nhanh bộ nhớ Unity một cách thích hợp.
- Kiểm tra các đăng ký sự kiện và rò rỉ bộ nhớ. Nếu các đối tượng đăng ký sự kiện (ví dụ: bằng cách uỷ quyền hoặc UnityEvents) nhưng không huỷ đăng ký đúng cách trước khi bị huỷ, thì trình quản lý sự kiện hoặc nhà xuất bản có thể giữ lại các tham chiếu đến những đối tượng đó. Điều này ngăn các đối tượng đó bị thu gom rác, dẫn đến tình trạng rò rỉ bộ nhớ.
- Giám sát các sự kiện lớp singleton hoặc toàn cục không được huỷ đăng ký khi huỷ đối tượng. Ví dụ: huỷ đăng ký hoặc huỷ liên kết các uỷ quyền trong hàm huỷ đối tượng.
- Đảm bảo việc huỷ các đối tượng được gộp hoàn toàn vô hiệu hoá các tham chiếu đến thành phần lưới văn bản, kết cấu và GameObject gốc.
- Xin lưu ý rằng khi so sánh các ảnh chụp nhanh của Unity Memory Profiler và quan sát thấy sự khác biệt về mức tiêu thụ bộ nhớ mà không có lý do rõ ràng, thì sự khác biệt đó có thể là do trình điều khiển đồ hoạ hoặc chính hệ điều hành gây ra.
Phân mảnh bộ nhớ
Phân mảnh bộ nhớ xảy ra khi nhiều lượt phân bổ nhỏ được giải phóng theo thứ tự ngẫu nhiên. Việc phân bổ vùng nhớ đệm được thực hiện tuần tự, tức là các khối bộ nhớ mới được tạo khi khối trước đó hết dung lượng. Do đó, các đối tượng mới không lấp đầy các vùng trống của các khối cũ, dẫn đến tình trạng phân mảnh. Ngoài ra, việc phân bổ tạm thời dung lượng lớn có thể gây ra tình trạng phân mảnh vĩnh viễn trong suốt thời gian diễn ra phiên của trò chơi.
Vấn đề này đặc biệt nghiêm trọng khi các hoạt động phân bổ lớn có thời gian tồn tại ngắn được thực hiện gần các hoạt động phân bổ có thời gian tồn tại dài.
Phân bổ nhóm dựa trên thời gian tồn tại; lý tưởng nhất là các hoạt động phân bổ có thời gian tồn tại lâu dài nên được thực hiện cùng nhau, ngay từ đầu vòng đời của ứng dụng.
Người quan sát và người quản lý sự kiện
- Ngoài vấn đề được đề cập trong phần (Rò rỉ bộ nhớ)77, theo thời gian, tình trạng rò rỉ bộ nhớ có thể góp phần vào việc phân mảnh bằng cách để lại bộ nhớ không dùng đến được phân bổ cho các đối tượng không còn được sử dụng.
- Đảm bảo việc huỷ các đối tượng được gộp hoàn toàn vô hiệu hoá các tham chiếu đến thành phần lưới văn bản, hoạ tiết và
GameObjects
mẹ. - Người quản lý sự kiện thường tạo và lưu trữ danh sách hoặc từ điển để quản lý các lượt đăng ký sự kiện. Nếu các đối tượng này tăng và giảm linh hoạt trong thời gian chạy, thì chúng có thể góp phần gây ra tình trạng phân mảnh bộ nhớ do việc phân bổ và huỷ phân bổ thường xuyên.
Mã
- Đôi khi, Coroutines phân bổ bộ nhớ. Bạn có thể dễ dàng tránh điều này bằng cách lưu vào bộ nhớ đệm câu lệnh trả về của IEnumerator thay vì khai báo một câu lệnh mới mỗi lần.
- Liên tục theo dõi các trạng thái vòng đời của các đối tượng được gộp để tránh giữ lại các tham chiếu ảo
UnityEngine.Object
.
Thành phần
- Sử dụng hệ thống dự phòng linh hoạt cho trải nghiệm trò chơi dựa trên văn bản để tránh tải trước tất cả phông chữ cho các trường hợp có nhiều ngôn ngữ.
- Sắp xếp các thành phần (ví dụ: kết cấu và hạt) theo loại và vòng đời dự kiến.
- Nén các thành phần có thuộc tính vòng đời ở trạng thái chờ, chẳng hạn như hình ảnh giao diện người dùng dư thừa và lưới tĩnh.
Phân bổ dựa trên vòng đời
- Phân bổ các thành phần có thời gian tồn tại lâu dài khi bắt đầu vòng đời của ứng dụng để đảm bảo việc phân bổ nhỏ gọn.
- Sử dụng NativeCollections hoặc trình phân bổ tuỳ chỉnh cho các cấu trúc dữ liệu tạm thời hoặc sử dụng nhiều bộ nhớ (ví dụ: các cụm vật lý).
Thao tác bộ nhớ liên quan đến mã và tệp thực thi
Tệp thực thi và các trình bổ trợ của trò chơi cũng ảnh hưởng đến mức sử dụng bộ nhớ.
Siêu dữ liệu IL2CPP
IL2CPP tạo siêu dữ liệu cho mọi loại (ví dụ: lớp, kiểu chung và uỷ quyền) tại thời gian xây dựng, sau đó được dùng tại thời gian chạy để phản ánh, kiểm tra kiểu và các thao tác cụ thể khác tại thời gian chạy. Siêu dữ liệu này được lưu trữ trong bộ nhớ và có thể đóng góp đáng kể vào tổng dung lượng bộ nhớ của ứng dụng. Bộ nhớ đệm siêu dữ liệu của IL2CPP đóng góp đáng kể vào thời gian khởi động và tải. Ngoài ra, IL2CPP không loại bỏ các phần tử siêu dữ liệu trùng lặp (ví dụ: các loại chung hoặc thông tin được chuyển đổi tuần tự), điều này có thể dẫn đến việc sử dụng bộ nhớ quá mức. Điều này càng trở nên trầm trọng hơn do việc sử dụng kiểu lặp đi lặp lại hoặc dư thừa trong dự án.
Bạn có thể giảm siêu dữ liệu IL2CPP bằng cách:
- Tránh sử dụng API phản chiếu vì chúng có thể đóng góp đáng kể vào việc phân bổ siêu dữ liệu IL2CPP
- Tắt các gói tích hợp sẵn
- Triển khai tính năng chia sẻ chung hoàn toàn của Unity 2022. Tính năng này sẽ giúp giảm mức hao tổn do các thành phần chung gây ra. Tuy nhiên, để giảm thêm nữa số lượng phân bổ, hãy giảm việc sử dụng kiểu chung.
Loại bỏ mã
Ngoài việc giảm kích thước của bản dựng, tính năng xoá mã cũng làm giảm mức sử dụng bộ nhớ. Khi dựng dựa trên phần phụ trợ tập lệnh IL2CPP, tính năng loại bỏ mã byte được quản lý (được kích hoạt theo mặc định) sẽ xoá mã không dùng đến khỏi các hợp ngữ được quản lý. Quy trình này hoạt động bằng cách xác định các thành phần gốc, sau đó sử dụng tính năng phân tích mã tĩnh để xác định những mã được quản lý khác mà các thành phần gốc đó sử dụng. Mọi mã không truy cập được sẽ bị xoá. Để biết thêm thông tin về tính năng Loại bỏ mã được quản lý, hãy xem bài đăng trên blog TTales from the optimization trenches: Better managed code stripping with Unity 2020 LTS (Câu chuyện từ những chiến hào tối ưu hoá: Cải thiện tính năng loại bỏ mã được quản lý bằng Unity 2020 LTS) và tài liệu Managed code stripping (Loại bỏ mã được quản lý).
Trình phân bổ gốc
Thử nghiệm với trình phân bổ bộ nhớ gốc để tinh chỉnh trình phân bổ bộ nhớ. Nếu trò chơi sắp hết bộ nhớ, hãy sử dụng các khối bộ nhớ nhỏ hơn, ngay cả khi điều này liên quan đến các trình phân bổ chậm hơn. Hãy xem ví dụ về trình phân bổ vùng nhớ khối xếp động để tìm hiểu thêm.
Quản lý các trình bổ trợ và SDK gốc
Tìm trình bổ trợ có vấn đề – Xoá từng trình bổ trợ và so sánh các ảnh chụp nhanh bộ nhớ trò chơi. Việc này liên quan đến việc tắt nhiều chức năng mã bằng Scripting Define Symbols (Tập lệnh xác định biểu tượng) và tái cấu trúc các lớp được liên kết chặt chẽ bằng các giao diện. Hãy xem Nâng cấp mã bằng các mẫu lập trình trò chơi để tạo điều kiện thuận lợi cho quá trình vô hiệu hoá các phần phụ thuộc bên ngoài mà không khiến trò chơi của bạn không chơi được.
Liên hệ với tác giả của trình bổ trợ hoặc SDK — Hầu hết các trình bổ trợ đều không phải là mã nguồn mở.
Tái tạo mức sử dụng bộ nhớ của trình bổ trợ – Bạn có thể viết một trình bổ trợ đơn giản (sử dụng trình bổ trợ Unity này làm tài liệu tham khảo) để phân bổ bộ nhớ. Kiểm tra các ảnh chụp nhanh bộ nhớ bằng Android Studio (vì Unity không theo dõi các hoạt động phân bổ này) hoặc gọi lớp
MemoryInfo
và phương thứcRuntime.totalMemory()
trong cùng một dự án.
Một trình bổ trợ Unity sẽ phân bổ bộ nhớ gốc và bộ nhớ Java; sau đây là cách thực hiện:
Java
byte[] largeObject = new byte[1024 * 1024 * megaBytes];
list.add(largeObject);
Gốc
char* buffer = new char[megabytes * 1024 * 1024];
// Random data to fill the buffer
for (int i = 1; i < megabytes * 1024 * 1024; ++i) {
buffer[i] = 'A' + (i % 26); // Fill with letters A-Z
}