Cách cải thiện hiệu suất cho các chức năng đắt tiền trong trình xây dựng thành phố 2d


9

Tôi đã tìm kiếm câu trả lời nhưng tôi không thể tìm ra cách tiếp cận tốt nhất để xử lý các hàm / tính toán đắt tiền.

Trong trò chơi hiện tại của tôi (tòa nhà thành phố dựa trên gạch 2d), người dùng có thể đặt các tòa nhà, xây dựng đường, v.v ... Tất cả các tòa nhà cần có kết nối với một ngã ba mà người dùng phải đặt ở biên giới của bản đồ. Nếu tòa nhà không được kết nối với ngã ba này, biển báo "Không được kết nối với đường" sẽ bật lên phía trên tòa nhà bị ảnh hưởng (nếu không phải xóa). Hầu hết các tòa nhà đều có bán kính và cũng có thể liên quan đến nhau (ví dụ: sở cứu hỏa có thể giúp tất cả các ngôi nhà trong bán kính 30 gạch). Đó là những gì tôi cũng cần cập nhật / kiểm tra khi kết nối đường bộ thay đổi.

Hôm qua tôi gặp phải một vấn đề hiệu suất lớn. Hãy xem xét kịch bản sau đây: Tất nhiên người dùng cũng có thể xóa các tòa nhà và đường. Vì vậy, nếu bây giờ người dùng ngắt kết nối ngay sau ngã ba, tôi cần cập nhật nhiều tòa nhà cùng một lúc . Tôi nghĩ một trong những lời khuyên đầu tiên là tránh các vòng lặp lồng nhau (đó chắc chắn là một lý do lớn trong kịch bản này) nhưng tôi phải kiểm tra ...

  1. nếu một tòa nhà vẫn được kết nối với ngã ba trong trường hợp gạch đường đã bị gỡ bỏ (tôi chỉ làm điều đó đối với các tòa nhà bị ảnh hưởng bởi con đường đó). (Có thể là một vấn đề nhỏ hơn trong kịch bản này)
  2. danh sách các ô bán kính và nhận các tòa nhà trong bán kính (các vòng lặp lồng nhau - vấn đề lớn!) .

    // Go through all buildings affected by erasing this road tile.
    foreach(var affectedBuilding in affectedBuildings) {
        // Get buildings within radius.
        foreach(var radiusTile in affectedBuilding.RadiusTiles) {
            // Get all buildings on Map within this radius (which is technially another foreach).
            var buildingsInRadius = TileMap.Buildings.Where(b => b.TileIndex == radiusTile.TileIndex);  
    
            // Do stuff.
        }
    }
    

Tất cả điều này phá vỡ FPS của tôi từ 60 đến gần 10 trong một giây.

Tôi cũng có thể làm được. Ý tưởng của tôi sẽ là:

  • Không sử dụng luồng chính (Chức năng cập nhật) cho luồng này mà là luồng khác. Tôi có thể gặp vấn đề về khóa khi tôi bắt đầu sử dụng đa luồng.
  • Sử dụng hàng đợi để xử lý nhiều tính toán (cách tiếp cận tốt nhất trong trường hợp này là gì?)
  • Giữ thêm thông tin trong các đối tượng của tôi (các tòa nhà) để tránh tính toán nhiều hơn (ví dụ: các tòa nhà trong bán kính).

Sử dụng cách tiếp cận cuối cùng, tôi có thể loại bỏ một lồng trong mẫu foreach này thay thế:

// Go through all buildings affected by erasing this road tile.
foreach(var affectedBuilding in affectedBuildings) {
    // Go through buildings within radius.
    foreach(var buildingInRadius in affectedBuilding.BuildingsInRadius) {
        // Do stuff.
    }
}

Nhưng tôi không biết nếu điều này là đủ. Các trò chơi như City Skylines phải xử lý nhiều tòa nhà hơn nếu người chơi có bản đồ lớn. Làm thế nào để họ xử lý những điều đó?! Có thể có một hàng đợi cập nhật vì không phải tất cả các tòa nhà đều cập nhật cùng một lúc.

Tôi đang mong chờ ý kiến ​​và ý kiến ​​của bạn!

Cảm ơn rất nhiều!


2
Sử dụng một trình lược tả sẽ giúp xác định bit nào của mã có vấn đề. Nó có thể là cách bạn tìm thấy các tòa nhà bị ảnh hưởng hoặc có thể // làm công cụ. Một lưu ý phụ là các trò chơi lớn như City Skylines giải quyết các vấn đề này bằng cách sử dụng các cấu trúc dữ liệu không gian như cây bốn lá, vì vậy tất cả các truy vấn không gian nhanh hơn nhiều so với đi qua một mảng với vòng lặp for. Trong trường hợp của bạn, ví dụ, bạn có thể có một biểu đồ phụ thuộc của tất cả các tòa nhà và bằng cách theo biểu đồ đó có thể biết ngay điều gì ảnh hưởng đến những gì mà không lặp lại.
Exaila

