Tính toán trọng lực đa luồng 2D


24

Tôi đang xây dựng một trò chơi khám phá không gian và hiện tại tôi đã bắt đầu làm việc về trọng lực (Trong C # với XNA).

Trọng lực vẫn cần điều chỉnh, nhưng trước khi tôi có thể làm điều đó, tôi cần giải quyết một số vấn đề về hiệu suất với các tính toán vật lý của mình.

Điều này đang sử dụng 100 đối tượng, thông thường kết xuất 1000 trong số chúng không có tính toán vật lý nào đạt được hơn 300 FPS (đó là giới hạn FPS của tôi), nhưng bất kỳ hơn 10 đối tượng nào cũng mang trò chơi (và một luồng duy nhất mà nó chạy) đến đầu gối khi làm tính toán vật lý.

Tôi đã kiểm tra việc sử dụng luồng của mình và luồng đầu tiên tự giết mình khỏi tất cả các công việc, vì vậy tôi đoán rằng tôi chỉ cần thực hiện phép tính vật lý trên một luồng khác. Tuy nhiên, khi tôi cố chạy phương thức Cập nhật của lớp Gravity.cs trên một luồng khác, ngay cả khi phương thức Cập nhật của Gravity không có gì trong đó, trò chơi vẫn giảm xuống còn 2 FPS.

Trọng lực.cs

public void Update()
    {
        foreach (KeyValuePair<string, Entity> e in entityEngine.Entities)
        {
            Vector2 Force = new Vector2();

            foreach (KeyValuePair<string, Entity> e2 in entityEngine.Entities)
            {
                if (e2.Key != e.Key)
                {
                    float distance = Vector2.Distance(entityEngine.Entities[e.Key].Position, entityEngine.Entities[e2.Key].Position);
                    if (distance > (entityEngine.Entities[e.Key].Texture.Width / 2 + entityEngine.Entities[e2.Key].Texture.Width / 2))
                    {
                        double angle = Math.Atan2(entityEngine.Entities[e2.Key].Position.Y - entityEngine.Entities[e.Key].Position.Y, entityEngine.Entities[e2.Key].Position.X - entityEngine.Entities[e.Key].Position.X);

                        float mult = 0.1f *
                            (entityEngine.Entities[e.Key].Mass * entityEngine.Entities[e2.Key].Mass) / distance * distance;

                        Vector2 VecForce = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
                        VecForce.Normalize();

                        Force = Vector2.Add(Force, VecForce * mult);
                    }
                }
            }

            entityEngine.Entities[e.Key].Position += Force;
        }

    }

Vâng, tôi biết. Đó là một vòng lặp foreach lồng nhau, nhưng tôi không biết làm thế nào khác để tính toán trọng lực, và điều này dường như hoạt động, nó chỉ chuyên sâu đến mức nó cần chủ đề riêng của nó. (Ngay cả khi ai đó biết một cách siêu hiệu quả để thực hiện các phép tính này, tôi vẫn muốn biết làm thế nào tôi CÓ THỂ làm điều đó trên nhiều luồng thay thế)

EntityEngine.cs (quản lý một thể hiện của Gravity.cs)

public class EntityEngine
{
    public Dictionary<string, Entity> Entities = new Dictionary<string, Entity>();
    public Gravity gravity;
    private Thread T;


    public EntityEngine()
    {
        gravity = new Gravity(this);
    }


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

        T = new Thread(new ThreadStart(gravity.Update));
        T.IsBackground = true;
        T.Start();
    }

}

EntityEngine được tạo trong Game1.cs và phương thức Update () của nó được gọi trong Game1.cs.

Tôi cần tính toán vật lý của mình trong Gravity.cs để chạy mỗi khi trò chơi cập nhật, trong một luồng riêng biệt để phép tính không làm chậm trò chơi xuống mức FPS khủng khiếp (0-2).

Làm thế nào tôi sẽ đi về làm cho luồng này hoạt động? (mọi đề xuất cho hệ thống Trọng lực hành tinh được cải thiện đều được hoan nghênh nếu có ai có chúng)

