Cải thiện hàm O (N ^ 2) (tất cả các thực thể lặp lại trên tất cả các thực thể khác)


21

Một chút nền tảng, tôi đang mã hóa một trò chơi tiến hóa với một người bạn trong C ++, sử dụng ENTT cho hệ thống thực thể. Các sinh vật đi bộ xung quanh trong bản đồ 2D, ăn rau xanh hoặc các sinh vật khác, sinh sản và các đặc điểm của chúng đột biến.

Ngoài ra, hiệu suất vẫn ổn (60fps không có vấn đề gì) khi trò chơi được chạy trong thời gian thực, nhưng tôi muốn có thể tăng tốc đáng kể để không phải chờ 4h để xem bất kỳ thay đổi đáng kể nào. Vì vậy, tôi muốn có được nó càng nhanh càng tốt.

Tôi đang vật lộn để tìm một phương pháp hiệu quả cho các sinh vật tìm thức ăn của chúng. Mỗi sinh vật được cho là tìm kiếm thức ăn tốt nhất đủ gần với chúng.

Ảnh chụp màn hình ví dụ của trò chơi

Nếu nó muốn ăn, sinh vật trong hình được cho là nhìn xung quanh trong bán kính 149,64 (khoảng cách nhìn của nó) và đánh giá loại thức ăn nào nên theo đuổi, dựa trên dinh dưỡng, khoảng cách và loại (thịt hoặc thực vật) .

Chức năng chịu trách nhiệm tìm kiếm mọi sinh vật thức ăn của chúng là ăn khoảng 70% thời gian chạy. Đơn giản hóa cách viết hiện tại của nó, nó đi như thế này:

for (creature : all_creatures)
{
  for (food : all_entities_with_food_value)
  {
    // if the food is within the creatures view and it's
    // the best food found yet, it becomes the best food
  }
  // set the best food as the target for creature
  // make the creature chase it (change its state)
}

Chức năng này được chạy mỗi tích tắc cho mọi sinh vật tìm kiếm thức ăn, cho đến khi sinh vật tìm thấy thức ăn và thay đổi trạng thái. Nó cũng chạy mỗi khi thức ăn mới sinh ra cho các sinh vật đang theo đuổi một loại thức ăn nào đó, để đảm bảo mọi người đều theo đuổi thức ăn tốt nhất có sẵn cho chúng.

Tôi cởi mở với những ý tưởng về cách làm cho quá trình này hiệu quả hơn. Tôi muốn giảm sự phức tạp từ Ôi(N2) , nhưng tôi không biết liệu điều đó có khả thi hay không.

Một cách tôi đã cải thiện nó là bằng cách sắp xếp all_entities_with_food_valuenhóm để khi một sinh vật lặp lại thức ăn quá lớn để nó ăn, nó dừng lại ở đó. Bất kỳ cải tiến nào khác đều được chào đón!

EDIT: Cảm ơn tất cả các bạn đã trả lời! Tôi đã thực hiện nhiều thứ từ nhiều câu trả lời khác nhau:

Trước tiên, tôi chỉ đơn giản là làm cho nó có chức năng phạm tội chỉ chạy một lần trong năm tích tắc, điều này làm cho trò chơi nhanh hơn gấp 4 lần, trong khi không thay đổi rõ ràng bất cứ điều gì về trò chơi.

Sau đó, tôi lưu trữ trong hệ thống tìm kiếm thực phẩm một mảng với thực phẩm được sinh ra trong cùng một dấu tích mà nó chạy. Bằng cách này, tôi chỉ cần so sánh thực phẩm mà sinh vật đang đuổi theo với những thực phẩm mới xuất hiện.

Cuối cùng, sau khi nghiên cứu phân vùng không gian và xem xét BVH và quadtree, tôi đã đi với cái sau, vì tôi cảm thấy nó đơn giản hơn và phù hợp hơn với trường hợp của tôi. Tôi thực hiện nó khá nhanh và hiệu suất được cải thiện đáng kể, việc tìm kiếm thực phẩm hầu như không mất thời gian!

Bây giờ kết xuất là thứ làm tôi chậm lại, nhưng đó là vấn đề cho một ngày khác. Cảm ơn tất cả!