Cảm ơn về thông tin chi tiết. Tôi thích ý tưởng của sự phụ thuộc! Tôi sẽ có một cái nhìn vào đó!
Yheeky

Lời khuyên của bạn thật tuyệt! Tôi chỉ sử dụng trình hồ sơ VS cho tôi thấy rằng tôi có chức năng tìm đường cho mỗi tòa nhà bị ảnh hưởng để kiểm tra xem kết nối đường nối có còn hợp lệ không. Tất nhiên là những thứ đắt đỏ như địa ngục! Nó chỉ có khoảng 5 FPS nhưng tốt hơn là không có gì. Tôi sẽ loại bỏ điều đó và chỉ định các tòa nhà cho các ô đường để tôi không cần phải thực hiện kiểm tra tìm đường này nhiều lần. Cảm ơn rất nhiều! Không, tôi chỉ cần sửa các tòa nhà trong vấn đề bán kính là cái lớn hơn.
Yheeky

Tôi rất vui vì bạn thấy nó hữu ích: D
Exaila

Câu trả lời:


3

Bảo hiểm tòa nhà bộ nhớ đệm

Ý tưởng lưu trữ thông tin mà các tòa nhà nằm trong phạm vi của tòa nhà hiệu ứng (mà bạn có thể lưu trữ bộ đệm từ bộ lọc hoặc trong bộ phận bị ảnh hưởng) chắc chắn là một ý tưởng tốt. Các tòa nhà (thường) không di chuyển, vì vậy có rất ít lý do để làm lại các tính toán đắt tiền này. "Tòa nhà này ảnh hưởng gì" và "điều gì ảnh hưởng đến tòa nhà này" là điều bạn chỉ cần kiểm tra khi tòa nhà được tạo hoặc xóa.

Đây là một trao đổi cổ điển của chu kỳ CPU cho bộ nhớ.

Xử lý thông tin bảo hiểm theo vùng

Nếu hóa ra bạn đang sử dụng quá nhiều bộ nhớ để theo dõi thông tin này, hãy xem liệu bạn có thể xử lý thông tin đó theo các vùng bản đồ hay không. Chia bản đồ của bạn thành các vùng vuông của n*ngạch lát. Nếu một khu vực được bao phủ hoàn toàn bởi một sở cứu hỏa, tất cả các tòa nhà trong khu vực đó cũng được bảo hiểm. Vì vậy, bạn chỉ cần lưu trữ thông tin bảo hiểm theo khu vực, không phải theo từng tòa nhà. Nếu một khu vực chỉ được bảo hiểm một phần, bạn cần quay lại xử lý các kết nối bằng cách xây dựng trong khu vực đó. Vì vậy, chức năng cập nhật cho các tòa nhà của bạn trước tiên sẽ kiểm tra "Khu vực tòa nhà này có được bao phủ bởi sở cứu hỏa không?" và nếu không "Tòa nhà này có được bảo vệ bởi một sở cứu hỏa không?". Điều này cũng tăng tốc độ cập nhật, bởi vì khi một sở cứu hỏa được gỡ bỏ, bạn không còn cần phải cập nhật trạng thái bảo hiểm của 2000 tòa nhà, bạn chỉ cần cập nhật 100 tòa nhà và 25 khu vực.

Trì hoãn cập nhật

Một tối ưu hóa khác bạn có thể làm là không cập nhật mọi thứ ngay lập tức và không cập nhật mọi thứ cùng một lúc.

Việc một tòa nhà có còn được kết nối với mạng lưới đường hay không không phải là thứ bạn cần kiểm tra từng khung hình (Nhân tiện, bạn cũng có thể tìm thấy một số cách để tối ưu hóa điều này một cách cụ thể bằng cách xem xét một chút về lý thuyết đồ thị). Sẽ hoàn toàn đủ nếu các tòa nhà chỉ kiểm tra định kỳ cứ sau vài giây sau khi tòa nhà được xây dựng (VÀ nếu có thay đổi mạng lưới đường bộ). Điều tương tự áp dụng cho các hiệu ứng phạm vi xây dựng. Hoàn toàn chấp nhận được nếu một tòa nhà chỉ kiểm tra cứ sau vài trăm khung "Có ít nhất một trong những sở cứu hỏa ảnh hưởng đến tôi vẫn hoạt động không?"