Tôi cũng không tìm kiếm một bài học về lý do tại sao tôi không nên sử dụng luồng hoặc sự nguy hiểm của việc sử dụng nó không chính xác, tôi đang tìm kiếm một câu trả lời thẳng về cách thực hiện. Tôi đã dành một giờ để googling câu hỏi này với rất ít kết quả mà tôi hiểu hoặc hữu ích. Tôi không có ý tỏ ra thô lỗ, nhưng dường như rất khó để một người lập trình nhận được câu trả lời có ý nghĩa thẳng, tôi thường nhận được một câu trả lời phức tạp đến mức tôi có thể dễ dàng giải quyết vấn đề của mình nếu tôi hiểu nó, hoặc ai đó nói lý do tại sao tôi không nên làm những gì tôi muốn làm và không đưa ra giải pháp thay thế nào (điều đó hữu ích).

Cảm ơn bạn vì sự giúp đỡ!

EDIT : Sau khi đọc câu trả lời tôi đã nhận được, tôi thấy rằng các bạn thực sự quan tâm và không chỉ cố gắng đưa ra một câu trả lời có thể có hiệu quả. Tôi muốn giết hai con chim bằng một viên đá (cải thiện hiệu suất và học một số điều cơ bản về đa luồng), nhưng có vẻ như hầu hết vấn đề nằm ở tính toán của tôi và việc xâu chuỗi rắc rối hơn so với việc tăng hiệu suất. Cảm ơn tất cả các bạn, tôi sẽ đọc lại câu trả lời của bạn và thử các giải pháp của bạn khi tôi hoàn thành việc học, Cảm ơn một lần nữa!


Nó [hệ thống luồng cập nhật của bạn đã nêu ở trên] làm gì bây giờ (nó có hoạt động không)? Btw Tôi sẽ bắt đầu nó càng sớm càng tốt trong chu kỳ trò chơi - ví dụ trước khi các thực thể được cập nhật.
ThorinII

2
Các cuộc gọi Trig trong phần bên trong các vòng lặp lồng nhau của bạn có lẽ là cú hích lớn nhất. Nếu bạn có thể tìm cách loại bỏ chúng, điều đó sẽ giảm bớt vấn đề knày O(n^2)rất nhiều.
RBarryYoung

1
Thật vậy, các cuộc gọi trig là hoàn toàn không cần thiết : trước tiên bạn tính một góc từ một vectơ, sau đó sử dụng nó để tạo ra một vectơ khác chỉ theo hướng đã cho. Sau đó, bạn bình thường hóa vectơ đó, nhưng vì sin² + cos² ≡ 1dù sao nó cũng đã được chuẩn hóa! Bạn chỉ có thể sử dụng vectơ gốc kết nối hai đối tượng bạn quan tâm và chuẩn hóa đối tượng này. Không có cuộc gọi trig nào cần thiết.
rẽ trái

XNA không được tán thành?
jcora

@yannbane câu hỏi đó không thêm bất cứ điều gì hữu ích cho cuộc thảo luận. Và không, trạng thái của XNA không phù hợp với bất kỳ định nghĩa về sự phản đối.
Seth Battin

Câu trả lời:


36

Những gì bạn có ở đây là một thuật toán O (n²) cổ điển . Nguyên nhân sâu xa của vấn đề của bạn không liên quan gì đến việc phân luồng và mọi thứ liên quan đến thực tế là thuật toán của bạn có độ phức tạp cao.

Nếu trước đây bạn chưa bắt gặp ký hiệu "Big O", thì về cơ bản, điều đó có nghĩa là số lượng thao tác cần thiết để làm việc trên n phần tử (đây là giải thích siêu đơn giản). 100 phần tử của bạn đang thực thi phần bên trong của vòng lặp 10000 lần .

Trong phát triển trò chơi, bạn thường muốn tránh các thuật toán O (n²) , trừ khi bạn có một lượng dữ liệu nhỏ (và tốt nhất là cố định hoặc giới hạn) và thuật toán rất nhanh.

Nếu mọi thực thể đều ảnh hưởng đến mọi thực thể khác, thì bạn sẽ cần một thuật toán O (n²). Nhưng có vẻ như chỉ có một vài thực thể thực sự tương tác (do if (distance < ...)) - vì vậy bạn có thể giảm đáng kể số lượng hoạt động của mình bằng cách sử dụng một cái gì đó gọi là " Phân vùng không gian ".

