Tìm hiểu về Memory trong Unity WebGL

Hello mọi người, mình vừa hoàn thành một game 2D đơn giản trên nền WebGL vài ngày trước. Trong quá trình làm game mình đã gặp rất nhiều vấn đề và thực sự mà nói dùng Unity3D để làm game cho nền tảng WebGL thì cũng không phải là lựa chọn quá tốt(theo ý kiến cá nhân của mình là vậy). Những vấn đề mình đã gặp phải có thể kể đến là khi upload game lên Facebook thì trên máy mình chạy rất tốt, sang máy khách thì lại không chạy được, thời gian load ở vài máy khá lâu, có trình duyệt không chạy được vì chưa bật WebGL(???), và lỗi mình cảm thấy khá bất lực đó là không đủ bộ nhớ để chạy game. Mang theo khá nhiều tâm lý ăn thua + acay thì sau khi hoàn thành game mình đã quyết định tìm hiểu sâu hơn về cách Unity quản lý bộ nhớ. Chúng ta sẽ cùng tìm hiểu bài viết UNDERSTANDING MEMORY IN UNITY WEBGL của tác giả Marco Trivellato để hiểu rõ hơn về bộ nhớ trong Unity. Mọi người có thể xem bài viết gốc ở link sau: https://blogs.unity3d.com/2016/09/20/understanding-memory-in-unity-webgl/.

Unity WebGL khác với các nền tảng khác như thế nào?

Một số lập trình viên đã quen với những nền tảng có bộ nhớ rất hạn chế. Còn những lập trình viên khác, họ là những người lập trình game cho desktop hoặc WebPlayer, họ không gặp vấn đề gì cho đến hiện tại. Khi bạn làm game cho nền tảng Console thì vấn đề bộ nhớ trở nên đơn giản hơn rất nhiều vì bạn biết được chắn chắn bạn có thể sử dụng bao nhiêu bộ nhớ. Điều này cho phép bạn phân phối bộ nhớ và xây dựng nội dung trong game một cách hợp lý. Trên các nền tảng di động thì mọi thứ trở nên phức tạp hơn bởi vì có rất nhiều thiết bị khác nhau trên thị trường, nhưng bạn vẫn có thể chọn cấu hình máy thấp nhất để sản xuất game cho các thiết bị đó và không cho phép các thiết bị đời thấp hơn chạy game của bạn. Trên môi trường Web, đơn giản là bạn không thể làm điều đó. Tất cả người dùng đều có trình duyệt 64-bit và rất nhiều bộ nhớ nhưng thực tế thì không phải là như vậy. Trên hết, chúng ta không có cách nào để biết được thông số kỹ thuật của phần cứng mà game của bạn đang chạy trên đó. Chúng ta có thể biết hệ điều hành, trình duyệt và không hơn. Cuối cùng, người dùng chắc chắn sẽ mở các tab trình duyệt khác cùng với chạy game cùng một lúc nên điều này sẽ trở thành một vấn đề rất lớn.

Tổng quát về bộ nhớ trong WebGL

Dưới đây là bức tranh tổng quát về bộ nhớ khi Unity WebGL chay trên trình duyệt: Bức tranh cho thấy bên trên Unity Heap, Unity WebGL sẽ cần thêm một sự cấp phát bộ nhớ trong bộ nhớ của trình duyệt. Chúng ta cần hiểu rõ điều này để có thể làm giảm số lượng người chơi bỏ game. Như bạn có thể thấy, có vài nhóm cấp phát bộ nhớ: DOM, Unity Heap, Asset Data và Code, những bộ nhớ đã được cấp phát này sẽ được lưu trong bộ nhớ lần đầu khi trang web load xong. Trong khi đó, Asset Bundles, WebAudio và Memory File System sẽ phụ thuộc vào game của bạn(ví dụ: tải Asset Bundles, phát nhạc liên tục...). Khi trang web đang load, có vài bộ nhớ tạm thời được cấp phát trong quá trình asm.js được biên dịch và thỉnh thoảng điều này dẫn tới vấn đề hết bộ nhớ trên các trình duyệt 32-bit.

