การคำนวณแรงโน้มถ่วงแบบมัลติเธรด 2D


24

ฉันกำลังสร้างเกมสำรวจอวกาศและตอนนี้ฉันเริ่มทำงานกับแรงโน้มถ่วง (ใน C # กับ XNA)

แรงโน้มถ่วงยังคงต้องการการปรับแต่ง แต่ก่อนที่ฉันจะทำได้ฉันต้องจัดการกับปัญหาด้านประสิทธิภาพด้วยการคำนวณทางฟิสิกส์ของฉัน

นี่คือการใช้วัตถุ 100 รายการโดยปกติแล้วการแสดงผล 1,000 รายการโดยไม่มีการคำนวณทางฟิสิกส์ได้ดีกว่า 300 FPS (ซึ่งเป็นฝา FPS ของฉัน) แต่วัตถุใด ๆ ที่มากกว่า 10 หรือมากกว่านั้นจะนำเกมมาด้วย เข่าเมื่อทำการคำนวณทางฟิสิกส์

ฉันตรวจสอบการใช้เธรดของฉันและเธรดแรกกำลังฆ่าตัวเองจากงานทั้งหมดดังนั้นฉันจึงคิดว่าฉันต้องทำการคำนวณทางฟิสิกส์ในเธรดอื่น อย่างไรก็ตามเมื่อฉันพยายามเรียกใช้เมธอดการอัปเดตของคลาส Gravity.cs ในเธรดอื่นแม้ว่าเมธอดการอัปเดตของ Gravity จะไม่มีอะไรอยู่เกมยังคงลดเหลือ 2 FPS

Gravity.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;
        }

    }

ใช่ฉันรู้. มันเป็นลูป foreach วนซ้อนกัน แต่ฉันไม่รู้วิธีอื่นในการคำนวณแรงโน้มถ่วงและดูเหมือนว่ามันจะทำงานได้มันเข้มข้นมากจนมันต้องมีเธรดของตัวเอง (แม้ว่าใครจะรู้วิธีที่มีประสิทธิภาพสูงสุดในการคำนวณเหล่านี้ฉันก็ยังอยากรู้ว่าฉันสามารถทำมันในหลาย ๆ กระทู้แทน)