Bởi vì đây là một chủ đề khá chi tiết và hơi cụ thể về trò chơi, tôi khuyên bạn nên hỏi một câu hỏi mới để biết thêm chi tiết. Tiếp tục nào...


Một trong những vấn đề hiệu năng chính với mã của bạn là khá đơn giản. Điều này thật chậm chạp :

foreach (KeyValuePair<string, Entity> e in Entities)
{
    Entities[e.Key].Update();
}

Bạn đang thực hiện tra cứu từ điển theo chuỗi, mỗi lần lặp (nhiều lần trong các vòng lặp khác của bạn), cho một đối tượng bạn đã có!

Bạn có thể làm điều này:

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

Hoặc bạn có thể làm điều này: (Cá nhân tôi thích điều này tốt hơn, cả hai nên có cùng tốc độ)

foreach (Entity e in Entities.Values)
{
    e.Update();
}

Một tra cứu từ điển theo chuỗi là khá chậm. Lặp lại trực tiếp sẽ nhanh hơn đáng kể .

Mặc dù, tần suất bạn thực sự cần phải tìm kiếm các mục theo tên? So với tần suất bạn phải lặp đi lặp lại qua tất cả chúng? Nếu bạn chỉ hiếm khi tìm kiếm tên, hãy xem xét việc lưu trữ các thực thể của bạn trong một List(cung cấp cho họ một Namethành viên).

Mã bạn thực sự có tương đối tầm thường. Tôi chưa mô tả sơ lược về nó, nhưng tôi cá rằng phần lớn thời gian thực hiện của bạn là vào phần tra cứu từ điển lặp đi lặp lại . Mã của bạn có thể "đủ nhanh" chỉ bằng cách khắc phục vấn đề này.

EDIT: Vấn đề lớn nhất tiếp theo có lẽ là gọi Atan2và sau đó ngay lập tức chuyển đổi nó trở lại thành một vectơ với SinCos! Chỉ cần sử dụng vector trực tiếp.


Cuối cùng, hãy giải quyết luồng và các vấn đề chính trong mã của bạn:

Đầu tiên và rõ ràng nhất: Đừng tạo một chủ đề mới mỗi khung hình! Đối tượng chủ đề khá "nặng". Giải pháp đơn giản nhất cho việc này sẽ chỉ đơn giản là sử dụng ThreadPoolthay thế.

Tất nhiên, nó không đơn giản. Hãy chuyển sang vấn đề thứ hai: Đừng chạm vào dữ liệu trên hai luồng cùng một lúc! (Không thêm cơ sở hạ tầng an toàn luồng thích hợp.)

Về cơ bản, bạn đang dậm bộ nhớ ở đây theo cách kinh khủng nhất. Không có chủ đề an toàn ở đây. Bất kỳ một trong nhiều gravity.Updateluồng "" bạn đang bắt đầu đều có thể ghi đè dữ liệu đang được sử dụng trong luồng khác vào thời điểm không mong muốn. Chủ đề chính của bạn, trong khi đó, chắc chắn sẽ chạm vào tất cả các cấu trúc dữ liệu này. Tôi sẽ không ngạc nhiên nếu mã này tạo ra các vi phạm truy cập bộ nhớ khó tái tạo.

Làm cho một cái gì đó như chủ đề này an toàn là khó khăn và có thể thêm chi phí hiệu suất đáng kể để nó thường không đáng nỗ lực.


Nhưng, khi bạn hỏi (không phải vậy) độc đáo về cách thực hiện bằng mọi cách, hãy nói về điều đó ...

Thông thường tôi khuyên bạn nên bắt đầu bằng cách thực hành một cái gì đó đơn giản, trong đó chủ đề của bạn về cơ bản là "cháy và quên". Phát âm thanh, ghi một cái gì đó vào đĩa, v.v. Mọi thứ trở nên phức tạp khi bạn phải đưa kết quả trở lại vào luồng chính.

Về cơ bản có ba cách tiếp cận vấn đề của bạn:

1) Đặt khóa xung quanh tất cả dữ liệu bạn sử dụng trên các chuỗi. Trong C #, điều này được thực hiện khá đơn giản với lockcâu lệnh.