Unity Heap

nói chung, Unity Heap là bộ nhớ chứa tất cả các đối tượng cụ thể, các thành phần, ảnh, shaders... Trên WebGL, độ lớn của Unity Heap cần được biết trước để trình duyệt có thể cấp phát không gian cho nó và khi đã được cấp phát thì bộ đệm không thể cắt bớt hoặc lớn thêm. Code để quản lý việc cấp phát cho Unity Heap được viết như sau:

buffer = new ArrayBuffer(TOTAL_MEMORY);

đoạn code này có thể tìm thấy trong file build.js được sinh ra khi build project cho WebGL và sẽ được thực thi bởi máy ảo Javascript của trình duyệt. TOTAL_MEMORY được lập trình viên thiết lập trong Player Settings thuộc tính WebGL Memory Size. Giá trị mặc định là 256mb nhưng đó chỉ là một giá trị tâm linh mà chúng ta chọn. Thực tế một project trống chỉ cần 16mb. Một game được phát hành ra cho người chơi thì cần nhiều hơn thế, khoảng 256 hoặc 386mb trong đa số trường hợp. Nên nhớ rằng, bạn sử dụng càng nhiều bộ nhớ thì càng ít người dùng có thể chơi game của bạn.

Bộ nhớ Source Code và Compiled Code

Trước khi Code của bạn được chạy thì nó phải được tải về sau đó được copy vào một chỗ chứa toàn bộ code trong game sau đó được biên dịch. Trong các bước này sẽ cần thêm vài phần bộ nhớ:

  • bộ nhớ đệm dùng để download là tạm thời nhưng bộ nhớ source code và complied code thì được lưu trữ trong bộ nhớ.
  • kích thước của bộ đệm tải về và source code là kích thước của file uncompressed javascript được sinh ra bởi Unity. Để xác định được kích thước bộ nhớ cần để chạy game chúng ta cần:
    • tạo một bản release build
    • đổi tên file có đuôi jsgz và datagz thành *.gz và giải nén các file đó bằng chương trình giải nén của bạn
    • kích thước các file giải nén ra sẽ là kích thước cần trên bộ nhớ của trình duyệt.
  • kích thước của compiled code phụ thuộc vào trình duyệt một cách tối ưu là bật chế độ Strip Engine Code, như thế thì bản build của bạn sẽ không có các đoạn code mà bạn không cần.

Asset Data

Trên các nền tảng khác, một ứng dụng có thể truy cập vào các file trên ổ đĩa của bạn nhưng trên web điều này không thể xảy ra được. Do đó, các file dữ liệu được WebGL tải về sẽ được lưu trong bộ nhớ. Các file dữ liệu đó sẽ chứa các file mà Unity sinh ra: data.unity3d(tất cả các scene, resources...). Để biết chính xác tổng kích thước của file dữ liệu bạn có thể xem file data.unity3d sau khi bạn build project

Memory File System

Mặc dù không có file hệ thống thực sự nào, nhưng Unity Web GL vẫn có thể đọc và ghi ra file. Sự khác biệt chính khi so sánh với các nền tảng khác đó là mọi hành động đọc ghi file đều xảy ra trong bộ nhớ. Một điều quan trọng cần ghi nhớ đó là File hệ thống thì không nằm trong heap. Do đó, nó sẽ cần thêm bộ nhớ. Ví dụ mình ghi một mảng vào trong file:

var buffer = new byte[10*1024*1024];
File.WriteAllBytes(Application.temporaryCachePath + "/buffer.bytes", buffer);

