Tăng hiệu quả mô phỏng trọng lực N-Body


7

Tôi đang thực hiện một trò chơi khám phá không gian, nó sẽ có nhiều hành tinh và các vật thể khác đều có lực hấp dẫn thực tế. Tôi hiện đang có một hệ thống hoạt động, nhưng nếu số lượng hành tinh vượt quá 70, FPS sẽ giảm tốc độ theo cấp số nhân thực tế. Tôi đang làm nó trong C # và XNA.

Tôi đoán là tôi sẽ có thể thực hiện các phép tính trọng lực giữa 100 vật thể mà không cần loại căng thẳng này, vì vậy rõ ràng phương pháp của tôi không hiệu quả như mong muốn.

Tôi có hai tệp, Gravity.cs và EntityEngine.cs. Gravity quản lý CHỈ các phép tính trọng lực, EntityEngine tạo ra một thể hiện của Gravity và chạy nó, cùng với các phương thức liên quan đến thực thể khác.

EntityEngine.cs

        public void Update()
        {
            foreach (KeyValuePair<string, Entity> e in Entities)
            {
                e.Value.Update();
            }

            gravity.Update();
        }

.

Trọng lực.cs

namespace ExplorationEngine
{
    public class Gravity
    {
        private EntityEngine entityEngine;
        private Vector2 Force;
        private Vector2 VecForce;
        private float distance;
        private float mult;

        public Gravity(EntityEngine e)
        {
            entityEngine = e;
        }


        public void Update()
        {
            //First loop
            foreach (KeyValuePair<string, Entity> e in entityEngine.Entities)
            {
            //Reset the force vector
            Force = new Vector2();

                //Second loop
                foreach (KeyValuePair<string, Entity> e2 in entityEngine.Entities)
                {
                    //Make sure the second value is not the current value from the first loop
                    if (e2.Value != e.Value )
                    {
                        //Find the distance between the two objects. Because Fg = G * ((M1 * M2) / r^2), using Vector2.Distance() and then squaring it
                        //is pointless and inefficient because distance uses a sqrt, squaring the result simple cancels that sqrt.
                        distance = Vector2.DistanceSquared(e2.Value.Position, e.Value.Position);

                        //This makes sure that two planets do not attract eachother if they are touching, completely unnecessary when I add collision,
                        //For now it just makes it so that the planets are not glitchy, performance is not significantly improved by removing this IF
                        if (Math.Sqrt(distance) > (e.Value.Texture.Width / 2 + e2.Value.Texture.Width / 2))
                        {
                            //Calculate the magnitude of Fg (I'm using my own gravitational constant (G) for the sake of time (I know it's 1 at the moment, but I've been changing it)
                            mult = 1.0f * ((e.Value.Mass * e2.Value.Mass) / distance);

                            //Calculate the direction of the force, simply subtracting the positions and normalizing works, this fixes diagonal vectors
                            //from having a larger value, and basically makes VecForce a direction.
                            VecForce = e2.Value.Position - e.Value.Position;
                            VecForce.Normalize();

                            //Add the vector for each planet in the second loop to a force var.
                            Force = Vector2.Add(Force, VecForce * mult);
                            //I have tried Force += VecForce * mult, and have not noticed much of an increase in speed.
                        }
                    }
                }

                //Add that force to the first loop's planet's position (later on I'll instead add to acceleration, to account for inertia)
                e.Value.Position += Force;
            }

        }

    }
}

Tôi đã sử dụng nhiều mẹo khác nhau (về tối ưu hóa trọng lực, không phân luồng) từ câu hỏi NÀY (mà tôi đã thực hiện ngày hôm qua). Tôi đã tạo ra phương pháp trọng lực này (Gravity.Update) hiệu quả như tôi biết cách tạo ra nó. Thuật toán O (N ^ 2) này dường như vẫn ăn hết năng lượng CPU của tôi.