Nói chung, bạn tạo (và giữ lại!) Một cách new objectcụ thể để khóa để bảo vệ một số bộ dữ liệu (vì lý do an toàn thường chỉ xuất hiện khi viết API công khai - nhưng phong cách tốt đều giống nhau). Sau đó, bạn phải khóa đối tượng khóa của mình ở mọi nơi bạn truy cập dữ liệu mà nó bảo vệ!

Tất nhiên, nếu một cái gì đó bị "khóa" bởi một luồng bởi vì nó đang được sử dụng và một luồng khác cố gắng truy cập nó - luồng thứ hai sau đó sẽ bị buộc phải đợi cho đến khi luồng đầu tiên kết thúc. Vì vậy, trừ khi bạn cẩn thận chọn các tác vụ có thể được thực hiện song song, về cơ bản, bạn sẽ nhận được hiệu suất đơn luồng (hoặc tệ hơn).

Vì vậy, trong trường hợp của bạn, không có lý do gì để làm điều này trừ khi bạn có thể kiến ​​trúc trò chơi của mình sao cho một số mã khác chạy song song sẽ không chạm vào bộ sưu tập thực thể của bạn.

2) Sao chép dữ liệu vào chuỗi, để nó xử lý và sau đó lấy kết quả ra khi kết thúc.

Chính xác cách bạn thực hiện điều này sẽ phụ thuộc vào những gì bạn đang làm. Nhưng rõ ràng điều này sẽ liên quan đến một hoạt động sao chép có khả năng tốn kém (hoặc hai) mà trong nhiều trường hợp sẽ chậm hơn so với chỉ làm những việc đơn luồng.

Và, tất nhiên, bạn vẫn phải có một số công việc khác phải làm trong nền, nếu không, chủ đề chính của bạn sẽ chỉ ngồi chờ đợi chủ đề khác của bạn kết thúc để nó có thể sao chép dữ liệu trở lại!

3) Sử dụng cấu trúc dữ liệu an toàn luồng.

Đây là một chút chậm hơn so với các đối tác đơn luồng của họ và thường khó sử dụng hơn so với khóa đơn giản. Chúng vẫn có thể biểu hiện các vấn đề về khóa (giảm hiệu suất xuống một luồng) trừ khi bạn sử dụng chúng cẩn thận.


Cuối cùng, vì đây là mô phỏng dựa trên khung, bạn sẽ cần có luồng chính chờ các luồng khác cung cấp kết quả, để khung có thể được hiển thị và mô phỏng có thể tiếp tục. Một lời giải thích đầy đủ thực sự quá dài để đưa vào đây, nhưng về cơ bản, bạn sẽ muốn tìm hiểu cách sử dụng Monitor.WaitMonitor.Pulse. Đây là một bài viết để bắt đầu bạn .


Tôi biết tôi đã không cung cấp chi tiết triển khai cụ thể (ngoại trừ bit cuối cùng) hoặc mã cho bất kỳ phương pháp tiếp cận nào trong số này. Trước hết, sẽ có rất nhiều để trang trải. Và, thứ hai, không ai trong số chúng có thể áp dụng cho mã của riêng bạn - bạn cần tiếp cận toàn bộ kiến ​​trúc của mình với một cái nhìn hướng tới việc thêm luồng.

Việc xâu chuỗi sẽ không kỳ diệu mã bạn có ở đó nhanh hơn - nó chỉ cho phép bạn làm một việc khác cùng một lúc!


8
+10 nếu tôi có thể. Có lẽ bạn có thể chuyển câu cuối cùng lên đầu như một lời giới thiệu, vì nó tóm tắt vấn đề cốt lõi ở đây. Chạy mã trên một luồng khác không làm tăng tốc độ kết xuất một cách kỳ diệu nếu bạn không có việc gì khác để làm cùng một lúc. Và trình kết xuất có thể đợi luồng kết thúc nhưng nếu nó không (và làm sao nó biết được?) Thì nó sẽ vẽ một trạng thái trò chơi không nhất quán với một số vật lý thực thể vẫn được cập nhật.
Tìm hiểuCocos2D