file này sẽ được lưu vào trong bộ nhớ và bạn có thể nhìn thấy trong profiler như sau: chú ý rằng Unity Heap có kích thước 256mb. Tương tự, hệ thống cache của Unity phụ thuộc vào file hệ thống nên các file asset bundle hoặc Player Prefs cũng được lưu trong bộ nhớ.

Asset Bundles

một cách quan trọng nhất trong thực tế làm giảm sự tiêu tốn bộ nhớ trên WebGL đó là sử dụng Asset Bundles. Dựa vào cách mà chúng được sử dụng thì nó có thể ảnh hưởng đến sự tiêu tốn bộ nhớ dẫn tới game không thể chạy được trên trình duyệt 32-bit. vậy bạn biết bạn cần sử dụng asset bundles cho game của bạn, vậy bạn sẽ làm gì? đóng gói tất cả các file của bạn vào 1 asset bundle? Bạn không thể làm thế, kể cả khi điều đó giúp thời gian loading ban đầu giảm đi nhưng bạn vẫn cần phải download và điều này có thể dẫn tới hết bộ nhớ. hãy nhìn bộ nhớ trước khi Asset Bundle tải về: như bạn có thể thấy, 256mb được cấp phát cho Unity Heap. Và đây là bộ nhớ sau khi tải Asset Bundle mà không caching: những gì bạn thấy bây giờ là một bộ đệm phát sinh với kích thước bằng với kích thước của asset bundle trên ổ cứng (~65mb). Bộ nhớ đệm này được cấp phát bởi XHR, nó chỉ là bộ nhớ tạm thời nhưng nó sẽ gây ra đầy bộ nhớ ở vài thời điểm cho đến khi nó được giải phóng bởi garbage collector. Bạn nên chắc chắn rằng bạn sử dụng hàm AssetBundle.Unload khi bạn làm việc xong với asset bundle đó.

Asset Bundle Caching

Asset Bundle Caching làm việc tương tự trên những nền tảng khác, bạn chỉ cần sử dụng WWW.LoadFromCacheOrDownload. Trên Web GL Asset Bundle caching phụ thuộc vào IndexedDB để lưu dữ liệu, vấn đề là các bản ghi trong DB cũng tồn tại trong bộ nhớ. Hãy nhìn vào bộ nhớ trước khi tải AB về sử dụng LoadFromCacheOrDownload: bạn có thể thấy 512mb được sử dụng cho Unity Heap và ~4mb cho các phần còn lại. Dưới đây là bộ nhớ sau khi load AB: bộ nhớ cần thêm giờ tăng lên ~167mb, phần bộ nhớ thêm đó chúng ta cần cho AB và dưới đây là bộ nhớ sau khi garbage collector giải phóng: nó đã giảm được khá nhiều nhưng vẫn cần đến 85mb. Phần lớn bộ nhớ này được sử dụng để lưu AB trong bộ nhớ file hệ thống. Bộ nhớ đó bạn sẽ không thể lấy lại được kể cả khi bạn unload AB.

Web Audio

Unity sẽ tạo các đối tượng AudioBuffer trong javascript, như thế chúng có thể chạy được trên Web Audio. WebAudio sẽ lưu các file audio của bạn dưới dạng chưa nén nên sẽ tốn bộ nhớ hơn. Do đó, bạn sẽ cần viết plugin của bạn để lưu các file audio trong bộ nhớ dưới dạng đã nén. điều này sẽ tiết kiệm được thêm bộ nhớ cho bạn sử dụng

Tổng kết

Như vậy mình đã cùng các bạn tìm hiểu khái quát về cách quản lý bộ nhớ trên WebGL. Tối ưu hóa game luôn là vấn đề nhức nhối và cần nhiều sự cố gắng để đạt được. Hi vọng bài viết này sẽ giúp các bạn có cái nhìn rõ hơn về một phần trong việc quản lý bộ nhớ. Mình sẽ cùng các bạn tìm hiểu nhiều vấn đề khác trong các bài viết/dịch sau. Cảm ơn mọi người.


All Rights Reserved