Đây là một LINK (ổ đĩa google, đi đến Tệp> tải xuống, giữ .Exe với thư mục nội dung, bạn sẽ cần XNA Framework 4.0 Redist. Nếu bạn chưa có nó) cho phiên bản trò chơi hiện tại của tôi. Nhấp chuột trái làm cho một hành tinh, nhấp chuột phải loại bỏ hành tinh cuối cùng. Chuột di chuyển camera, cuộn bánh xe phóng to và thu nhỏ. Xem FPS và Planet Count để biết ý tôi nói về các vấn đề hiệu suất trong 70 hành tinh. (TẤT CẢ 70 hành tinh phải di chuyển, tôi đã có 100 hành tinh đứng yên và chỉ có 5 hành tinh di chuyển trong khi vẫn có 300 khung hình / giây, vấn đề phát sinh khi 70+ đang di chuyển xung quanh)

Sau 70 hành tinh được thực hiện, xe tăng hiệu suất theo cấp số nhân. Với <70 hành tinh, tôi nhận được 330 khung hình / giây (tôi có giới hạn ở mức 300). Ở 90 hành tinh, FPS chỉ còn khoảng 2, nhiều hơn thế và nó dao động ở mức 0 FPS. Thật kỳ lạ, khi tất cả các hành tinh đều đứng yên, FPS tăng trở lại khoảng 300, nhưng ngay khi có thứ gì đó di chuyển, nó quay trở lại với những gì nó đã xảy ra, tôi không có hệ thống nào để thực hiện điều này, nó chỉ xảy ra.

Tôi đã xem xét đa luồng, nhưng câu hỏi trước đó tôi đã hỏi đã dạy cho tôi một hoặc hai điều, và tôi thấy bây giờ đó không phải là một lựa chọn khả thi.

Thay vào đó, tôi cũng nghĩ có lẽ tôi có thể thực hiện các tính toán trên GPU của mình, mặc dù tôi không nghĩ rằng nó là cần thiết. Tôi cũng không biết làm thế nào để làm điều này, nó không phải là một khái niệm đơn giản và tôi muốn tránh nó trừ khi ai đó biết một cách đơn giản thực sự thân thiện để thực hiện nó sẽ làm việc để tính toán trọng lực cơ thể. (Tôi có một NVidia gtx 660)

Cuối cùng tôi đã xem xét sử dụng một hệ thống loại mười bốn. (Mô phỏng Barnes Hut) Tôi đã được nói (trong câu hỏi trước) rằng đây là một phương pháp tốt thường được sử dụng, và nó có vẻ hợp lý và đơn giản, tuy nhiên việc triển khai diễn ra trong đầu tôi và tôi không tìm thấy một cách tốt hướng dẫn cho C # nhưng giải thích nó theo cách tôi có thể hiểu hoặc sử dụng mã mà cuối cùng tôi có thể tìm ra.

Vì vậy, câu hỏi của tôi là: Làm thế nào tôi có thể làm cho phương pháp trọng lực của mình hiệu quả hơn, cho phép tôi sử dụng hơn 100 vật thể (tôi có thể kết xuất 1000 hành tinh với hơn 300 FPS không cần tính toán trọng lực) và nếu tôi không thể làm gì nhiều để cải thiện hiệu suất (bao gồm một số loại hệ thống tứ giác), tôi có thể sử dụng GPU của mình để thực hiện các phép tính không?



Điểm với các hành tinh di chuyển và tĩnh là thú vị. Bạn có thể vui lòng xác định những gì khác biệt chính xác giữa các loại này không?
floAr

Mặc dù O ^ 2 là xấu, nhưng 70 không phải là một trò chơi đơn giản .. Tôi tranh luận bạn lập hồ sơ mã của bạn và tìm ra nút cổ chai của bạn.
Concept3d

1
@SeanMiddleditch Nhiều loại bộ sưu tập tích hợp trong C # có các liệt kê loại giá trị không phân bổ bộ nhớ GC! Điều này bao gồm ListDictionary(và của nó KeysValuesbộ sưu tập). Chúng khá an toàn để sử dụng với foreach. ( foreachtrong C # không sử dụng IEnumerator, nó sử dụng kiểu gõ vịt để các điều tra viên có thể structvà làm việc như mong đợi).
Andrew Russell