Vì vậy, bạn có thể có vòng lặp cập nhật chỉ thực hiện các tính toán đắt tiền này cho vài trăm tòa nhà tại mỗi lần cập nhật. Bạn có thể muốn ưu tiên cho các tòa nhà hiện đang ở trên màn hình, vì vậy người chơi sẽ nhận được phản hồi ngay lập tức cho hành động của họ.

Về đa luồng

Các nhà xây dựng thành phố có xu hướng ở phía đắt hơn về mặt tính toán, đặc biệt nếu bạn muốn cho phép người chơi xây dựng thực sự lớn và nếu bạn muốn có độ phức tạp mô phỏng cao. Vì vậy, về lâu dài có thể không sai khi nghĩ về những tính toán trong trò chơi của bạn có thể được xử lý không đồng bộ.


Điều này giải thích tại sao SimCity trên SNES mất một thời gian để cấp điện lại / kết nối, tôi đoán nó cũng xảy ra với các hiệu ứng trên toàn khu vực khác.
lozzajp

Cảm ơn bình luận hữu ích của bạn! Tôi cũng nghĩ rằng việc nắm giữ nhiều thông tin hơn trong bộ nhớ có thể tăng tốc trò chơi của tôi. Tôi cũng thích ý tưởng chia Ngói bản đồ thành các khu vực nhưng tôi không biết cách tiếp cận này có đủ tốt để thoát khỏi vấn đề ban đầu của tôi từ lâu không. Tôi là một câu hỏi liên quan đến việc cập nhật chậm trễ. Giả sử tôi có một chức năng khiến FPS của tôi giảm từ 60 xuống 45. Cách tiếp cận tốt nhất để phân chia các tính toán để xử lý số lượng hoàn hảo mà CPU có thể xử lý?
Yheeky

@Yheeky Không có giải pháp áp dụng chung cho việc này, bởi vì nó phụ thuộc nhiều vào tình huống mà bạn có thể trì hoãn tính toán, mà bạn không thể và đó là đơn vị tính toán hợp lý.
Philipp

Cách tôi cố gắng trì hoãn các tính toán này là tạo ra một hàng đợi với các mục có cờ "Hiện tại cập nhật". Chỉ mục này có cờ này được đặt thành đúng được xử lý. Khi tính toán đã hoàn thành, mục này đã bị xóa khỏi danh sách và mục tiếp theo được xử lý. Điều này nên làm việc, phải không? Nhưng loại phương pháp nào có thể được sử dụng nếu bạn biết rằng chính một phép tính sẽ làm giảm FPS của bạn?
Yheeky

1
@Yheeky Như tôi đã nói, không có giải pháp áp dụng chung. Những gì tôi thường sẽ thử (theo thứ tự đó): 1. Xem bạn có thể tối ưu hóa tính toán đó hay không bằng cách sử dụng các thuật toán và / hoặc cấu trúc dữ liệu phù hợp hơn. 2. Xem nếu bạn có thể chia nó thành các nhiệm vụ phụ, bạn có thể trì hoãn riêng lẻ. 3. Xem nếu bạn có thể làm điều đó trong một mối đe dọa riêng biệt. 4. Loại bỏ cơ chế trò chơi cần tính toán đó và xem liệu bạn có thể thay thế nó bằng một cái gì đó ít tính toán hơn không.
Philipp

3

1. Công việc trùng lặp .

Bạn affectedBuildingscó lẽ gần nhau, vì vậy các bán kính khác nhau sẽ chồng chéo lên nhau. Gắn cờ các tòa nhà cần được cập nhật, sau đó cập nhật chúng.

var toBeUpdated = new HashSet<Tiles>();
foreach(var affectedBuilding in affectedBuildings) {
    foreach(var radiusTile in affectedBuilding.RadiusTiles) {
         toBeUpdated.Add(radiusTile);

}
foreach (var tile in toBeUpdated)
{
    var buildingsInTile = TileMap.Buildings.Where(b => b.TileIndex == radiusTile.TileIndex);
    // Do stuff.
}

2. Cơ sở dữ liệu không phù hợp.

var buildingsInTile = TileMap.Buildings.Where(b => b.TileIndex == radiusTile.TileIndex);

nên rõ ràng

var buildingsInRadius = tile.Buildings;

trong đó Tòa nhà là một IEnumerablevới thời gian lặp không đổi (ví dụ a List<Building>)


Điểm tốt! Tôi đoán rằng tôi đã thử sử dụng Dấu phân biệt () trên cái đó bằng cách sử dụng MoreLINEQ nhưng tôi đồng ý rằng việc này có thể nhanh hơn kiểm tra các bản sao.
Yheeky
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.