EntityEngine.cs (จัดการอินสแตนซ์ของ 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 ถูกสร้างขึ้นใน Game1.cs และวิธีการ Update () นั้นเรียกว่าภายใน Game1.cs

ฉันต้องการการคำนวณทางฟิสิกส์ใน Gravity.cs เพื่อให้ทำงานทุกครั้งที่มีการอัปเดตเกมในเธรดแยกต่างหากเพื่อให้การคำนวณไม่ทำให้เกมช้าลงจนเหลือน้อยมาก (0-2) FPS

ฉันจะดำเนินการเกี่ยวกับการทำเกลียวนี้อย่างไร (คำแนะนำใด ๆ สำหรับระบบแรงโน้มถ่วงของดาวเคราะห์ที่ได้รับการปรับปรุงจะยินดีต้อนรับถ้ามีใคร)

ฉันยังไม่ได้มองหาบทเรียนเพราะเหตุใดฉันจึงไม่ควรใช้การทำเกลียวหรืออันตรายจากการใช้อย่างไม่ถูกต้องฉันกำลังมองหาคำตอบที่ตรงไปตรงมาเกี่ยวกับวิธีการทำ ฉันใช้เวลาไปหนึ่งชั่วโมงกับคำถามนี้มากกับผลลัพธ์เล็ก ๆ น้อย ๆ ที่ฉันเข้าใจหรือมีประโยชน์ ฉันไม่ได้ตั้งใจออกมาหยาบคาย แต่มันก็ยากที่จะเขียนโปรแกรม noob เพื่อให้ได้คำตอบที่มีความหมายฉันมักจะได้คำตอบที่ซับซ้อนดังนั้นฉันจึงสามารถแก้ไขปัญหาของฉันได้ง่ายถ้าฉันเข้าใจหรือ บางคนบอกว่าทำไมฉันไม่ควรทำในสิ่งที่ฉันต้องการจะทำและไม่เสนอทางเลือกอื่น (มีประโยชน์)

ขอขอบคุณสำหรับความช่วยเหลือ!

แก้ไข : หลังจากอ่านคำตอบที่ฉันได้รับฉันเห็นว่าพวกคุณสนใจจริง ๆ และไม่เพียง แต่พยายามที่จะคายคำตอบที่อาจใช้งานได้ ฉันต้องการฆ่านกสองตัวด้วยหินก้อนเดียว (ปรับปรุงประสิทธิภาพและเรียนรู้พื้นฐานบางอย่างของการมัลติเธรด) แต่ดูเหมือนว่าปัญหาส่วนใหญ่อยู่ในการคำนวณของฉันและเกลียวนั้นยุ่งยากกว่าการเพิ่มประสิทธิภาพการทำงาน ขอบคุณทุกคนฉันจะอ่านคำตอบของคุณอีกครั้งและลองวิธีแก้ปัญหาของคุณเมื่อฉันทำกับโรงเรียนขอบคุณอีกครั้ง!


[ระบบเธรดการอัพเดตของคุณที่อธิบายไว้ข้างต้น] ทำอะไรได้บ้าง (ใช้งานได้) Btw ฉันจะเริ่มโดยเร็วที่สุดในรอบเกม - เช่นก่อนที่จะอัพเดตเอนทิตี
ThorinII

2
การเรียกใช้ Trig ในการตกแต่งภายในของลูปซ้อนกันของคุณน่าจะเป็นที่นิยมที่สุด หากคุณสามารถหาวิธีกำจัดพวกเขาได้นั่นจะช่วยลดปัญหาkนี้ได้O(n^2)มาก
RBarryYoung

1
แท้จริงแล้วการเรียกตรีโกณมิตินั้น ไม่จำเป็นอย่างสมบูรณ์ : คุณคำนวณมุมจากเวกเตอร์ก่อนจากนั้นใช้เพื่อสร้างเวกเตอร์อีกอันที่ชี้ไปในทิศทางที่กำหนด จากนั้นคุณก็ทำให้เวกเตอร์นั้นกลับสู่สภาพปกติ แต่เนื่องจากsin² + cos² ≡ 1มันถูกทำให้เป็นมาตรฐานอยู่แล้ว! คุณสามารถใช้เวกเตอร์ดั้งเดิมที่เชื่อมวัตถุสองตัวที่คุณสนใจและทำให้วัตถุนี้เป็นปกติ ไม่มีการเรียกตรีโกณมิติใด ๆ ที่จำเป็น
leftaroundabout

XNA เลิกใช้แล้วหรือไม่?
jcora

@yannbane คำถามนั้นไม่ได้เพิ่มสิ่งใดที่เป็นประโยชน์ต่อการอภิปราย และไม่สถานะของ XNA นั้นไม่ตรงกับคำจำกัดความใด ๆ ที่ถูกปฏิเสธ
เซทแบททิน

คำตอบ:


36

สิ่งที่คุณมีที่นี่คืออัลกอริทึมO (n²)แบบคลาสสิก สาเหตุที่แท้จริงของปัญหาของคุณไม่มีส่วนเกี่ยวข้องกับการทำเกลียวและทุกอย่างเกี่ยวกับข้อเท็จจริงที่ว่าอัลกอริทึมของคุณมีความซับซ้อนสูง

หากคุณยังไม่เคยพบเห็นสัญกรณ์ "Big O" มาก่อนหมายความว่าจำนวนการปฏิบัติการที่จำเป็นสำหรับการทำงานกับองค์ประกอบn (นี่คือคำอธิบายที่ง่ายที่สุด) 100 องค์ประกอบของคุณกำลังดำเนินการในส่วนด้านในของวงของคุณ10000 ครั้ง

ในการพัฒนาเกมโดยทั่วไปคุณต้องการหลีกเลี่ยงอัลกอริทึมO (n²)เว้นแต่ว่าคุณจะมีข้อมูลจำนวนน้อย (และควรได้รับการแก้ไขหรือ จำกัด ไว้) และอัลกอริทึมที่รวดเร็ว

หากทุกเอนทิตีมีผลกระทบต่อเอนทิตีอื่น ๆ คุณจำเป็นต้องใช้อัลกอริทึม O (n²) แต่ดูเหมือนว่ามีเพียงไม่กี่หน่วยงานเท่านั้นที่โต้ตอบกันจริง ๆ (เนื่องจากif (distance < ...)) - ดังนั้นคุณจึงสามารถลดจำนวนการดำเนินการลงได้อย่างมากโดยใช้สิ่งที่เรียกว่า "การแบ่งพาร์ติชันแบบอวกาศ "

เนื่องจากนี่เป็นหัวข้อที่ค่อนข้างละเอียดและค่อนข้างเจาะจงเฉพาะเกมฉันขอแนะนำให้คุณถามคำถามใหม่เพื่อรับรายละเอียดเพิ่มเติม มาต่อกันที่ ...


หนึ่งในปัญหาด้านประสิทธิภาพที่สำคัญของโค้ดของคุณนั้นค่อนข้างง่าย นี่เป็นสิ่งที่ทำให้ช้า :

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

คุณกำลังค้นหาพจนานุกรมโดยสตริงทุกรอบ (ซ้ำหลายครั้งในลูปอื่น ๆ ของคุณ) สำหรับวัตถุที่คุณมีอยู่แล้ว!

คุณสามารถทำได้:

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

หรือคุณสามารถทำได้: (โดยส่วนตัวแล้วฉันชอบสิ่งนี้ดีกว่าทั้งสองควรมีความเร็วเท่ากัน)

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

การค้นหาพจนานุกรมตามสตริงค่อนข้างช้า การวนซ้ำโดยตรงจะเร็วขึ้นอย่างมีนัยสำคัญ

ถึงแม้ว่าคุณจะทำบ่อยแค่ไหน จริงต้องดูรายการตามชื่อ? เปรียบเทียบกับความถี่ที่คุณต้องวนซ้ำมันทั้งหมดหรือไม่ หากคุณทำการค้นหาชื่อไม่ค่อยให้พิจารณาจัดเก็บเอนทิตีของคุณในList(ให้Nameสมาชิก)

รหัสที่คุณมีมีค่อนข้างเล็กน้อย ฉันไม่ได้ทำโปรไฟล์ แต่ฉันเดิมพันมากที่สุดของเวลาการดำเนินการของคุณจะไปที่การค้นหาพจนานุกรมซ้ำแล้วซ้ำอีก รหัสของคุณอาจ "เร็วพอ" เพียงแก้ไขปัญหานี้

แก้ไข: ปัญหาที่ใหญ่ที่สุดถัดไปอาจจะเรียกAtan2แล้วแปลงกลับเป็นเวกเตอร์ด้วยทันทีSinและCosทันที! เพียงแค่ใช้เวกเตอร์โดยตรง


ในที่สุดเรามาพูดถึงการทำเกลียวและปัญหาสำคัญในรหัสของคุณ:

สิ่งแรกและชัดเจนที่สุด: อย่าสร้างเธรดใหม่ทุกเฟรม!วัตถุของเธรดค่อนข้าง "หนัก" ทางออกที่ง่ายที่สุดสำหรับสิ่งนี้คือใช้ThreadPoolแทน

แน่นอนว่าไม่ใช่เรื่องง่าย มาดูปัญหาข้อที่สองกันเถอะ: อย่าแตะที่ข้อมูลในสองเธรดพร้อมกัน!(โดยไม่ต้องเพิ่มโครงสร้างพื้นฐานความปลอดภัยของเธรดที่เหมาะสม)

คุณกำลังเหยียบย่ำความทรงจำที่นี่ด้วยวิธีที่น่ากลัวที่สุด ไม่มีกระทู้ความปลอดภัยที่นี่ หนึ่งในหลาย ๆ "gravity.Updateเธรดที่คุณกำลังเริ่มสามารถเขียนทับข้อมูลที่ใช้ในเธรดอื่นในเวลาที่ไม่คาดคิด เธรดหลักของคุณในขณะเดียวกันก็ไม่ต้องสงสัยเลยว่าจะต้องสัมผัสกับโครงสร้างข้อมูลเหล่านี้ทั้งหมดเช่นกัน ฉันจะไม่แปลกใจถ้ารหัสนี้สร้างการละเมิดการเข้าถึงหน่วยความจำที่ยากต่อการทำซ้ำ

การทำสิ่งที่ปลอดภัยเช่นเธรดนี้เป็นเรื่องยากและสามารถเพิ่มค่าใช้จ่ายด้านประสิทธิภาพที่สำคัญเช่นที่มักไม่คุ้มค่ากับความพยายาม


แต่เมื่อเห็นว่าคุณถาม (ไม่เช่นนั้น) เกี่ยวกับวิธีที่จะทำต่อไปเรามาคุยกันเรื่องนี้ ...

โดยปกติแล้วฉันขอแนะนำให้เริ่มต้นด้วยการฝึกอะไรที่เรียบง่ายโดยที่เธรดของคุณนั้น "ดับแล้วลืม" การเล่นเสียงการเขียนบางอย่างลงบนดิสก์ ฯลฯ สิ่งต่าง ๆ มีความซับซ้อนเมื่อคุณต้องป้อนผลลัพธ์กลับเข้าไปในเธรดหลัก

โดยทั่วไปมีสามวิธีในการแก้ไขปัญหาของคุณ:

1)ใส่ล็อกข้อมูลทั้งหมดที่คุณใช้ข้ามเธรด ใน C # นี้ทำค่อนข้างง่ายด้วยlockคำสั่ง

