Tại sao vòng lặp for hiệu quả hơn vòng lặp for cho mỗi vòng lặp trong Unity?


8

Trong một cuộc nói chuyện về Unity về hiệu suất, anh chàng đã nói với chúng tôi tránh for eachvòng lặp để tăng hiệu suất và thay vào đó hãy sử dụng vòng lặp thông thường. Sự khác biệt giữa forfor eachtrong quan điểm thực hiện là gì? Điều gì làm cho for eachkhông hiệu quả?


1
Ngoài việc đếm có bao nhiêu phần tử trong mảng hoặc bất cứ thứ gì bạn sử dụng, không nên có nhiều sự khác biệt. Với một tìm kiếm nhanh trên google, tôi đã tìm thấy điều này: stackoverflow.com/questions 43249594/iêu (Điều này cũng sẽ áp dụng cho Unity, mặc dù ngôn ngữ khác, nhưng nó vẫn áp dụng logic cơ bản tương tự)
John Hamilton

2
Tôi muốn thêm: Trong lần lặp đầu tiên của bạn không tinh chỉnh hiệu năng nhưng ưu tiên phải là kiểu kiến ​​trúc và mã sạch. Chỉ tối ưu hóa (hiệu suất) nếu cần sau này ...
Marco

2
Bạn có một liên kết đến cuộc nói chuyện đó?
Vaillancourt

2
Theo trang Unity này , for eachcác vòng lặp lặp lại trên bất kỳ thứ gì ngoài mảng tạo ra rác (phiên bản Unity trước 5.5)
Hellium

2
Tôi thấy một phiếu bầu để đóng cái này ngoài chủ đề vì nó là chương trình chung, nhưng vì nó được hỏi trong bối cảnh của Unity nói riêng và đây là một hành vi đã thay đổi giữa các phiên bản của Unity, tôi nghĩ rằng nó đáng để mở ở đây để tham khảo Nhà phát triển trò chơi thống nhất.
DMGregory

Câu trả lời:


13

Một vòng lặp foreach dường như tạo ra một đối tượng IEnumerator ra khỏi bộ sưu tập mà bạn vượt qua nó, và sau đó đi qua đó. Vì vậy, một vòng lặp như thế này:

foreach(var entry in collection)
{
    entry.doThing();
}

dịch sang một cái gì đó giống như thế này:
(gợi ra một số phức tạp xung quanh cách liệt kê được xử lý sau này)

var enumerator = collection.GetEnumerator();
while(enumerator.MoveNext()) {
   var entry = enumerator.Current;
   entry.doThing();
}

Nếu điều này được biên dịch một cách ngây thơ / trực tiếp, thì đối tượng liệt kê đó sẽ được phân bổ trên heap (mất một ít thời gian), ngay cả khi kiểu cơ bản của nó là một cấu trúc - thứ gọi là quyền anh. Sau khi hoàn thành vòng lặp, phân bổ này sẽ trở thành rác để dọn dẹp sau đó và trò chơi của bạn có thể bị trì hoãn trong giây lát nếu đủ rác chất đống để người thu gom chạy quét toàn bộ. Cuối cùng, MoveNextphương thức và thuộc Currenttính có thể bao gồm nhiều bước gọi hàm hơn là chỉ lấy một mục trực tiếp collection[i], nếu trình biên dịch không thực hiện hành động đó.

Các liên kết Hellium tài liệu Hellium ở trên chỉ ra rằng hình thức ngây thơ này chính xác là những gì xảy ra trong Unity trước phiên bản 5.5 , nếu lặp lại bất cứ điều gì khác ngoài Mảng bản địa.

Trong phiên bản 5.6+ (bao gồm cả phiên bản 2017), quyền anh không cần thiết này đã biến mất và bạn không nên trải nghiệm sự phân bổ không cần thiết nếu bạn đang sử dụng bất kỳ loại cụ thể nào (ví dụ: int[]hoặc List<GameObject>hoặc Queue<T>).

Bạn vẫn sẽ nhận được phân bổ nếu phương thức được đề cập chỉ biết rằng nó hoạt động với một loại IList hoặc giao diện khác - trong những trường hợp đó, nó không biết trình liệt kê cụ thể nào nó hoạt động để nó phải đưa nó vào đối tượng IEnumerator trước .

Vì vậy, rất có thể đây là lời khuyên cũ không quá quan trọng trong phát triển Unity hiện đại. Các nhà phát triển đoàn kết như chúng ta có thể hơi mê tín về những thứ từng là chậm / nhiều lông trong các phiên bản cũ, vì vậy hãy đưa ra bất kỳ lời khuyên nào về hình thức đó với một hạt muối. ;) Động cơ phát triển nhanh hơn niềm tin của chúng tôi về nó.

Trong thực tế, tôi chưa bao giờ có một triển lãm trò chơi chậm chạp đáng chú ý hoặc trục trặc thu gom rác từ việc sử dụng các vòng lặp foreach, nhưng số dặm của bạn có thể thay đổi. Hồ sơ trò chơi của bạn trên phần cứng đã chọn của bạn để xem liệu nó có đáng lo ngại không. Nếu bạn cần, việc thay thế các vòng lặp foreach thành các vòng lặp rõ ràng sau này trong quá trình phát triển sẽ là một vấn đề, vì vậy tôi sẽ không hy sinh tính dễ đọc và dễ mã hóa sớm trong quá trình phát triển.


Chỉ cần kiểm tra, a List<MyCustomType>IEnumerator<MyCustomType> getListEnumerator() { }ổn, trong khi một phương pháp IEnumerator getListEnumerator() { }sẽ không được.
Draco18 không còn tin tưởng vào

0

Nếu một vòng lặp được sử dụng cho bộ sưu tập hoặc mảng đối tượng (tức là mảng của tất cả các phần tử không phải là kiểu dữ liệu nguyên thủy), thì GC (Garbage Collector) được gọi vào không gian trống của biến tham chiếu ở cuối vòng lặp cho mỗi vòng lặp.

foreach (Gameobject temp in Collection)
{
    //Code Do Task
}

Trong khi đó vòng lặp được sử dụng để lặp lại các phần tử bằng cách sử dụng một chỉ mục và do đó kiểu dữ liệu nguyên thủy sẽ không ảnh hưởng đến hiệu suất so với kiểu dữ liệu không nguyên thủy.

for (int i = 0; i < Collection.length; i++){
    //Get reference using index i;
    //Code Do Task
}

Bạn có một trích dẫn cho điều này? Các tempbiến ở đây có thể được cấp phát trên stack, ngay cả khi bộ sưu tập là đầy đủ các loại tài liệu tham khảo (vì nó chỉ là một tham chiếu đến các mục hiện đã có trên đống, không phân bổ bộ nhớ heap mới để giữ một thể hiện của loại tài liệu tham khảo), do đó chúng tôi sẽ không mong đợi rác xảy ra ở đây. Bản thân điều tra viên có thể được đóng hộp trong một số trường hợp và kết thúc trên heap, nhưng những trường hợp đó cũng áp dụng cho các kiểu dữ liệu nguyên thủy.
DMGregory
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.