1
@SeanMiddleditch Khá chắc chắn rằng điều này luôn luôn như vậy. Đây là trang có liên quan từ thông số kỹ thuật C # . Phiên bản 2003 (.NET 1.1), không kém. Lưu ý nơi nó nói "mẫu sưu tập" (đây là kiểu gõ vịt). Tôi sẽ ngạc nhiên nếu Mono thực hiện phần này của thông số không chính xác - ngay cả trong các phiên bản cũ. (Lưu ý: các phiên bản mới hơn của thông số kỹ thuật yêu cầu rõ ràng rằng một IDisposableloại giá trị sẽ không có hộp, thay vì chỉ ngụ ý nó.)
Andrew Russell

Câu trả lời:


6

Cách tiếp cận và thực hiện của bạn là hợp lệ (bỏ qua position += forcedòng đó ). Miễn là chỉ có một ví dụ gravity. Không có gì trong mã bạn đã cung cấp dẫn đến thời gian chạy quá O (n²). Do đó, thật hợp lý khi hy vọng giảm một nửa tốc độ khung hình ở 100 hành tinh. Đó là 100 hành tinh với tốc độ 150fps. 200 hành tinh với tốc độ 30 khung hình / giây. Vân vân.

Vì điều này không phù hợp với quan sát của bạn, một số câu hỏi phát sinh. Nổi bật nhất: Bạn có chắc chắn fps bị mất gravity.Update? Bạn đã đo nó với một hồ sơ ? Tuyên bố nào là người phạm tội? Có sự khác biệt đáng chú ý trong tiêu thụ bộ nhớ? Là người thu gom rác bận rộn? Bạn có thể kết xuất 100-200 hành tinh ở tốc độ 300fps khi không có lực hấp dẫn không?


Tôi đã cung cấp hai tối ưu hóa có thể có dưới đây, nhưng nghi ngờ @LumpN là chính xác và có một cái gì đó khác đang diễn ra.
Ken

+1 cho hồ sơ mã của bạn. Tôi hoàn toàn đồng ý với điều này, có vẻ như anh ta đang làm điều gì đó sai ..
khái niệm3

Tôi chưa bao giờ sử dụng một hồ sơ trước đây, nhưng tôi đã thử. Nó thực sự đã nói rằng khoảng 54% thời gian CPU đã chuyển sang phương pháp trọng lực. Phương thức khởi động () (tính toán trọng lực) Có vẻ như việc thực hiện tất cả các phép tính nhỏ (chủ yếu là hiệu quả) trong vòng lặp thứ hai là quá nhiều đối với nó khi nó chạy chúng 10.000 lần.
Người đưa thư

@Postman có O (N ^ 2) chắc chắn là xấu, bạn cần tìm cách khai thác thông tin không gian, tôi khuyên bạn nên dùng cây bsp và câu trả lời của Ken về việc sử dụng luật thứ 3 của newton.
concept3d

1
@Postman Đó có phải là kịch bản cho 90 hành tinh / 2fps không? Đó là những gì bạn cần để hồ sơ. Nếu đúng như vậy, thì điều lạ hơn là nó chỉ chiếm 54% thời gian của CPU.
RBarryYoung

5

Bạn có thể dễ dàng tăng gấp đôi tốc độ của mình bằng cách thêm lực hấp dẫn được tính vào CẢ HAI vật thể liên quan.

Bạn đang tính toán lực giữa A & B và sau đó tính toán lực giữa B & A, nhưng tất nhiên, đó là cùng một lực trong cả hai trường hợp. Không cần phải tính toán hai lần.

Để làm điều này, bạn sẽ cần cơ cấu lại các vòng lặp để tính toán trọng lực giữa e1 và chỉ các đối tượng SAU e1 trong danh sách thực thể của bạn.

//pseudo code
for(i=0 to num_objects){
    for(j=i+1 to num_objects){ //inner loop starts just after position in outer loop 
        f=calc_force_between_objects(i,j)
        i.forces+=f;
        j.forces+=-f; // same force, opposite direction
    }
}

3