โดยทั่วไปคุณสร้าง (และเก็บ!) การnew objectล็อกโดยเฉพาะเพื่อปกป้องชุดข้อมูล (เพื่อเหตุผลด้านความปลอดภัยที่โดยทั่วไปจะเกิดขึ้นเมื่อเขียน API สาธารณะเท่านั้น - แต่มีสไตล์ที่ดีเหมือนกันทั้งหมด) จากนั้นคุณต้องล็อควัตถุล็อคของคุณทุกที่ที่คุณเข้าถึงข้อมูลที่ปกป้อง!

แน่นอนถ้ามีบางสิ่ง "ล็อค" โดยเธรดหนึ่งอันเนื่องจากมีการใช้งานอยู่และเธรดอื่นพยายามเข้าถึงเธรดนั้น - เธรดที่สองจะถูกบังคับให้รอจนกว่าเธรดแรกจะเสร็จสิ้น ดังนั้นถ้าคุณไม่เลือกงานที่สามารถทำได้ในแบบคู่ขนานคุณจะได้รับประสิทธิภาพการทำงานแบบเธรดเดียว (หรือแย่กว่านั้น)

ดังนั้นในกรณีของคุณไม่มีประเด็นในการทำเช่นนี้เว้นแต่ว่าคุณจะสามารถออกแบบเกมของคุณเพื่อให้โค้ดอื่น ๆ ทำงานแบบขนานซึ่งจะไม่แตะต้องคอลเลกชันของคุณ