Tôi hoàn toàn bị thuyết phục luồng không phải là những gì tôi cần, cảm ơn bạn cho thông tin dài và hiểu biết! Đối với các cải tiến hiệu suất, tôi đã thực hiện các thay đổi mà bạn (và những người khác) đã đề xuất, nhưng tôi vẫn nhận được hiệu suất kém khi xử lý> 60 đối tượng. Tôi nghĩ sẽ tốt nhất cho tôi khi đặt một câu hỏi khác tập trung hơn vào hiệu quả mô phỏng N-Body. Bạn nhận được câu trả lời của tôi cho điều này, mặc dù. cảm ơn!
Người đưa thư

1
Bạn được chào đón, rất vui vì nó đã giúp :) Khi bạn đăng câu hỏi mới của mình, vui lòng bỏ một liên kết ở đây để tôi và bất kỳ ai khác theo dõi sẽ thấy nó.
Andrew Russell

@Postman Mặc dù tôi đồng ý với những gì câu trả lời này nói chung, tôi nghĩ rằng nó hoàn toàn bỏ lỡ thực tế rằng về cơ bản đây là thuật toán HOÀN HẢO để tận dụng luồng. Có một lý do họ làm công cụ này trên GPU và đó là vì nó là một thuật toán song song tầm thường nếu bạn di chuyển ghi vào bước thứ hai. Không cần phải khóa hoặc sao chép hoặc xử lý các cấu trúc dữ liệu an toàn. Một Parallel.ForEach đơn giản và được thực hiện mà không có vấn đề gì.
Chewy Gumball

@ChewyGumball Một điểm rất hợp lệ! Và, trong khi Postman sẽ phải thực hiện thuật toán của mình hai pha, thì dù sao nó cũng phải là hai pha. Tuy nhiên, điều đáng để chỉ ra Parallellà không phải không có chi phí hoạt động, vì vậy đây chắc chắn là thứ cần được lập hồ sơ - đặc biệt là đối với các tập dữ liệu nhỏ như vậy và (nên là gì) một đoạn mã tương đối nhanh. Và, tất nhiên, vẫn còn tốt hơn để giảm độ phức tạp thuật toán trong trường hợp này - thay vì chỉ đơn giản là ném song song vào nó.
Andrew Russell

22

Ok thoạt nhìn có một số điều bạn nên thử. Lúc đầu, bạn nên cố gắng giảm kiểm tra va chạm, bạn có thể làm điều này bằng cách sử dụng một số loại cấu trúc không gian như một hình tứ giác . Điều này sẽ cho phép bạn giảm số lượng foreach thứ hai, vì bạn sẽ chỉ truy vấn các thực thể đóng cái đầu tiên.

Về luồng của bạn: Cố gắng không tạo một luồng mỗi lượt Cập nhật. Chi phí này có thể làm chậm tốc độ của bạn hơn là tăng tốc mọi thứ. Thay vào đó, hãy thử tạo một luồng va chạm duy nhất và để nó thực hiện công việc cho bạn. Tôi không có cách tiếp cận sao chép-dán-mã-mã cụ thể này , nhưng có những bài viết về đồng bộ hóa luồng và nhân viên nền cho C #.

Một điểm khác là trong vòng lặp foreach bạn không cần phải làm entityEngine.Entities[e.Key].Texturevì bạn đã truy cập dict trong tiêu đề foreach của bạn. Thay vào đó bạn chỉ có thể viết e.Texture. Tôi không thực sự biết về tác động của việc này, chỉ muốn cho bạn biết;)

Một điều cuối cùng: Tại thời điểm bạn đang kiểm tra hai lần mọi thực thể, bởi vì nó được truy vấn trong vòng lặp đầu tiên VÀ vòng thứ hai.

Ví dụ với 2 thực thể A và B:

pick A in first foreach loop
   pick A in second foreach loop
      skip A because keys are the same
   pick B in second foreach loop
      collision stuff
pick B in first foreach loop
   pick A in second foreach loop
      collision stuff
   pick B in second foreach loop
      skip B because keys are the same

Mặc dù đây là một cách tiếp cận khả thi, nhưng có thể bạn có thể xử lý A và B trong một lượt, bỏ qua một nửa số kiểm tra va chạm của bạn

Hy vọng điều này sẽ giúp bạn bắt đầu =)