2
Bạn đã thử nghiệm với nhiều luồng trên nhiều lõi CPU chạy cùng lúc chưa?
Ed Marty

6
Trung bình bạn có bao nhiêu sinh vật? Nó dường như không cao đến thế, đánh giá từ ảnh chụp nhanh. Nếu luôn luôn như vậy, phân vùng không gian sẽ không giúp được nhiều. Bạn đã cân nhắc việc không chạy chức năng này ở mỗi tích tắc chưa? Bạn có thể chạy nó cứ sau 10 giây. Kết quả mô phỏng không nên thay đổi về chất.
Turms

4
Bạn đã thực hiện bất kỳ hồ sơ chi tiết để tìm ra phần tốn kém nhất của việc đánh giá thực phẩm? Thay vì nhìn vào sự phức tạp tổng thể, có lẽ bạn cần phải xem liệu có một số tính toán cụ thể hoặc truy cập cấu trúc bộ nhớ đang làm bạn nghẹt thở.
Harabeck

Một đề xuất ngây thơ: bạn có thể sử dụng cấu trúc dữ liệu tứ giác hoặc liên quan thay vì cách O (N ^ 2) mà bạn đang thực hiện ngay bây giờ.
Seiyria

3
Như @Harabeck đã đề xuất, tôi sẽ đào sâu hơn để xem nơi nào trong vòng lặp mà tất cả thời gian đó đang được sử dụng. Ví dụ, nếu tính toán căn bậc hai cho khoảng cách, bạn có thể so sánh các hợp đồng XY trước khi loại bỏ rất nhiều ứng cử viên trước khi phải thực hiện sqrt đắt tiền trên các ứng dụng còn lại. Việc thêm if (food.x>creature.x+149.64 or food.x<creature.x-149.64) continue;sẽ dễ dàng hơn việc thực hiện cấu trúc lưu trữ "phức tạp" NẾU nó đủ hiệu quả. (Liên quan: Nó có thể giúp chúng tôi nếu bạn đăng thêm một chút mã trong vòng lặp bên trong của bạn)
AC

Câu trả lời:


34

Tôi biết bạn không khái niệm đây là va chạm, tuy nhiên những gì bạn đang làm là va chạm một vòng tròn tập trung vào sinh vật, với tất cả thức ăn.

Bạn thực sự không muốn kiểm tra thực phẩm mà bạn biết là xa, chỉ có những gì gần đó. Đó là lời khuyên chung để tối ưu hóa va chạm. Tôi muốn khuyến khích tìm kiếm các kỹ thuật để tối ưu hóa các va chạm và không giới hạn bản thân trong C ++ khi tìm kiếm.


Sinh vật tìm thức ăn

Đối với kịch bản của bạn, tôi sẽ đề nghị đưa thế giới lên lưới. Tạo các ô ít nhất là bán kính của các vòng tròn bạn muốn va chạm. Sau đó, bạn có thể chọn một ô mà sinh vật được đặt và tối đa tám ô lân cận và chỉ tìm kiếm tối đa chín ô.

Lưu ý : bạn có thể tạo các ô nhỏ hơn, điều đó có nghĩa là vòng tròn bạn đang tìm kiếm sẽ vượt ra ngoài hàng xóm nhập cư, yêu cầu bạn lặp lại ở đó. Tuy nhiên, nếu vấn đề là có quá nhiều thực phẩm, các tế bào nhỏ hơn có thể có nghĩa là lặp đi lặp lại trên tổng số thực thể thực phẩm ít hơn, điều này đôi khi có lợi cho bạn. Nếu bạn nghi ngờ đây là trường hợp, kiểm tra.

Nếu thực phẩm không di chuyển, bạn có thể thêm các thực thể thực phẩm vào lưới khi tạo, để bạn không cần phải tìm kiếm những thực thể nào trong tế bào. Thay vào đó bạn truy vấn ô và nó có danh sách.

Nếu bạn làm cho kích thước của các ô có sức mạnh bằng hai, bạn có thể tìm thấy ô mà sinh vật được đặt đơn giản bằng cách cắt các tọa độ của nó.

Bạn có thể làm việc với khoảng cách bình phương (còn gọi là không làm sqrt) trong khi so sánh để tìm khoảng cách gần nhất. Hoạt động sqrt ít hơn có nghĩa là thực hiện nhanh hơn.