2)คัดลอกข้อมูลลงในเธรดปล่อยให้ประมวลผลแล้วนำผลลัพธ์ออกมาอีกครั้งเมื่อเสร็จสิ้น

วิธีที่คุณใช้สิ่งนี้จะขึ้นอยู่กับสิ่งที่คุณทำ แต่เห็นได้ชัดว่าสิ่งนี้จะเกี่ยวข้องกับการดำเนินการคัดลอกที่อาจมีราคาแพง (หรือสอง) ว่าในหลายกรณีจะช้ากว่าการทำสิ่งที่เป็นเธรดเดี่ยว

และแน่นอนคุณยังต้องมีงานอื่นที่ต้องทำในพื้นหลังมิฉะนั้นเธรดหลักของคุณจะนั่งรอให้เธรดอื่นของคุณเสร็จสิ้นเพื่อให้สามารถคัดลอกข้อมูลกลับมาได้!

3)ใช้โครงสร้างข้อมูลแบบเธรดที่ปลอดภัย

สิ่งเหล่านี้ค่อนข้างช้ากว่าคู่เธรดเธรดเดียวและมักจะใช้งานได้ยากกว่าการล็อคแบบง่าย พวกเขายังสามารถแสดงปัญหาของการล็อก (ลดประสิทธิภาพลงเป็นเธรดเดียว) ยกเว้นว่าคุณใช้อย่างระมัดระวัง


ในที่สุดเนื่องจากนี่เป็นการจำลองแบบเฟรมคุณจะต้องรอเธรดหลักเพื่อรอเธรดอื่นเพื่อให้ผลลัพธ์ของพวกเขาเพื่อให้เฟรมสามารถเรนเดอร์และการจำลองสามารถดำเนินต่อไปได้ คำอธิบายเต็มมันนานเกินไปที่จะใส่ในที่นี่ แต่โดยทั่วไปคุณจะต้องการที่จะเรียนรู้วิธีการใช้งานและMonitor.Wait นี่คือบทความที่จะเริ่มต้นคุณออกMonitor.Pulse


ฉันรู้ว่าฉันไม่ได้ระบุรายละเอียดการใช้งานเฉพาะ (ยกเว้นบิตสุดท้าย) หรือรหัสสำหรับวิธีการเหล่านี้ ครั้งแรกของทั้งหมดจะมีจำนวนมากที่จะปก และประการที่สองไม่มีรหัสใดที่ใช้กับโค้ดของคุณได้ด้วยตัวเอง - คุณต้องเข้าใกล้สถาปัตยกรรมทั้งหมดของคุณเพื่อดูการเพิ่มเธรด

การทำเกลียวจะไม่ใช้รหัสที่คุณมีได้เร็วขึ้นอย่างน่าอัศจรรย์ - เพียงแค่ให้คุณทำอย่างอื่นในเวลาเดียวกัน!


8
+10 ถ้าทำได้ บางทีคุณสามารถย้ายประโยคสุดท้ายไปด้านบนเพื่อเป็นการแนะนำได้เพราะมันสรุปประเด็นหลักที่นี่ การเรียกใช้โค้ดบนเธรดอื่นไม่ได้ทำให้การเรนเดอร์เร็วขึ้นอย่างน่าอัศจรรย์หากคุณไม่มีสิ่งอื่นให้ทำพร้อมกัน และตัวเรนเดอร์อาจรอเธรดให้เสร็จสิ้น แต่ถ้ามันไม่ (และจะรู้ได้อย่างไร) มันจะวาดสถานะเกมที่ไม่สอดคล้องกับฟิสิกส์เอนทิตี้บางส่วนที่ยังคงได้รับการอัพเดต
LearnCocos2D

ฉันเชื่ออย่างถี่ถ้วนว่าการทำเกลียวไม่ใช่สิ่งที่ฉันต้องการขอขอบคุณสำหรับข้อมูลที่ยาวและมีความรู้! สำหรับการปรับปรุงประสิทธิภาพฉันได้ทำการเปลี่ยนแปลงตามที่คุณแนะนำ (และอื่น ๆ ) แต่ฉันยังคงได้รับประสิทธิภาพที่ไม่ดีเมื่อจัดการกับวัตถุ> 60 ฉันคิดว่ามันจะเป็นการดีที่สุดสำหรับฉันที่จะถามคำถามอื่นที่เน้นประสิทธิภาพของการจำลองร่างกาย N-Body คุณได้รับคำตอบจากฉันในเรื่องนี้ ขอบคุณ!
บุรุษไปรษณีย์

1
ยินดีต้อนรับดีใจที่ช่วย :) เมื่อคุณโพสต์คำถามใหม่ของคุณโปรดวางลิงค์ที่นี่เพื่อที่ฉันและคนอื่น ๆ ที่ติดตามมาจะเห็นมัน
Andrew Russell

