Tôi nên tính toán như thế nào khi xây dựng trò chơi với Unity?


15

* Theo như tôi biết, Unity3D cho iOS dựa trên thời gian chạy Mono và Mono chỉ có dấu hiệu thế hệ & quét GC.

Hệ thống GC này không thể tránh được thời gian GC dừng hệ thống trò chơi. Tập hợp sơ thẩm có thể làm giảm điều này nhưng không hoàn toàn, bởi vì chúng tôi không thể kiểm soát việc khởi tạo xảy ra trong thư viện lớp cơ sở của CLR. Những trường hợp nhỏ và thường xuyên ẩn này sẽ tăng thời gian GC không xác định cuối cùng. Buộc hoàn thành GC theo định kỳ sẽ làm giảm hiệu suất rất nhiều (thực sự Mono có thể hoàn thành GC không?)

Vì vậy, làm thế nào tôi có thể tránh được thời gian GC này khi sử dụng Unity3D mà không làm giảm hiệu suất lớn?


3
Unity Pro Profiler mới nhất bao gồm lượng rác được tạo ra trong các lệnh gọi chức năng nhất định. Bạn có thể sử dụng công cụ này để giúp xem các địa điểm khác có thể tạo rác mà có thể không rõ ràng ngay lập tức.
Tết

Câu trả lời:


18

May mắn thay, như bạn đã chỉ ra, các bản dựng COMPACT Mono sử dụng một thế hệ GC (trái ngược hoàn toàn với các Microsoft, như WinMo / WinPhone / XBox, người chỉ duy trì một danh sách phẳng).

Nếu trò chơi của bạn đơn giản thì GC sẽ xử lý tốt, nhưng đây là một số gợi ý bạn có thể muốn xem xét.

Tối ưu hóa sớm

Trước tiên hãy chắc chắn rằng đây thực sự là một vấn đề cho bạn trước khi cố gắng khắc phục nó.

Pooling Các loại tài liệu tham khảo đắt tiền

Bạn nên gộp các kiểu tham chiếu mà bạn tạo thường xuyên hoặc có cấu trúc sâu. Một ví dụ về mỗi cái sẽ là:

  • Tạo thường xuyên: Một Bulletđối tượng trong một trò chơi địa ngục .
  • Cấu trúc sâu: Cây quyết định cho việc triển khai AI.

Bạn nên sử dụng Stacknhư một nhóm của bạn (không giống như hầu hết các triển khai sử dụng a Queue). Lý do cho điều này là vì với mộtStack nếu bạn trả lại một vật thể cho nhóm và một cái gì đó ngay lập tức lấy nó; nó sẽ có cơ hội cao hơn trong một trang hoạt động - hoặc thậm chí trong bộ đệm CPU nếu bạn may mắn. Nó chỉ nhanh hơn một chút thôi. Hơn nữa, luôn luôn giới hạn kích thước nhóm của bạn (chỉ cần bỏ qua 'checkins' nếu giới hạn của bạn đã bị vượt quá).

Tránh tạo danh sách mới để xóa chúng

Đừng tạo một cái mới Listkhi bạn thực sự có ý nghĩa với Clear()nó. Bạn có thể sử dụng lại mảng phụ trợ và lưu một lượng phân bổ mảng và bản sao. Tương tự như vậy, hãy thử và tạo danh sách với công suất ban đầu có ý nghĩa (hãy nhớ rằng đây không phải là giới hạn - chỉ là công suất bắt đầu) - không cần chính xác, chỉ là ước tính. Điều này nên áp dụng cho cơ bản bất kỳ loại bộ sưu tập nào - ngoại trừ a LinkedList.

Sử dụng Mảng cấu trúc (hoặc danh sách) khi có thể

Bạn có được ít lợi ích từ việc sử dụng các cấu trúc (hoặc các loại giá trị nói chung) nếu bạn chuyển chúng xung quanh giữa các đối tượng. Ví dụ, trong hầu hết các hệ thống hạt 'tốt', các hạt riêng lẻ được lưu trữ trong một mảng lớn: mảng và chỉ mục được truyền xung quanh thay vì chính hạt đó. Lý do điều này hoạt động rất tốt là vì khi GC cần thu thập mảng, nó có thể bỏ qua toàn bộ nội dung (đó là mảng nguyên thủy - không có gì để làm ở đây). Vì vậy, thay vì nhìn vào 10 000 đối tượng, GC chỉ cần nhìn vào 1 mảng: mức tăng khổng lồ! Một lần nữa, điều này sẽ chỉ làm việc với các loại giá trị .