Thực phẩm mới được thêm vào

Khi thức ăn mới được thêm vào, chỉ những sinh vật gần đó cần được đánh thức. Đó là ý tưởng tương tự, ngoại trừ bây giờ bạn cần lấy danh sách các sinh vật trong các tế bào thay thế.

Thú vị hơn nhiều, nếu bạn chú thích trong sinh vật nó cách thức ăn của nó bao xa ... bạn có thể trực tiếp kiểm tra khoảng cách đó.

Một điều khác sẽ giúp bạn là có thức ăn nhận thức được những gì sinh vật đang đuổi theo nó. Điều đó sẽ cho phép bạn chạy mã tìm kiếm thức ăn cho tất cả các sinh vật đang đuổi theo một miếng thức ăn vừa ăn.

Trong thực tế, bắt đầu mô phỏng không có thức ăn và bất kỳ sinh vật nào có khoảng cách vô tận chú thích. Sau đó bắt đầu thêm thức ăn. Cập nhật khoảng cách khi các sinh vật di chuyển ... Khi thức ăn được ăn, hãy lấy danh sách các sinh vật đang đuổi theo nó, và sau đó tìm một mục tiêu mới. Bên cạnh trường hợp đó, tất cả các cập nhật khác được xử lý khi thực phẩm được thêm vào.


Bỏ qua mô phỏng

Biết tốc độ của một sinh vật, bạn biết nó là bao nhiêu cho đến khi nó đạt được mục tiêu. Nếu tất cả các sinh vật có cùng tốc độ, con vật sẽ đạt được đầu tiên là con có khoảng cách chú thích nhỏ nhất.

Nếu bạn cũng biết thời gian cho đến khi bạn thêm nhiều thức ăn ... Và hy vọng bạn có khả năng dự đoán tương tự cho sinh sản và cái chết, thì bạn biết thời gian tới sự kiện tiếp theo (có thể thêm thức ăn hoặc ăn một sinh vật).

Bỏ qua khoảnh khắc đó. Bạn không cần phải mô phỏng các sinh vật di chuyển xung quanh.


1
"và chỉ tìm kiếm ở đó." và các ô ngay lập tức - nghĩa là tổng cộng 9 ô. Tại sao 9? Bởi vì điều gì sẽ xảy ra nếu sinh vật ở ngay góc của một tế bào.
UKMonkey

1
@UKMonkey "Tạo các ô ít nhất là bán kính của các vòng tròn bạn muốn va chạm", nếu cạnh ô là bán kính và sinh vật ở trong góc ... tốt, tôi cho rằng bạn chỉ cần tìm kiếm bốn trong trường hợp đó. Tuy nhiên, chắc chắn, chúng ta có thể làm cho các tế bào nhỏ hơn, điều đó có thể hữu ích nếu có quá nhiều thức ăn và quá ít sinh vật. Chỉnh sửa: Tôi sẽ làm rõ.
Theraot

2
Chắc chắn - nếu bạn muốn tìm ra nếu bạn cần tìm kiếm trong các ô bổ sung ... nhưng được cho rằng hầu hết các ô sẽ không có thức ăn (từ hình ảnh đã cho); Sẽ nhanh hơn khi chỉ tìm kiếm 9 ô, hơn là tìm ra 4 ô bạn cần tìm.
UKMonkey

@UKMonkey đó là lý do tại sao tôi không đề cập đến điều đó ban đầu.
Theraot

16

Bạn nên áp dụng thuật toán phân vùng không gian như BVH để giảm độ phức tạp. Để cụ thể cho trường hợp của bạn, bạn cần tạo một cây bao gồm các hộp giới hạn theo trục có chứa các miếng thức ăn.

Để tạo cấu trúc phân cấp, hãy đặt các miếng thức ăn gần nhau trong AABB, sau đó đặt các AABB đó vào các AABB lớn hơn, một lần nữa, theo khoảng cách giữa chúng. Làm điều này cho đến khi bạn có một nút gốc.

Để sử dụng cây, trước tiên hãy thực hiện kiểm tra giao cắt AABB vòng tròn với nút gốc, sau đó nếu xảy ra va chạm, hãy kiểm tra đối với con của từng nút liên tiếp. Cuối cùng, bạn nên có một nhóm các miếng thức ăn.