@ โพสต์ในขณะที่ฉันเห็นด้วยกับสิ่งที่คำตอบนี้โดยทั่วไปแล้วฉันคิดว่ามันขาดความจริงที่ว่านี่เป็นอัลกอริทึมที่สมบูรณ์แบบเพื่อใช้ประโยชน์จากเธรด มีเหตุผลที่พวกเขาทำสิ่งนี้ใน GPU และเป็นเพราะมันเป็นอัลกอริทึมแบบขนานเล็กน้อยหากคุณย้ายการเขียนไปยังขั้นตอนที่สอง ไม่จำเป็นต้องล็อคหรือคัดลอกหรือสร้างโครงสร้างข้อมูลที่ปลอดภัย Parallel.ForEach ที่เรียบง่ายและทำได้โดยไม่มีปัญหา
Chewy Gumball

@ChewyGumball จุดที่ถูกต้องมาก! และในขณะที่บุรุษไปรษณีย์จะต้องสร้างอัลกอริทึมของเขาสองเฟส แต่เนื้อหานั้นควรเป็นสองเฟสต่อไป มันเป็นสิ่งที่ควรค่าแก่การชี้ให้เห็นซึ่งParallelไม่ได้เป็นสิ่งที่ไม่มีค่าใช้จ่ายดังนั้นจึงเป็นเรื่องที่ควรทำอย่างชัดเจนโดยเฉพาะอย่างยิ่งสำหรับชุดข้อมูลขนาดเล็กดังกล่าว และแน่นอนว่ามันยังดีกว่าที่จะลดความซับซ้อนของอัลกอริธึมในกรณีนี้ - แทนที่จะเพียงแค่ขนานมัน
Andrew Russell

22

ตกลงได้อย่างรวดเร็วก่อนมีบางสิ่งที่คุณควรลอง ที่แรกที่คุณควรพยายามที่จะลดการตรวจสอบการชนกันของคุณคุณสามารถทำได้โดยใช้ชนิดของโครงสร้างเชิงพื้นที่บางส่วนเช่นวอดทรี สิ่งนี้จะช่วยให้คุณลดจำนวนครั้งที่สองของแต่ละหน้าเนื่องจากคุณจะค้นหาเฉพาะรายการปิดรายการแรก

เกี่ยวกับเธรดของคุณ: พยายามอย่าสร้างเธรดทุกครั้งที่มีการอัพเดต ค่าโสหุ้ยนี้อาจทำให้คุณช้าลงมากกว่าที่จะเร่งความเร็ว ให้ลองสร้างเธรดการชนกันครั้งเดียวและปล่อยให้มันทำงานแทนคุณได้ ฉันไม่มีวิธีการคัดลอกวางโค้ดนี้อย่างเป็นรูปธรรมแต่มีบทความเกี่ยวกับการทำข้อมูลให้ตรงกันของเธรดและผู้ทำงานเบื้องหลังสำหรับ C #

อีกจุดหนึ่งคือในลูป foreach คุณไม่จำเป็นต้องทำentityEngine.Entities[e.Key].Textureเพราะคุณเข้าถึง dict ในส่วนหัว foreach แล้ว แต่คุณสามารถเขียนe.Textureได้ ฉันไม่รู้จริง ๆ เกี่ยวกับผลกระทบของสิ่งนี้เพียงต้องการแจ้งให้คุณทราบ)

สิ่งสุดท้ายสิ่งหนึ่ง: ในขณะที่คุณกำลังตรวจสอบทุก ๆ เอนทิตีเพราะได้รับการสอบถามในลูปแรกและอันที่สอง

ตัวอย่างที่มี 2 เอนทิตี A และ 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

แม้ว่านี่จะเป็นวิธีที่เป็นไปได้ แต่คุณสามารถจัดการ A และ B ได้ในคราวเดียวโดยข้ามการตรวจสอบการชนของคุณครึ่งหนึ่ง

หวังว่านี่จะช่วยให้คุณเริ่มต้น =)

PS: แม้ว่าคุณจะบอกว่าคุณไม่ต้องการที่จะได้ยิน: พยายามที่จะตรวจจับการชนกันในหัวข้อเดียวกันและเพียงแค่เพิ่มความเร็วขึ้นพอ การทำเกลียวดูเหมือนจะเป็นความคิดที่ดี แต่ด้วยความจำเป็นในการซิงโครไนซ์เหมือนนรก หากการตรวจสอบการชนของคุณช้ากว่าการอัพเดทของคุณ (เหตุผลในการทำเกลียว) คุณจะได้รับข้อผิดพลาดและข้อผิดพลาดเนื่องจากการชนจะเริ่มขึ้นหลังจากเรือรบเคลื่อนที่ไปแล้วและในทางกลับกัน ฉันไม่ต้องการที่จะกีดกันคุณนี่เป็นเพียงประสบการณ์ส่วนตัว