Sau RoyT. cung cấp một số phản hồi khả thi và mang tính xây dựng Tôi cảm thấy tôi cần mở rộng thêm về điều này. Bạn chỉ nên sử dụng kỹ thuật này khi bạn đang xử lý một lượng lớn thực thể (hàng ngàn đến hàng chục nghìn). Ngoài ra, nó phải là một cấu trúc không được có bất kỳ trường loại tham chiếu nào và phải sống trong một mảng được gõ rõ ràng. Trái với phản hồi của anh ấy, chúng tôi đang đặt nó trong một mảng rất có khả năng là một trường trong một lớp - có nghĩa là nó sẽ tăng lên đống (chúng tôi không cố gắng tránh phân bổ heap - chỉ đơn thuần là tránh công việc của GC). Chúng tôi thực sự quan tâm đến thực tế rằng đó là một đoạn bộ nhớ liền kề với rất nhiều giá trị mà GC có thể chỉ cần nhìn vào trong một O(1)hoạt động thay vì một O(n)hoạt động.

Bạn cũng nên phân bổ các mảng này càng gần với khởi động ứng dụng của bạn càng tốt để giảm thiểu khả năng xảy ra sự phân mảnh hoặc làm việc quá mức khi GC cố gắng di chuyển các khối này xung quanh, (và xem xét sử dụng danh sách được liên kết lai thay vì Listloại tích hợp ).

GC.Collect ()

Đây chắc chắn là cách TỐT NHẤT để tự bắn vào chân mình (xem: "Cân nhắc về hiệu suất") với một thế hệ GC. Bạn chỉ nên gọi nó khi bạn đã tạo ra một lượng rác EXTREME - và một trường hợp có thể là vấn đề chỉ sau khi bạn đã tải nội dung cho một cấp độ - và thậm chí sau đó bạn có thể chỉ nên thu thập thế hệ đầu tiên ( GC.Collect(0);) để hy vọng ngăn chặn việc thúc đẩy các đối tượng đến thế hệ thứ ba.

IDis Dùng một lần và Nulling

Nó đáng giá cho các trường null khi bạn không còn cần một đối tượng (hơn nữa đối với các đối tượng bị ràng buộc). Lý do là trong các chi tiết về cách thức hoạt động của GC: nó chỉ loại bỏ các đối tượng không được root (tức là được tham chiếu) ngay cả khi đối tượng đó đã không được điều khiển vì các đối tượng khác bị xóa trong bộ sưu tập hiện tại ( lưu ý: điều này phụ thuộc vào GC hương vị trong sử dụng - một số thực sự làm sạch chuỗi). Ngoài ra, nếu một đối tượng sống sót trong một bộ sưu tập, nó sẽ ngay lập tức được thăng cấp cho thế hệ tiếp theo - điều này có nghĩa là bất kỳ đối tượng nào nằm xung quanh trong các trường sẽ được thăng cấp trong một bộ sưu tập. Mỗi thế hệ kế tiếp đắt hơn theo cấp số nhân (và xảy ra không thường xuyên).

Lấy ví dụ sau:

MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// G1 Collection
MyNestObject (G2) -> MyFurtherNestedObject (G2)
// G2 Collection
MyFurtherNestedObject (G3)

Nếu MyFurtherNestedObjectcó chứa một đối tượng nhiều megabyte, bạn có thể được đảm bảo rằng GC sẽ không nhìn vào nó trong một thời gian khá dài - bởi vì bạn đã vô tình quảng cáo nó lên G3. Tương phản với ví dụ này:

MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// Dispose
MyObject (G1)
MyNestedObject (G1)
MyFurtherNestedObject (G1)
// G1 Collection

Mẫu Disposeer giúp bạn thiết lập một cách có thể dự đoán để yêu cầu các đối tượng xóa các trường riêng của chúng. Ví dụ:

public class MyClass : IDisposable
{
    private MyNestedType _nested;

    // A finalizer is only needed IF YOU CONTROL UNMANAGED RESOURCES
    // ~MyClass() { }

    public void Dispose()
    {
       _nested = null;
    }
}

1
WTF bạn đang về? .NET Framework trên Windows hoàn toàn là thế hệ. Lớp của họ thậm chí có phương thức GetGeneration .
DeadMG

2
.NET trên Windows sử dụng Trình thu gom rác thế hệ, tuy nhiên .NET Compact Framework như được sử dụng trên WP7 và Xbox360 có một GC hạn chế hơn, có lẽ nó không chỉ là một danh sách phẳng mà nó không phải là một thế hệ hoàn chỉnh.
Roy T.

Jonathan Dickinson: Tôi muốn thêm rằng các cấu trúc không phải lúc nào cũng là câu trả lời. Trong .Net và có lẽ cũng là Mono, một cấu trúc không nhất thiết phải sống trên ngăn xếp (điều này là tốt) nhưng cũng có thể sống trên đống (đó là nơi bạn cần Trình thu gom rác). Các quy tắc cho điều này khá phức tạp nhưng nói chung nó xảy ra khi một cấu trúc có chứa các loại không có giá trị.
Roy T.

1
@RoyT. xem bình luận ở trên. Tôi đã chỉ ra rằng các cấu trúc là tốt cho một lượng lớn dữ liệu trong một mảng - giống như một hệ thống hạt. Nếu bạn lo lắng về các cấu trúc GC trong một mảng là cách để đi; cho dữ liệu như các hạt.
Jonathan Dickinson

10
@DeadMG cũng WTF rất chưa được biết đến - vì bạn thực sự đã không đọc câu trả lời chính xác. Hãy bình tĩnh và đọc lại câu trả lời trước khi viết những bình luận ghét bỏ trong một cộng đồng thường cư xử tốt như thế này.
Jonathan Dickinson

7

Rất ít khởi tạo động xảy ra tự động bên trong thư viện cơ sở, trừ khi bạn gọi một cái gì đó yêu cầu nó. Phần lớn phân bổ bộ nhớ và phân bổ bộ nhớ đến từ mã của riêng bạn và bạn có thể kiểm soát điều đó.

Theo tôi hiểu, bạn không thể tránh hoàn toàn điều này - tất cả những gì bạn có thể làm là đảm bảo rằng bạn tái chế và gộp các đối tượng nếu có thể, Sử dụng cấu trúc thay vì các lớp, tránh hệ thống UnityGUI và thường tránh tạo các đối tượng mới trong bộ nhớ động trong thời gian khi hiệu suất quan trọng.

Bạn cũng có thể buộc thu gom rác vào những thời điểm nhất định, điều này có thể hoặc không thể giúp đỡ: xem một số đề xuất tại đây: http://unity3d.com/support/documentation/Manual/iphone-Optimizing-Scripts.html


2
Bạn nên thực sự giải thích những tác động của việc ép buộc GC. Nó chơi tàn phá với các heuristic và bố cục của GC và có thể dẫn đến các vấn đề về bộ nhớ lớn hơn nếu sử dụng không chính xác: cộng đồng nhân viên / XNA / MVPs thường coi nội dung sau tải là thời gian hợp lý duy nhất để buộc thu thập. Bạn thực sự nên tránh đề cập đến nó hoàn toàn nếu bạn chỉ dành một câu cho vấn đề này. GC.Collect()= bộ nhớ miễn phí bây giờ nhưng rất có thể vấn đề sau này.
Jonathan Dickinson

Không, bạn nên giải thích những tác động của việc ép buộc bởi vì bạn biết nhiều về nó hơn tôi. :) Tuy nhiên, hãy nhớ rằng Mono GC không nhất thiết phải giống với Microsoft GC và các rủi ro có thể không giống nhau.
Kylotan

Tuy nhiên, +1. Tôi đã tiếp tục và xây dựng một số kinh nghiệm cá nhân với GC - điều mà bạn có thể thấy thú vị.
Jonathan Dickinson
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.