PS: Ngay cả khi bạn nói rằng bạn không muốn nghe nó: Cố gắng giữ phát hiện va chạm trong cùng một luồng và chỉ cần tăng tốc độ đủ. Việc xâu chuỗi có vẻ như là một ý tưởng tốt nhưng đi kèm với điều này là cần phải đồng bộ hóa như địa ngục. Nếu bạn kiểm tra va chạm chậm hơn so với cập nhật của bạn (lý do cho việc xâu chuỗi nó), bạn S get gặp trục trặc và lỗi, vì va chạm sẽ kích hoạt sau khi tàu đã di chuyển và ngược lại. Tôi không muốn làm nản lòng bạn, đây chỉ là một experiene cá nhân.

EDIT1: Liên kết với hướng dẫn QuadTree (Java): http://gamedev.tutsplus.com/tutorials/imcellenceation/quick-tip-use-quadtrees-to-detect-labilities-collutions-in-2d-space/


10
Điều tuyệt vời khi sử dụng quad / octrees cho mô phỏng trọng lực là, thay vì chỉ bỏ qua các hạt ở xa, bạn có thể lưu trữ tổng khối lượng và tâm khối lượng của tất cả các hạt trong mỗi nhánh của cây và sử dụng điều này để tính hiệu ứng hấp dẫn trung bình của tất cả các hạt trong nhánh này trên các hạt khác, xa. Thuật toán này được gọi là thuật toán Barnes-Hutđó là những gì các chuyên gia sử dụng .
Ilmari Karonen

10

Thành thật mà nói, điều đầu tiên bạn nên làm là chuyển sang một thuật toán tốt hơn.

Song song với mô phỏng của bạn, ngay cả trong trường hợp tốt nhất có thể, chỉ tăng tốc theo hệ số bằng số lượng CPU × lõi trên mỗi CPU × luồng trên mỗi lõi có sẵn trên hệ thống của bạn - tức là từ 4 đến 16 cho một PC hiện đại. (Moving mã của bạn để GPU có thể mang lại nhiều ấn tượng hơn các yếu tố song song, với chi phí phức tạp phát triển thêm và tốc độ tính toán ban đầu thấp hơn cho mỗi chủ đề.) Với một thuật toán O (n ²), giống như mã ví dụ của bạn, điều này sẽ cho phép bạn sử dụng từ 2 đến 4 lần số hạt mà bạn hiện có.

Ngược lại, chuyển sang một thuật toán hiệu quả hơn có thể dễ dàng tăng tốc độ mô phỏng của bạn bằng cách, hệ số từ 100 đến 10000 (số hoàn toàn được dự đoán). Độ phức tạp thời gian của các thuật toán mô phỏng cơ thể n tốt sử dụng các phân chia không gian quy mô gần như là O (n log n), là "gần như tuyến tính", do đó bạn có thể mong đợi gần như cùng một yếu tố tăng số lượng hạt bạn có thể xử lý. Ngoài ra, điều đó vẫn sẽ chỉ sử dụng một luồng, vì vậy vẫn còn chỗ để song song hóa trên đó .

Dù sao, như các câu trả lời khác đã lưu ý, mẹo chung để mô phỏng hiệu quả số lượng lớn các hạt tương tác là tổ chức chúng theo hình tứ giác (ở dạng 2D) hoặc hình bát giác (ở dạng 3D). Đặc biệt, để mô phỏng lực hấp dẫn, thuật toán cơ bản bạn muốn sử dụng là thuật toán mô phỏng Barnesẩu Hut , trong đó bạn lưu trữ tổng khối lượng (và tâm khối lượng) của tất cả các hạt có trong mỗi ô của quad / octree của bạn và sử dụng điều đó để ước tính hiệu ứng hấp dẫn trung bình của các hạt trong tế bào đó trên các hạt khác, ở xa.

Bạn có thể tìm thấy rất nhiều mô tả và hướng dẫn về thuật toán Barnes của Hut bởi Googling cho nó, nhưng đây là một mô tả hay và đơn giản để bạn bắt đầu , trong khi đây là một mô tả về triển khai nâng cao được sử dụng để mô phỏng GPU về va chạm thiên hà.


6

Một câu trả lời tối ưu hóa không liên quan gì đến chủ đề. Xin lỗi vì điều đó.

Bạn đang tính Khoảng cách () của mỗi cặp. Điều này liên quan đến việc lấy một căn bậc hai, đó là chậm. Nó cũng liên quan đến một số tra cứu đối tượng để có được kích thước thực tế.