EDIT1: ลิงก์กับบทช่วยสอน QuadTree (Java): http://gamedev.tutsplus.com/tutorials/implementation/quick-tip-use-quadtrees-to-detect-ossible-collisions-in-2dspacespace/


10
สิ่งที่ดีเกี่ยวกับการใช้ quad / octrees สำหรับการจำลองแรงโน้มถ่วงคือแทนที่จะเพียงแค่ละเว้นอนุภาคที่อยู่ห่างไกลคุณสามารถจัดเก็บมวลรวมและศูนย์กลางของมวลของอนุภาคทั้งหมดในแต่ละสาขาของต้นไม้ของคุณและใช้สิ่งนี้เพื่อคำนวณค่าเฉลี่ยแรงโน้มถ่วง ของอนุภาคทั้งหมดในสาขานี้กับอนุภาคที่อยู่ไกลออกไปอื่น ๆ นี้เป็นที่รู้จักกันเป็นอัลกอริทึมบาร์นส์ฮัทและมันเป็นสิ่งที่ผู้เชี่ยวชาญใช้
Ilmari Karonen

10

จริงๆแล้วสิ่งแรกที่คุณควรทำคือเปลี่ยนเป็นอัลกอริธึมที่ดีกว่า

การจำลองแบบขนานของคุณสามารถทำได้แม้ในกรณีที่เป็นไปได้ที่ดีที่สุดเพิ่มความเร็วโดยปัจจัยเท่ากับจำนวน CPU ×แกนต่อ CPU ×เธรดต่อแกนที่มีอยู่ในระบบของคุณ - เช่นระหว่าง 4 ถึง 16 สำหรับพีซีที่ทันสมัย (การเคลื่อนย้ายรหัสของคุณกับ GPU สามารถให้ผลผลิตมากน่าประทับใจมากขึ้นปัจจัยขนานที่ค่าใช้จ่ายของความซับซ้อนในการพัฒนาและการเสริมพื้นฐานความเร็วการคำนวณลดลงต่อด้าย.) ด้วยอัลกอริทึม O (n²) เช่นโค้ดตัวอย่างของคุณนี้จะช่วยให้คุณ ใช้ 2-4 อนุภาคมากเท่าที่คุณมีอยู่ในปัจจุบัน

ในทางกลับกันการเปลี่ยนไปใช้อัลกอริทึมที่มีประสิทธิภาพมากขึ้นสามารถเพิ่มความเร็วในการจำลองของคุณได้อย่างง่ายดายโดยใช้ตัวคูณ 100 ถึง 10,000 (ตัวเลขคาดเดาหมดจด) ความซับซ้อนของเวลาของอัลกอริธึมการจำลองร่างกายดีโดยใช้การแบ่งเชิงพื้นที่มีขนาดประมาณ O (n log n) ซึ่งก็คือ "เกือบเป็นเชิงเส้น" ดังนั้นคุณจึงสามารถคาดหวังได้ว่าจำนวนอนุภาคที่คุณสามารถจัดการได้เพิ่มขึ้นเกือบเท่ากัน นอกจากนี้ที่จะยังคงใช้เพียงหนึ่งหัวข้อดังนั้นก็จะยังคงมีห้องพักสำหรับการขนานด้านบนของที่

อย่างไรก็ตามตามคำตอบอื่น ๆ ที่ได้กล่าวมาแล้วเคล็ดลับทั่วไปในการจำลองอนุภาคที่มีปฏิสัมพันธ์จำนวนมากได้อย่างมีประสิทธิภาพคือการจัดระเบียบพวกมันในรูปสี่เหลี่ยมสี่แกน (ใน 2D) หรือoctree (ใน 3D) โดยเฉพาะอย่างยิ่งสำหรับการจำลองแรงโน้มถ่วงอัลกอริทึมพื้นฐานที่คุณต้องการใช้คืออัลกอริธึมการจำลอง Barnes – Hutซึ่งคุณเก็บมวลรวม (และศูนย์กลางของมวล) ของอนุภาคทั้งหมดที่มีอยู่ในแต่ละเซลล์ของ quad / octree และ ใช้สิ่งนั้นเพื่อประมาณผลความโน้มถ่วงเฉลี่ยของอนุภาคในเซลล์นั้นกับอนุภาคอื่นที่อยู่ไกล

คุณสามารถหาคำอธิบายและแบบฝึกหัดมากมายบนอัลกอริทึม Barnes – Hut โดยGooglingแต่นี่เป็นวิธีที่ง่ายและดีในการเริ่มต้นใช้งานในขณะที่นี่คือคำอธิบายของการใช้งานขั้นสูงที่ใช้สำหรับการจำลอง GPU ของกาแล็คซี่ชน


6

คำตอบการเพิ่มประสิทธิภาพอื่นที่ไม่เกี่ยวข้องกับเธรด ขอโทษสำหรับเรื่องนั้น.