Bạn cũng có thể sử dụng thư viện AABB.cc.


1
Điều đó thực sự sẽ làm giảm sự phức tạp đối với N log N, nhưng cũng sẽ tốn kém khi thực hiện phân vùng. Xem như tôi cần thực hiện phân vùng mỗi tích tắc (vì các sinh vật di chuyển mọi tích tắc) liệu nó có còn giá trị không? Có giải pháp nào giúp phân vùng ít thường xuyên hơn không?
Alexandre Coleues

3
@AlexandreRodrigues bạn không phải xây dựng lại toàn bộ cây mỗi lần đánh dấu, chỉ cập nhật các phần di chuyển và chỉ khi có thứ gì đó nằm ngoài một thùng chứa AABB cụ thể. Để cải thiện hiệu suất hơn nữa, bạn có thể muốn vỗ béo các nút (để lại một khoảng trống giữa các con) để bạn không phải xây dựng lại toàn bộ chi nhánh trên một bản cập nhật lá.
Ocelot

6
Tôi nghĩ rằng một BVH có thể quá phức tạp ở đây - một lưới thống nhất được triển khai như một bảng băm là đủ tốt.
Steven

1
@Steven bằng cách triển khai BVH, bạn có thể dễ dàng mở rộng quy mô mô phỏng trong tương lai. Và bạn không thực sự mất bất cứ điều gì nếu bạn làm điều đó cho một mô phỏng quy mô nhỏ.
Ocelot

2

Trong khi các phương pháp phân vùng không gian được mô tả thực sự có thể làm giảm thời gian vấn đề chính của bạn không chỉ là tra cứu. Đó là khối lượng tìm kiếm tuyệt vời mà bạn thực hiện khiến nhiệm vụ của bạn chậm lại. Vì vậy, tối ưu hóa vòng lặp bên trong của bạn, nhưng bạn cũng có thể tối ưu hóa vòng lặp bên ngoài.

Vấn đề của bạn là bạn tiếp tục bỏ phiếu dữ liệu. Nó giống như có những đứa trẻ ở ghế sau yêu cầu lần thứ một ngàn "chúng ta đã ở đó chưa", không cần phải làm điều đó mà tài xế sẽ thông báo khi bạn ở đó.

Thay vào đó, bạn nên cố gắng, nếu có thể, để giải quyết từng hành động để hoàn thành, hãy đặt nó vào hàng đợi và để những sự kiện bong bóng đó ra ngoài, điều này có thể thay đổi hàng đợi nhưng không sao. Điều này được gọi là mô phỏng sự kiện rời rạc. Nếu bạn có thể thực hiện mô phỏng của mình theo cách này thì bạn đang tìm kiếm một tốc độ khá lớn vượt xa tốc độ bạn có thể nhận được từ việc tra cứu phân vùng không gian tốt hơn.

Để nhấn mạnh quan điểm trong sự nghiệp trước đây, tôi đã tạo ra các mô phỏng nhà máy. Chúng tôi đã mô phỏng nhiều tuần của các nhà máy / sân bay lớn toàn bộ dòng nguyên liệu ở mỗi cấp độ vật phẩm với phương pháp này trong vòng chưa đầy một giờ. Trong khi mô phỏng dựa trên dấu thời gian chỉ có thể mô phỏng nhanh hơn 4-5 lần so với thời gian thực.

Ngoài ra, như một loại trái cây treo thực sự thấp, hãy cân nhắc việc tách rời thói quen vẽ của bạn khỏi mô phỏng của bạn. Mặc dù mô phỏng của bạn rất đơn giản, vẫn có một số chi phí vẽ. Tệ hơn nữa, trình điều khiển hiển thị có thể giới hạn bạn x cập nhật mỗi giây trong khi thực tế bộ xử lý của bạn có thể thực hiện mọi việc nhanh hơn 100 lần. Điều này cho thấy sự cần thiết cho hồ sơ.


@Theraot chúng tôi không biết làm thế nào các bản vẽ được cấu trúc. Nhưng vâng, các ngăn kéo sẽ trở thành nút cổ chai khi bạn đủ nhanh dù sao đi nữa
joojaa