Thay vào đó, bạn có thể tối ưu hóa điều này bằng cách sử dụng hàm distanceSquared (). Tính toán trước khoảng cách tối đa mà bất kỳ hai đối tượng nào có thể tương tác, bình phương nó và sau đó so sánh điều này với Khoảng cách (). Nếu và chỉ khi khoảng cách bình phương nằm trong mức tối đa, thì lấy căn bậc hai và so sánh nó với kích thước đối tượng thực.

EDIT : Tối ưu hóa này chủ yếu là khi bạn đang thử nghiệm va chạm, mà bây giờ tôi nhận thấy không thực sự là những gì bạn đang làm (mặc dù bạn chắc chắn sẽ có lúc nào đó). Nó vẫn có thể được áp dụng cho trường hợp của bạn, nếu tất cả các hạt có kích thước / khối lượng tương tự nhau.


Vâng. Giải pháp này có thể ổn (chỉ mất độ chính xác không đáng kể), nhưng gặp rắc rối khi khối lượng vật thể khác nhau rất nhiều. Nếu khối lượng của một số vật thể rất lớn trong khi một số vật thể có khối lượng rất nhỏ thì khoảng cách tối đa cho hợp lý lại cao hơn. Ví dụ: ảnh hưởng của trọng lực trái đất lên hạt bụi nhỏ không đáng kể đối với trái đất, nhưng không phải đối với hạt bụi (đối với khoảng cách khá lớn). Nhưng trên thực tế, hai hạt bụi ở cùng một khoảng cách không ảnh hưởng đáng kể đến nhau.
SDwarfs

Trên thực tế đó là một điểm rất tốt. Tôi đã đọc sai điều này như một thử nghiệm va chạm, nhưng thực tế nó lại làm ngược lại: các hạt ảnh hưởng lẫn nhau nếu chúng không chạm vào nhau.
Alistair Buxton

3

Tôi không biết nhiều về luồng, nhưng có vẻ như các vòng lặp của bạn tốn thời gian, vì vậy có thể thay đổi từ điều này

i = 0; i < count; i++
  j = 0; j < count; j++

  object_i += force(object_j);

đến đây

i = 0; i < count-1; i++
  j = i+1; j < count; j++

  object_i += force(object_j);
  object_j += force(object_i);

có thể giúp


1
Tại sao điều đó sẽ giúp?

1
Bởi vì hai vòng đầu tiên thực hiện 10 000 lần lặp, nhưng vòng lặp thứ hai chỉ thực hiện 4 lần lặp 950 .
Buksy

1

Nếu bạn đã có vấn đề lớn như vậy với 10 đối tượng mô phỏng, bạn sẽ cần tối ưu hóa mã! Vòng lặp lồng nhau của bạn sẽ chỉ gây ra 10 * 10 lần lặp trong đó 10 lần lặp được bỏ qua (cùng một đối tượng), dẫn đến 90 lần lặp của vòng lặp bên trong. Nếu bạn chỉ đạt được 2 FPS, điều này có nghĩa là hiệu suất của bạn rất tệ, bạn chỉ đạt được 180 lần lặp của vòng lặp bên trong mỗi giây.