คุณกำลังคำนวณระยะทาง () ของทุกคู่ สิ่งนี้เกี่ยวข้องกับการรากที่สองซึ่งช้า นอกจากนี้ยังเกี่ยวข้องกับการค้นหาวัตถุหลายอย่างเพื่อให้ได้ขนาดจริง

คุณสามารถปรับให้เหมาะสมได้โดยใช้ฟังก์ชัน DistanceSquared () แทน คำนวณระยะทางสูงสุดที่วัตถุสองตัวใด ๆ สามารถโต้ตอบจัดวางมันแล้วเปรียบเทียบสิ่งนี้กับ DistanceSquared () ถ้าหากระยะห่างกำลังสองอยู่ภายในค่าสูงสุดแล้วให้หาสแควร์รูทแล้วเปรียบเทียบกับขนาดวัตถุจริง

แก้ไข : การเพิ่มประสิทธิภาพนี้ส่วนใหญ่ใช้สำหรับเมื่อคุณกำลังทดสอบการชนซึ่งตอนนี้ฉันสังเกตเห็นว่าไม่ใช่สิ่งที่คุณกำลังทำ มันอาจจะยังคงใช้กับสถานการณ์ของคุณแม้ว่าถ้าอนุภาคทั้งหมดมีขนาด / มวลใกล้เคียง


ใช่. วิธีนี้อาจจะดี (การสูญเสียความแม่นยำเล็กน้อยเท่านั้น) แต่จะมีปัญหาเมื่อมวลของวัตถุแตกต่างกันมาก หากมวลของวัตถุบางชิ้นมีขนาดใหญ่มากในขณะที่วัตถุบางส่วนมีขนาดเล็กมากระยะทางสูงสุดสำหรับเหตุผลนั้นสูงกว่า เช่นผลกระทบของแรงโน้มถ่วงของโลกต่ออนุภาคฝุ่นขนาดเล็กนั้นมีความสำคัญต่อโลก แต่ไม่ใช่สำหรับฝุ่นละออง (สำหรับระยะทางที่ค่อนข้างใหญ่) แต่ในความเป็นจริงแล้วฝุ่นละอองสองตัวที่อยู่ในระยะเดียวกันไม่มีผลต่อกันและกันอย่างมีนัยสำคัญ
SDwarfs

จริงๆแล้วมันเป็นจุดที่ดีมาก ฉันอ่านผิดเป็นแบบทดสอบการชน แต่จริง ๆ แล้วมันทำตรงกันข้าม: อนุภาคมีอิทธิพลต่อกันและกันถ้าพวกเขาไม่ได้สัมผัส
Alistair Buxton

3

ฉันไม่รู้มากเกี่ยวกับการทำเกลียว แต่ดูเหมือนว่าลูปของคุณใช้เวลานานดังนั้นอาจเปลี่ยนไปจากนี้

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

  object_i += force(object_j);

สำหรับสิ่งนี้

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

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

สามารถช่วยได้


1
ทำไมถึงช่วยได้?

1
เพราะลูปสองวงแรกให้ทำซ้ำ10,000ครั้ง แต่ลูปที่สองทำให้วนซ้ำได้เพียง4 950เท่านั้น
Buksy

1

หากคุณมีปัญหาใหญ่กับวัตถุจำลอง 10 รายการคุณจะต้องปรับรหัสให้เหมาะสม! การวนซ้ำซ้อนกันของคุณจะทำให้เกิดการวนซ้ำ 10 * 10 ซึ่งการวนซ้ำ 10 ครั้งจะถูกข้าม (วัตถุเดียวกัน) ส่งผลให้ 90 การวนซ้ำของลูปภายใน หากคุณบรรลุเพียง 2 FPS นี่หมายความว่าประสิทธิภาพของคุณแย่มากจนทำให้คุณวนซ้ำภายใน 180 ครั้งต่อวินาที