Nếu vũ trụ của bạn được chia thành các cụm tự nhiên (ví dụ như một số hệ mặt trời) thì bạn có thể coi mỗi cụm là một cơ thể hấp dẫn duy nhất cho mục đích mô phỏng trọng lực (chỉ cần tổng hợp khối lượng của các vật thể trong cụm và thực hiện trọng số (!) trung bình vị trí của chúng để lấy tâm khối lượng của cụm). Nếu cụm bị chi phối bởi một cơ thể lớn, ví dụ như mặt trời, thì chỉ cần sử dụng nó làm trung tâm của khối lượng.

Điều này sẽ hoạt động khá chính xác cho các cơ quan không đóng cụm.

Bạn cũng có thể có các cụm trong cụm. ví dụ Sao Mộc và đó là các mặt trăng, là một cụm trong hệ mặt trời.


1

Ok tôi sẽ cung cấp cho bạn câu trả lời đơn giản ở đây. Bắt đầu với việc tạo cấu trúc không gian như một hình tứ giác. Có rất nhiều tài liệu C # về điều này trực tuyến.

Ý tưởng chính là chia thiên hà của bạn thành một lưới, bạn bắt đầu với một ô lớn và chia nó thành 4 ô có kích thước bằng nhau, v.v. Dưới đây là một bài viết về QT trong C # , nhưng vui lòng cố gắng không hiểu khái niệm và không sao chép - dán, vì vấn đề của bạn đòi hỏi phải thực hiện rất đặc biệt.

Sau khi đọc bài viết, bạn nên biết về một cơ chế cho phép các hạt đăng ký với các tế bào nếu chúng di chuyển. Vì vậy, nếu hạt A di chuyển từ ô [1,1] sang ô [2,1], thì nó phải tự hủy đăng ký trong ô đầu tiên và đăng ký chính nó trong ô thứ hai.

Với cấu trúc này, bạn có được một cây, với độ chi tiết khác nhau. Những gì bạn có thể làm bây giờ là tạo Cấp độ Chi tiết cho các điểm chung của bạn. Ở cấp độ tốt nhất (lá của cây), mỗi hạt sẽ có khối lượng và trung tâm riêng. Nếu bây giờ bạn bước lên một cấp, bạn sẽ có một nút chứa một số lá (hành tinh). Bây giờ bạn tính trọng tâm khối lượng và khối lượng cho tất cả các hành tinh được kết hợp (bằng cách c * tính toán vị trí trung bình của tất cả các hành tinh * và thêm khối lượng lên ).

Bây giờ bạn lại bước lên một cấp và có một nút chứa một số nút phụ (mà chúng ta vừa tính toán). Bây giờ bạn làm điều tương tự một lần nữa và tạo ra một trung tâm mới và khối lượng mới được thêm vào. Tiếp tục với quá trình này cho đến khi bạn đạt đến nút gốc. Bây giờ bạn sẽ có một cái cây, trong đó tất cả các hành tinh đều là lá đơn và mỗi nút chứa trung tâm trung bình và khối lượng được làm mờ của tất cả các hành tinh bên dưới nó.

Trong Cập nhật logic của bạn, giờ đây bạn đã giành được kiểm tra đối với mọi hành tinh. Bạn sẽ tính toán lực chính xác cho các hành tinh gần với vật thể (có thể cùng cấp nút). Đối với các hành tinh ở xa hơn, bạn sẽ không bước xuống cây cho đến khi bạn chạm tới những chiếc lá, thay vào đó bạn sẽ sử dụng các giá trị trung bình được tính toán trước đó để tạo ra lực và xấp xỉ .

Bất cứ khi nào một hành tinh / vật thể thay đổi tế bào cây của nó, bạn cần tính toán lại cây từ đó trở lên , nhưng nó vẫn sẽ làm giảm tính toán của bạn xuống một cách tuyệt vời.

Cố gắng thực hiện một hệ thống như thế này và chơi với các thông số. Bạn sẽ có thể tạo ra một hệ thống khá mạnh mẽ với các công cụ này.

Đây là rất nhiều văn bản, cố gắng quấn đầu của bạn xung quanh này và cảm thấy thoải mái để bình luận lại cho câu hỏi :)