1

Bạn có thể sử dụng thuật toán quét dòng để giảm độ phức tạp thành Nlog (N). Lý thuyết là các sơ đồ Voronoi, tạo ra một phân vùng của khu vực xung quanh một sinh vật thành các khu vực bao gồm tất cả các điểm gần với sinh vật đó hơn bất kỳ điểm nào khác.

Thuật toán được gọi là Fortune's thực hiện điều đó cho bạn trong Nlog (N) và trang wiki trên đó có chứa mã giả để thực hiện nó. Tôi chắc chắn rằng có những triển khai thư viện ngoài kia là tốt. https://en.wikipedia.org/wiki/Fortune%27s_alacticm


Chào mừng bạn đến với GDSE và cảm ơn bạn đã trả lời. Chính xác thì bạn sẽ áp dụng điều này vào tình huống của OP như thế nào? Mô tả vấn đề nói rằng một thực thể nên xem xét tất cả các thực phẩm trong khoảng cách xem của nó và chọn thực phẩm tốt nhất. Một Voronoi truyền thống sẽ loại trừ trong phạm vi thực phẩm gần với thực thể khác. Tôi không nói rằng Voronoi sẽ không hoạt động, nhưng không rõ ràng từ mô tả của bạn về cách OP nên sử dụng một cho vấn đề như được mô tả.
Gulalek

Tôi thích ý tưởng này, tôi muốn thấy nó được mở rộng. Làm thế nào để bạn đại diện cho sơ đồ voronoi (như trong cấu trúc dữ liệu bộ nhớ)? Làm thế nào để bạn truy vấn nó?
Theraot

@Theraot bạn không cần sơ đồ voronoi chỉ cần mở ra cùng một ý tưởng quét.
joojaa

-2

Giải pháp đơn giản nhất là tích hợp một động cơ vật lý và chỉ sử dụng thuật toán phát hiện va chạm. Chỉ cần xây dựng một vòng tròn / hình cầu xung quanh mỗi thực thể và để động cơ vật lý tính toán các va chạm. Đối với 2D tôi sẽ đề xuất Box2D hoặc ChipmunkBullet cho 3D.

Nếu bạn cảm thấy rằng việc tích hợp toàn bộ động cơ vật lý là quá nhiều, tôi khuyên bạn nên xem xét các thuật toán va chạm cụ thể. Hầu hết các thư viện phát hiện va chạm đều hoạt động theo hai bước:

  • Phát hiện pha rộng: mục tiêu của giai đoạn này là lấy danh sách các cặp đối tượng ứng cử viên có thể va chạm, càng nhanh càng tốt. Hai tùy chọn phổ biến là:
    • Quét và tỉa : sắp xếp các hộp giới hạn dọc theo trục X và đánh dấu các cặp đối tượng giao nhau. Lặp lại cho mọi trục khác. Nếu một cặp ứng cử viên vượt qua tất cả các bài kiểm tra, nó sẽ chuyển sang giai đoạn tiếp theo. Thuật toán này rất tốt trong việc khai thác sự gắn kết tạm thời: bạn có thể giữ danh sách các thực thể được sắp xếp và cập nhật chúng mọi khung hình, nhưng vì chúng gần như được sắp xếp, nó sẽ rất nhanh. Nó cũng khai thác sự kết hợp không gian: bởi vì các thực thể được sắp xếp theo thứ tự không gian tăng dần, khi bạn đang kiểm tra va chạm, bạn có thể dừng ngay khi một thực thể không va chạm, bởi vì tất cả các thực thể tiếp theo sẽ ở xa hơn.
    • Các cấu trúc dữ liệu phân vùng không gian, như quadtrees, octrees và lưới. Các lưới rất dễ thực hiện, nhưng có thể rất lãng phí nếu mật độ thực thể thấp và rất khó thực hiện đối với không gian không giới hạn. Cây không gian tĩnh cũng dễ thực hiện, nhưng khó cân bằng hoặc cập nhật tại chỗ, do đó bạn sẽ phải xây dựng lại từng khung.
  • Pha hẹp: các cặp ứng cử viên được tìm thấy trên pha rộng được thử nghiệm thêm với các thuật toán chính xác hơn.
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.