ฉันขอแนะนำให้คุณทำดังต่อไปนี้:

  1. การเตรียมการ / การสร้างแบรนด์: หากต้องการทราบว่ารูทีนนี้เป็นปัญหาให้เขียนรูทีนเบนช์มาตรฐานเล็ก ๆ มันจะดำเนินการUpdate()วิธีการของแรงโน้มถ่วงหลายครั้งเช่น 1,000 ครั้งและวัดเวลา หากคุณต้องการบรรลุ 30 FPS ด้วย 100 วัตถุคุณควรจำลอง 100 วัตถุและวัดเวลาสำหรับการประมวลผล 30 ครั้ง ควรน้อยกว่า 1 วินาที จำเป็นต้องใช้การวัดประสิทธิภาพเพื่อทำการเพิ่มประสิทธิภาพที่เหมาะสม มิฉะนั้นคุณอาจจะได้สิ่งที่ตรงกันข้ามและทำให้โค้ดทำงานช้าลงเพราะคุณคิดว่ามันจะต้องเร็วกว่านี้ ...

  2. การเพิ่มประสิทธิภาพ: ในขณะที่คุณไม่สามารถทำอะไรได้มากเกี่ยวกับปัญหาความพยายาม O (N²) (ความหมาย: เวลาการคำนวณเพิ่มขึ้นเป็นสองเท่ากับจำนวนของวัตถุจำลอง N) คุณสามารถปรับปรุงโค้ดเองได้

    a) คุณใช้การค้นหา "associative array" (พจนานุกรม) จำนวนมากภายในรหัสของคุณ เหล่านี้ช้า! entityEngine.Entities[e.Key].Positionเช่น คุณใช้e.Value.Positionไม่ได้เหรอ วิธีนี้ช่วยประหยัดการค้นหาหนึ่งครั้ง คุณทำสิ่งนี้ได้ทุกที่ในวงในเพื่อเข้าถึงคุณสมบัติของวัตถุที่อ้างอิงโดย e และ e2 ... เปลี่ยนสิ่งนี้! b) คุณสร้างเวกเตอร์ใหม่ภายในลูป) ภายในลูป หากความแม่นยำของคุณไม่จำเป็นต้องแม่นยำจริงๆคุณอาจลองใช้ตารางการค้นหาแทน ในการทำเช่นนี้คุณปรับขนาดค่าของคุณเป็นช่วงกำหนดให้ปัดเป็นค่าจำนวนเต็มแล้วค้นหาในตารางของผลลัพธ์ที่คำนวณล่วงหน้า หากคุณต้องการความช่วยเหลือเพียงแค่ถาม d) คุณมักจะใช้ คุณสามารถคำนวณล่วงหน้าและเก็บผลลัพธ์ได้หรือถ้านี่เป็นจำนวนเต็มบวกเสมอคุณสามารถใช้การเปลี่ยนแปลงแบบบิตได้new Vector2( .... )คุณสร้างเวกเตอร์ใหม่ภายในวง การเรียก "ใหม่" ทั้งหมดเกี่ยวข้องกับการจัดสรรหน่วยความจำบางส่วน (และหลังจากนั้น: การจัดสรรคืน) สิ่งเหล่านี้ช้ากว่าการค้นหาพจนานุกรมมาก หากคุณต้องการเวกเตอร์นี้เป็นการชั่วคราวดังนั้นจัดสรรนอกลูปและ - ใช้ซ้ำโดยการกำหนดค่าของมันเป็นค่าใหม่แทนการสร้างวัตถุใหม่ c) คุณใช้ฟังก์ชันตรีโกณมิติจำนวนมาก (เช่นatan2และcos.Texture.Width / 2.Texture.HalfWidth>> 1 เพื่อแบ่งสอง

ทำการเปลี่ยนแปลงเพียงครั้งเดียวในแต่ละครั้งและวัดการเปลี่ยนแปลงตามเกณฑ์มาตรฐานเพื่อดูว่ามันส่งผลต่อรันไทม์ของคุณอย่างไร! อาจเป็นสิ่งหนึ่งที่ดีในขณะที่ความคิดอื่น ๆ ไม่ดี (แม้ฉันจะเสนอพวกเขาข้างต้น!) ...

ฉันคิดว่าการเพิ่มประสิทธิภาพเหล่านี้จะดีกว่าการพยายามบรรลุประสิทธิภาพที่ดีขึ้นโดยใช้หลายเธรด! คุณจะมีปัญหามากในการประสานงานหัวข้อดังนั้นพวกเขาจะไม่เขียนทับค่าอื่น ๆ นอกจากนี้พวกเขาจะขัดแย้งกันเมื่อเข้าถึงพื้นที่หน่วยความจำที่คล้ายกันด้วย หากคุณใช้ 4 ซีพียู / เธรดสำหรับงานนี้คุณสามารถคาดหวังเพียง 2 ถึง 3 สำหรับอัตราเฟรม


0

คุณสามารถทำงานซ้ำได้โดยไม่ต้องมีบรรทัดการสร้างวัตถุหรือไม่

Vector2 Force = Vector2 ใหม่ ();

Vector2 VecForce = ใหม่ Vector2 ((ลอย) Math.Cos (มุม), (float) Math.Sin (มุม));

หากบางทีคุณสามารถวางค่าแรงลงในเอนทิตีแทนการสร้างวัตถุใหม่สองรายการในแต่ละครั้งมันอาจช่วยปรับปรุงประสิทธิภาพ


4
Vector2ใน XNA เป็นประเภทค่า ไม่มีค่าใช้จ่าย GC และค่าใช้จ่ายในการก่อสร้างจะเล็กน้อย นี่ไม่ใช่สาเหตุของปัญหา
Andrew Russell

@ Andrew รัสเซล: ฉันไม่แน่ใจ แต่นั่นก็ยังคงเป็นกรณีนี้หรือไม่ถ้าคุณใช้ "new Vector2" หากคุณใช้ Vector2 (.... ) โดยไม่มี "ใหม่" สิ่งนี้อาจแตกต่างกัน
SDwarfs

1
@StefanK ใน C # คุณไม่สามารถทำได้ ต้องการสิ่งใหม่ คุณคิดว่า C ++ หรือไม่
MrKWatkins
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.