Trả lời

PS2: có thể thử thay đổi if (Math.Sqrt(distance) > (e.Value.Texture.Width / 2 + e2.Value.Texture.Width / 2))

vào

if (distance > ((e.Value.Texture.Width * e.Value.Texture.Width ) / 2 + (e2.Value.Texture.Width * e2.Value.Texture.Width) / 2)). trong khi chiến thắng này là nguồn gốc của vấn đề của bạn, nó sẽ tăng tốc độ lên một chút;)


0

Vấn đề chắc chắn là thứ tự phức tạp N ^ 2. Đề nghị của tôi là kết hợp quãng tám và xấp xỉ để tránh tính toán càng nhiều càng tốt. Một số khu vực có thể sử dụng xấp xỉ là:

  • Nếu m / r ^ 2 quá thấp, hãy bỏ qua phần đóng góp là quá nhỏ.
  • Bất kỳ khối lượng nào trong một khu vực đủ nhỏ, đủ xa, có thể được coi là một khối lượng duy nhất.

Tôi chưa có một thuật toán nào, nhưng một octree sẽ cho phép bạn bỏ qua toàn bộ một nút đóng góp quá ít và cũng sẽ cho phép bạn coi một nút đủ tập trung thành một khối duy nhất mà không cần khám phá sâu hơn. "Tập trung đủ" có thể được đánh giá theo nhiều cách, nhưng một công thức đơn giản có thể chỉ là diện tích của nút chia cho khoảng cách đến nút.


0

Câu trả lời này tương tự như ý tưởng cây quad / oct, nhưng không hoàn toàn giống nhau.

Tôi nghi ngờ rằng có khả năng thu được lợi nhuận khổng lồ bằng cách đánh giá lại các vectơ lực giữa các cặp cơ thể dựa trên sự gần nhau của chúng.

Nghĩa là, mỗi cơ thể duy trì một danh sách ưu tiên các lực được áp dụng bởi các cơ quan khác. [Đây thực sự không phải là nơi lưu trữ nhiều cho vài trăm cơ thể.]

Sau khi tính toán khoảng cách, cũng tính toán vận tốc tương đối và xác định thời gian "tốt cho" lực. Ví dụ, một lực là tốt miễn là hướng của nó không thay đổi nhiều hơn 1e-5 radian hoặc khoảng cách tương đối của nó hơn 1e-5.

class Force
    {
        public Vector2 force;
        public Time    validUntil;
        public Entity  otherBody;
    };

Sau đó Update, xóa những cái đó Forcekhỏi đầu hàng đợi ưu tiên validUntilđã hết hạn (ít hơn thời gian mô phỏng hiện tại). Đối với mỗi trong số này trừ đi lực lượng hiện có trên Thực thể; tính toán lại lực với otherBody; thêm nó trở lại thực thể; tính toán validUntilvà chèn lại.

Điều này sẽ làm giảm đáng kể số lần các vật thể ở xa tính toán lại lực của chúng. Trong thực tế, bạn nên thấy rằng hai khoảng cách xa có hiệu quả tác động lực không đổi lên nhau một cách tự nhiên.

Một vấn đề thứ hai áp dụng cho các đối tượng gần đó. Sử dụng tương đương với s = ut + at^2/2:

Position oldP;
Velocity oldV;
Acceleration a;
Time dt;

Velocity newV = oldV + dt*a;
Position newApprox = oldP + dt * oldV + 0.5*dt*dt*a;

Điều này sẽ cung cấp cho bạn hành vi tốt hơn nhiều so với biểu hiện hiện tại của bạn, cho phép bạn tăng lên dt.

Khi xây dựng một mô hình trọng lực, tôi thích áp dụng các thử nghiệm khác nhau.

Đơn giản nhất là kiểm tra xem bạn có thực sự có được quỹ đạo tròn cho một thứ giống như hệ mặt trời không.

Tinh vi hơn là tính toán những thứ như tổng động lượng và tổng động lượng góc và xác minh rằng những điều này vẫn không đổi.

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.