Tôi đề nghị bạn làm như sau:

  1. CHUẨN BỊ / LỢI ÍCH: Để chắc chắn biết rằng thói quen này là vấn đề, hãy viết một thói quen điểm chuẩn nhỏ. Nó sẽ thực hiện Update()phương pháp Trọng lực nhiều lần, ví dụ 1000 lần và đo thời gian của nó. Nếu bạn muốn đạt được 30 FPS với 100 đối tượng, bạn nên mô phỏng 100 đối tượng và đo thời gian cho 30 lần thực hiện. Nó sẽ ít hơn 1 giây. Sử dụng một tiêu chuẩn như vậy là cần thiết để làm tối ưu hóa hợp lý. Khác có thể bạn sẽ đạt được điều ngược lại và làm cho mã chạy chậm hơn bởi vì bạn chỉ nghĩ rằng nó phải nhanh hơn ... Vì vậy, tôi thực sự khuyến khích bạn làm điều này!

  2. TỐI ƯU HÓA: Mặc dù bạn không thể làm gì nhiều với bài toán nỗ lực O (N²) (nghĩa là: thời gian tính toán tăng theo phương trình bậc hai với số lượng đối tượng mô phỏng N), bạn có thể tự cải thiện mã.

    a) Bạn sử dụng rất nhiều tra cứu "mảng kết hợp" (Từ điển) trong mã của bạn. Những thứ này chậm! Ví dụ entityEngine.Entities[e.Key].Position. Bạn không thể sử dụng e.Value.Position? Điều này tiết kiệm một tra cứu. Bạn làm điều này ở mọi nơi trong toàn bộ vòng lặp bên trong để truy cập các thuộc tính của các đối tượng được tham chiếu bởi e và e2 ... Thay đổi điều này! b) Bạn tạo một Vector mới bên trong vòng lặp new Vector2( .... ). Tất cả các cuộc gọi "mới" ngụ ý một số cấp phát bộ nhớ (và sau này: thỏa thuận). Chúng thậm chí còn chậm hơn nhiều so với tra cứu của Từ điển. Nếu bạn chỉ cần Vector này tạm thời, vì vậy hãy phân bổ nó bên ngoài các vòng lặp VÀ sử dụng lại nó bằng cách xác định lại các giá trị của nó thành các giá trị mới thay vì tạo một đối tượng mới. c) Bạn sử dụng nhiều hàm lượng giác (ví dụ atan2cos) trong vòng lặp. Nếu độ chính xác của bạn không cần thực sự chính xác, bạn có thể thử sử dụng bảng tra cứu thay thế. Để làm điều này, bạn chia tỷ lệ giá trị của mình thành một phạm vi xác định, làm tròn nó thành một giá trị số nguyên và tìm kiếm nó trong một bảng kết quả được tính toán trước. Nếu bạn cần giúp đỡ với điều đó, chỉ cần hỏi. d) Bạn thường sử dụng .Texture.Width / 2. Bạn có thể tính toán trước giá trị này và lưu trữ kết quả dưới dạng .Texture.HalfWidthhoặc - nếu đây luôn là giá trị nguyên dương chẵn - bạn có thể sử dụng bit thao tác dịch chuyển >> 1để chia cho hai.

Chỉ thực hiện một trong những thay đổi tại một thời điểm và đo lường sự thay đổi theo điểm chuẩn để xem nó ảnh hưởng đến thời gian chạy của bạn như thế nào! Có lẽ một điều là tốt trong khi ý tưởng khác là xấu (thậm chí tôi đã đề xuất chúng ở trên!) ...

Tôi nghĩ những tối ưu hóa này sẽ tốt hơn nhiều so với việc cố gắng đạt được hiệu suất tốt hơn bằng cách sử dụng nhiều luồng! Bạn sẽ gặp nhiều khó khăn khi phối hợp các luồng, vì vậy chúng sẽ không ghi đè lên các giá trị khác. Ngoài ra, họ sẽ xung đột khi truy cập các vùng bộ nhớ tương tự quá. Nếu bạn sử dụng 4 CPU / Chủ đề cho công việc này, bạn chỉ có thể mong đợi tốc độ tăng lên từ 2 đến 3 cho tốc độ khung hình.


0

Bạn có thể làm lại nó mà không cần các dòng tạo đối tượng?

Lực lượng Vector2 = new Vector2 ();

Vector2 VecForce = new Vector2 ((float) Math.Cos (angle), (float) Math.Sin (angle));

nếu có lẽ bạn có thể đặt giá trị lực vào thực thể thay vì tạo hai đối tượng mới mỗi lần, điều đó có thể giúp cải thiện hiệu suất.


4
Vector2trong XNA là một loại giá trị . Nó không có chi phí hoạt động và chi phí xây dựng không đáng kể. Đây không phải là nguồn gốc của vấn đề.
Andrew Russell

@Andrew Russell: Tôi không chắc lắm, nhưng đó có thực sự là trường hợp nếu bạn sử dụng "Vector2 mới" không? Nếu bạn sử dụng Vector2 (....) mà không có "mới", điều này có thể sẽ khác.
SDwarfs

1
@StefanK. Trong C # bạn không thể làm điều đó. Cần cái mới. Bạn đang nghĩ về C ++?
MrKWatkins
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.