2 डी गुरुत्वाकर्षण गणनाओं को गुणा करना


24

मैं एक अंतरिक्ष अन्वेषण खेल बना रहा हूं और मैंने वर्तमान में गुरुत्वाकर्षण पर काम करना शुरू कर दिया है (XNA के साथ C # में)।

गुरुत्वाकर्षण को अभी भी ट्विकिंग की आवश्यकता है, लेकिन इससे पहले कि मैं ऐसा कर सकता हूं, मुझे अपनी भौतिकी गणना के साथ कुछ प्रदर्शन मुद्दों को संबोधित करने की आवश्यकता है।

यह 100 वस्तुओं का उपयोग कर रहा है, आम तौर पर उनमें से 1000 का प्रतिपादन बिना किसी भौतिकी गणना के 300 एफपीएस (जो कि मेरी एफपीएस कैप है) से अधिक हो जाता है, लेकिन 10 या अधिक वस्तुओं में से कोई भी खेल लाता है (और जिस एकल धागे पर यह चलता है) भौतिकी की गणना करते समय घुटने।

मैंने अपने थ्रेड उपयोग की जाँच की और पहला थ्रेड सभी काम से खुद को मार रहा था, इसलिए मुझे लगा कि मुझे दूसरे थ्रेड पर भौतिकी गणना करने की आवश्यकता है। हालाँकि जब मैं Gravity.cs क्लास के अपडेट मेथड को दूसरे थ्रेड पर चलाने की कोशिश करता हूं, भले ही ग्रेविटी के अपडेट मेथड में कुछ भी न हो, फिर भी गेम 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;
        }

    }

हाँ मुझे पता है। यह एक नेस्टेड फॉरेस्ट लूप है, लेकिन मुझे नहीं पता कि गुरुत्वाकर्षण गणना कैसे करनी है, और यह काम करने लगता है, यह सिर्फ इतना गहन है कि इसे अपने स्वयं के धागे की आवश्यकता है। (यहां तक ​​कि अगर किसी को इन गणनाओं को करने के लिए एक सुपर कुशल तरीका पता है, तो मैं अभी भी जानना चाहूंगा कि मैं इसे बजाय कई सूत्र पर कैसे कर सकता हूं)

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 में बनाया गया है, और इसकी अद्यतन () विधि Game1.cs के भीतर कहा जाता है।

मुझे हर बार गेम अपडेट को चलाने के लिए Gravity.cs में अपनी भौतिकी गणना की आवश्यकता होती है, एक अलग थ्रेड में, ताकि गणना गेम को बहुत कम (0-2) FPS तक धीमा न करे।

मैं इस थ्रेडिंग काम को बनाने के बारे में कैसे जाऊँगा? (यदि किसी के पास है तो बेहतर ग्रहों के गुरुत्वाकर्षण प्रणाली के लिए कोई सुझाव स्वागत योग्य है)

मैं एक पाठ की तलाश में नहीं हूं कि मुझे थ्रेडिंग का उपयोग क्यों नहीं करना चाहिए या इसे गलत तरीके से उपयोग करने के खतरों का सामना नहीं करना चाहिए, मैं इसे कैसे करना है पर सीधे उत्तर की तलाश कर रहा हूं। मैं पहले ही एक घंटा बिता चुका हूं, इस छोटे से प्रश्न को बहुत कम परिणामों के साथ समझ रहा हूं जो मुझे समझ में आया या मददगार थे। मुझे अशिष्टता से बाहर आने का मतलब नहीं है, लेकिन यह हमेशा एक प्रोग्रामिंग नॉब के रूप में कठिन लगता है एक सीधा सार्थक जवाब पाने के लिए, मुझे आमतौर पर एक जवाब मिलता है, इसलिए मैं आसानी से अपने मुद्दे को हल कर सकता हूं अगर मैं इसे समझ पाया, या कोई कह रहा है कि मुझे वह क्यों नहीं करना चाहिए जो मैं करना चाहता हूं और कोई विकल्प नहीं देना (जो सहायक है)।

सहायता के लिए धनयवाद!

संपादित करें : मैंने जो उत्तर प्राप्त किए हैं, उन्हें पढ़ने के बाद, मैं देखता हूं कि आप लोग वास्तव में परवाह करते हैं और केवल एक उत्तर को निकालने की कोशिश नहीं कर रहे हैं जो काम कर सकता है। मैं एक पत्थर से दो पक्षियों को मारना चाहता था (प्रदर्शन में सुधार करना और मल्टीथ्रेडिंग के कुछ मूल बातें सीखना), लेकिन यह प्रतीत होता है कि अधिकांश मुद्दा मेरी गणना में निहित है और प्रदर्शन बढ़ने के लिए यह थ्रेडिंग अधिक परेशानी है। आप सभी को धन्यवाद, मैं आपके उत्तरों के माध्यम से फिर से पढ़ूंगा और आपके समाधान की कोशिश करूंगा जब मैं स्कूल के साथ होऊंगा, फिर से धन्यवाद!


यह क्या करता है [आपका अपडेट थ्रेडिंग सिस्टम ऊपर उल्लिखित है] अब करें (क्या यह काम करता है)? Btw मैं इसे खेल चक्र में ASAP शुरू करूँगा - जैसे कि संस्थाओं को अपडेट करने से पहले।
थोरिनि

2
आपके नेस्टेड लूप के इंटीरियर में ट्रिग कॉल शायद सबसे बड़ी हिट है। यदि आप उन्हें खत्म करने का एक तरीका खोज सकते हैं, तो वह kइस O(n^2)समस्या को बहुत कम कर देगा ।
RBarryYoung

1
वास्तव में ट्रिगर कॉल पूरी तरह से अनावश्यक हैं : आप पहले एक वेक्टर से कोण की गणना करते हैं, फिर उस दूसरी दिशा में इंगित करने वाले दूसरे वेक्टर को उत्पन्न करने के लिए उपयोग करते हैं। फिर आप उस वेक्टर को सामान्य करते हैं, लेकिन चूंकि sin² + cos² ≡ 1यह पहले से ही सामान्यीकृत है! आप बस मूल वेक्टर का उपयोग कर सकते थे जो उन दो वस्तुओं को जोड़ता है जिन्हें आप रुचि रखते हैं, और इसे एक सामान्यीकृत करें। कोई ट्रिगर कॉल जो भी आवश्यक हो।
वामावर्तबूट

क्या XNA पदावनत नहीं है?
19

@yannbane का यह सवाल चर्चा में मददगार नहीं है। और नहीं, XNA की स्थिति पदावनत की किसी भी परिभाषा के अनुरूप नहीं है।
सेठ बतिन

जवाबों:


36

यहाँ आपके पास एक क्लासिक O (n () अल्गोरिथम है। आपकी समस्या के मूल कारण का थ्रेडिंग और इस तथ्य से कोई लेना देना नहीं है कि आपके एल्गोरिथ्म में एक उच्च जटिलता है।

यदि आप पहले "बिग ओ" संकेतन में नहीं आए हैं, तो इसका मूल रूप से मतलब है एन तत्वों पर काम करने के लिए आवश्यक संचालन की संख्या (यह सुपर-सरलीकृत स्पष्टीकरण है)। आपके 100 तत्व आपके लूप के भीतरी भाग को 10000 बार निष्पादित कर रहे हैं

खेल विकास में आप आमतौर पर O (n development ) एल्गोरिदम से बचना चाहते हैं , जब तक कि आपके पास डेटा की एक छोटी (और अधिमानतः तय या छायांकित) मात्रा और बहुत तेज़ एल्गोरिथम न हो।

यदि प्रत्येक इकाई हर दूसरे इकाई को प्रभावित कर रही थी, तो आपको आवश्यकता होगी कि आपको O (n algorithm) एल्गोरिथ्म की आवश्यकता हो। लेकिन ऐसा लगता है कि केवल कुछ इकाइयां वास्तव में बातचीत कर रही हैं (कारण के कारण 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();
}

स्ट्रिंग द्वारा एक शब्दकोश खोज बहुत धीमी है। सीधे Iterating काफी तेज हो जाएगा ।

हालाँकि, कितनी बार आपको वास्तव में नाम से आइटम देखने की आवश्यकता है? उनकी तुलना में आपको उन सभी के माध्यम से कितनी बार चलना चाहिए? यदि आप केवल नाम खोज ही करते हैं, तो अपनी संस्थाओं को एक में संग्रहीत करने पर विचार करें List(उन्हें देंName सदस्य दें) ।

आपके पास वास्तव में मौजूद कोड अपेक्षाकृत तुच्छ है। मैंने इसे प्रोफाइल नहीं किया है, लेकिन मुझे यकीन है कि आपका अधिकांश निष्पादन समय दोहराया शब्दकोश लुकअप में जा रहा है । इस समस्या को ठीक करने से आपका कोड अच्छी तरह से "तेज़ पर्याप्त" हो सकता है।

संपादित करें: अगली सबसे बड़ी समस्या संभवतः कॉल करना है Atan2और फिर तुरंत इसे एक वेक्टर में वापस परिवर्तित करना हैSin और Cos! बस सीधे वेक्टर का उपयोग करें।


अंत में, आइए सूत्रण और अपने कोड में प्रमुख मुद्दों को संबोधित करें:

सबसे पहले और सबसे स्पष्ट रूप से: हर फ्रेम में एक नया धागा न बनाएं!थ्रेड ऑब्जेक्ट्स काफी "भारी" हैं। इसका सबसे सरल समाधान बस उपयोग करना होगाThreadPool इसके बजाय करना होगा।

बेशक, यह इतना आसान नहीं है। चलिए समस्या नंबर दो पर जाते हैं: डेटा को एक साथ दो थ्रेड पर स्पर्श न करें!(उपयुक्त थ्रेड-सुरक्षा बुनियादी ढांचे को जोड़ने के बिना।)

आप मूल रूप से सबसे भयानक तरीके से यहां याददाश्त बढ़ा रहे हैं । यहां थ्रेड-सेफ्टी नहीं है। कई में से कोई एक "gravity.Updateआपके द्वारा शुरू किए जा रहे " थ्रेड्स में अनपेक्षित समय में डेटा को किसी अन्य थ्रेड में उपयोग किया जा सकता है। इस बीच, आपका मुख्य धागा, इन सभी डेटा संरचनाओं को भी नहीं छू पाएगा। मुझे आश्चर्य नहीं होगा अगर यह कोड हार्ड-टू-रेप्रिज़्यूरी मेमोरी एक्सेस उल्लंघन का उत्पादन करता है।

इस धागे को सुरक्षित बनाना कुछ मुश्किल है और यह महत्वपूर्ण प्रदर्शन को ऊपर की ओर जोड़ सकता है जैसे कि यह अक्सर प्रयास के लायक नहीं होता है।


लेकिन, जैसा कि आपने पूछा (ऐसा नहीं) अच्छी तरह से करने के बारे में वैसे भी, आइए उस बारे में बात करते हैं ...

आम तौर पर मैं कुछ सरल अभ्यास करके शुरू करने की सलाह दूंगा, जहां आपका धागा मूल रूप से "आग और भूल" है। ऑडियो खेलना, डिस्क पर कुछ लिखना आदि चीजें जटिल हो जाती हैं जब आपको परिणाम को मुख्य धागे में वापस फीड करना होता है।

आपकी समस्या के मूल रूप से तीन दृष्टिकोण हैं:

1) उन सभी डेटा के चारों ओर ताले लगाएं जो आप थ्रेड्स में उपयोग करते हैं। C # में इसे lockस्टेटमेंट के साथ काफी सरल बनाया गया है।

आम तौर पर आप new objectडेटा के कुछ सेट की रक्षा के लिए विशेष रूप से लॉक करने के लिए (और बनाए रखते हैं!) (यह सुरक्षा कारणों से है जो आम तौर पर केवल सार्वजनिक एपीआई लिखते समय सामने आते हैं - लेकिन अच्छी शैली सभी के लिए)। फिर आपको अपनी लॉक ऑब्जेक्ट को हर जगह लॉक करना होगा जहाँ आप डेटा की सुरक्षा करते हैं!

बेशक, अगर कुछ एक धागे से "बंद" है क्योंकि यह उपयोग में है, और दूसरा धागा इसे एक्सेस करने की कोशिश करता है - तो वह दूसरा धागा तब तक इंतजार करने के लिए मजबूर होगा जब तक कि पहला धागा समाप्त न हो जाए। इसलिए जब तक आप ध्यान से समानांतर में किए जा सकने वाले कार्यों का चयन नहीं करते, आप मूल रूप से एकल-थ्रेडेड प्रदर्शन (या बदतर) प्राप्त कर रहे होंगे।

तो आपके मामले में, ऐसा करने का कोई मतलब नहीं है जब तक कि आप अपने खेल को ऐसा नहीं बना सकते कि कुछ अन्य कोड समानांतर में चलें जो आपके इकाई संग्रह को छू नहीं पाएंगे।

2) डेटा को थ्रेड में कॉपी करें, इसे प्रोसेस करने दें, और जब यह समाप्त हो जाए तो फिर से परिणाम निकाल लें।

वास्तव में आप इसे कैसे लागू करते हैं, इस पर निर्भर करेगा कि आप क्या कर रहे हैं। लेकिन जाहिर है कि इसमें एक संभावित महंगा कॉपी ऑपरेशन (या दो) शामिल होगा जो कि कई मामलों में केवल थ्रेडेड चीजों को करने की तुलना में धीमा होगा।

और, ज़ाहिर है, आपको अभी भी पृष्ठभूमि में कुछ और काम करना होगा, अन्यथा आपका मुख्य धागा बस अपने दूसरे धागे के खत्म होने के इंतजार में बैठा रहेगा, ताकि यह डेटा वापस कॉपी कर सके!

3) थ्रेड-सुरक्षित डेटा संरचनाओं का उपयोग करें।

ये अपने एकल-थ्रेडेड समकक्षों की तुलना में काफी कम हैं और सरल लॉकिंग की तुलना में उपयोग करने के लिए अक्सर कठिन होते हैं। वे तब भी लॉकिंग की समस्याओं को प्रदर्शित कर सकते हैं (प्रदर्शन को एक धागे से कम कर सकते हैं) जब तक कि आप उन्हें सावधानी से उपयोग न करें।


अंत में, क्योंकि यह एक फ्रेम-आधारित सिमुलेशन है, आपको अपने परिणाम प्रदान करने के लिए अन्य थ्रेड्स के लिए मुख्य थ्रेड प्रतीक्षा की आवश्यकता होगी, ताकि फ्रेम का प्रतिपादन किया जा सके और सिमुलेशन जारी रह सके। एक पूर्ण विवरण वास्तव में यहां डालने के लिए बहुत लंबा है, लेकिन मूल रूप से आप सीखना चाहते हैं कि कैसे उपयोग करें Monitor.Waitऔर Monitor.Pulseयहाँ एक लेख है आपको शुरू करने का


मुझे पता है कि मैंने इनमें से किसी भी दृष्टिकोण के लिए विशिष्ट कार्यान्वयन विवरण (अंतिम बिट को छोड़कर) या कोड नहीं दिया है। सबसे पहले, कवर करने के लिए बहुत कुछ होगा । और, दूसरी बात, उनमें से कोई भी अपने कोड पर अपने आप पर लागू नहीं होता है - आपको थ्रेडिंग को जोड़ने की दिशा में अपनी पूरी वास्तुकला को देखने की जरूरत है।

थ्रेडिंग जादुई रूप से आपके पास कोई भी कोड नहीं है - यह आपको उसी समय कुछ और करने देता है!


8
+10 अगर मैं कर सका। हो सकता है कि आप अंतिम वाक्य को एक परिचय के रूप में शीर्ष पर ले जा सकते हैं, क्योंकि यह यहां मूल मुद्दे को सारांशित करता है। यदि आप एक ही समय में कुछ और नहीं करना चाहते हैं तो कोड को दूसरे थ्रेड पर चलाना जादुई रूप से गति प्रदान नहीं करता है। और रेंडरर संभवत: थ्रेड के खत्म होने का इंतजार करता है, लेकिन अगर ऐसा नहीं है (और यह कैसे पता चल सकता है?) तो यह कुछ असंगत भौतिकी अवस्था को आकर्षित कर रहा होगा जिसमें अभी भी कुछ भौतिकी को अद्यतन किया जाना है।
LearnCocos2D

मुझे अच्छी तरह से विश्वास है कि थ्रेडिंग में मेरी ज़रूरत नहीं है, लंबी और ज्ञानवर्धक जानकारी के लिए धन्यवाद! प्रदर्शन में सुधार के लिए, मैंने आपके (और अन्य) सुझावों में बदलाव किए, लेकिन> 60 वस्तुओं के साथ काम करते समय मुझे अभी भी बुरा प्रदर्शन मिल रहा है। मुझे लगता है कि एन-बॉडी सिमुलेशन दक्षता पर एक और सवाल करना मेरे लिए सबसे अच्छा होगा। हालांकि आपको इसके लिए मेरा जवाब मिलेगा। धन्यवाद!
डाकिया

1
आपका स्वागत है, ख़ुशी हुई कि इसने मदद की :) जब आप अपना ताज़ा प्रश्न पोस्ट करते हैं, तो कृपया यहाँ एक लिंक ड्रॉप करें ताकि मैं और कोई भी साथ में, इसे देख सके।
एंड्रयू रसेल

@Postman हालांकि मैं इस बात से सहमत हूं कि यह उत्तर सामान्य रूप से क्या कहता है, मुझे लगता है कि यह पूरी तरह से इस तथ्य को याद करता है कि यह मूल रूप से थ्रेडिंग का लाभ उठाने के लिए सही एल्गोरिदम है। एक कारण है कि वे इस सामान को GPU पर करते हैं और ऐसा इसलिए है क्योंकि यह राइट्स को दूसरे चरण में ले जाने पर एक तुच्छ समानांतर एल्गोरिदम है। सुरक्षित डेटा संरचनाओं को लॉक करने या कॉपी करने या थ्रेड करने की कोई आवश्यकता नहीं है। एक साधारण समानांतर। इसके अलावा और कोई मुद्दों के साथ किया।
Chewy Gumball

@ChewyGumball एक बहुत ही मान्य बिंदु! और, जबकि डाकिया को अपने एल्गोरिथ्म को दो-चरण बनाना होगा, यह यकीनन दो-चरण होना चाहिए। यह इंगित करने योग्य है, हालांकि, यह Parallelओवरहेड के बिना नहीं है, इसलिए यह निश्चित रूप से प्रोफाइल के लिए कुछ है - विशेष रूप से ऐसे छोटे डेटा सेट और (क्या होना चाहिए) कोड का एक अपेक्षाकृत तेज़ टुकड़ा। और, ज़ाहिर है, इस मामले में एल्गोरिथ्म की जटिलता को कम करने के लिए यह अभी भी बेहतर है - इसके बजाय केवल समानता को फेंकना।
एंड्रयू रसेल

22

ठीक है पहली नज़र में कुछ चीजें हैं जिन्हें आपको आज़माना चाहिए। सबसे पहले आपको अपने टकराव की जांच को कम करने की कोशिश करनी चाहिए, आप इसे किसी तरह की स्थानिक संरचना का उपयोग करके कर सकते हैं जैसे कि क्वाडट्री । यह आपको दूसरी फ़ॉरवर्ड काउंट को कम करने की अनुमति देगा, क्योंकि आप केवल पहले वाले इकाइयों को बंद करेंगे।

अपने थ्रेडिंग के बारे में: कोशिश करें कि हर अपडेट मोड़ के लिए एक धागा न बनाएं। यह ओवरहेड शायद अपनी गति बढ़ाने वाली चीजों की तुलना में आपकी गति को धीमा कर रहा है। इसके बजाय एकल टकराव धागा बनाने की कोशिश करें और इसे आपके लिए काम करने दें। मेरे पास कोई ठोस कॉपी-पेस्ट-यह-कोड दृष्टिकोण नहीं है, लेकिन सी # के लिए थ्रेड सिंक्रोनाइजिंग और पृष्ठभूमि कार्यकर्ता के बारे में लेख हैं।

एक और मुद्दा यह है कि फ़ॉरच लूप में आपको ऐसा करने की ज़रूरत नहीं है entityEngine.Entities[e.Key].Textureक्योंकि आप पहले से ही अपने फ़ॉरचैट हेडर में तानाशाही को एक्सेस कर चुके हैं। इसके बजाय आप बस लिख सकते हैं e.Texture। मैं वास्तव में इस के प्रभाव के बारे में नहीं जानता, बस आपको बता देना चाहता था;)

एक आखिरी बात: इस समय आप प्रत्येक इकाई की दोहरी जाँच कर रहे हैं, क्योंकि यह पहले और दूसरे फॉरेक्स लूप में बोली जाती है।

2 संस्थाओं ए और बी के साथ उदाहरण:

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

हालांकि यह एक संभव दृष्टिकोण है, हो सकता है कि आप ए और बी को एक बारी में संभाल सकते हैं, आपकी टक्कर की आधी जांच को छोड़ दें

आशा है कि यह आपके लिए शुरू हो जाता है =)

पुनश्च: यहां तक ​​कि अगर आपने कहा कि आप इसे सुनना नहीं चाहते हैं: टकराव का पता उसी धागे में रखने की कोशिश करें और बस इसे पर्याप्त गति दें। इसे फैलाना एक अच्छे विचार की तरह लगता है लेकिन इसके साथ ही नरक की तरह सिंक्रनाइज़ करने की आवश्यकता होती है। यदि आप टकराव की जांच आपके अपडेट से धीमी है (इसे फैलाने का कारण) तो आपको ग्लिच और त्रुटियां मिलेंगी, क्योंकि जहाजों के पहले से ही चले जाने और इसके उलट होने के बाद टक्कर चालू हो जाएगी। मैं आपको हतोत्साहित नहीं करना चाहता, यह सिर्फ एक व्यक्तिगत अनुभव है।

EDIT1: QuadTree ट्यूटोरियल (जावा) के साथ लिंक: http://gamedev.tutsplus.com/tutorials/implementation/quick-tip-use-quadtrees-to-detect-likely-collisions-in-2d-space/


10
गुरुत्वाकर्षण सिमुलेशन के लिए क्वाड / ऑक्टर्स का उपयोग करने के बारे में अच्छी बात यह है कि, केवल दूर के कणों को अनदेखा करने के बजाय, आप अपने पेड़ की प्रत्येक शाखा में कुल द्रव्यमान और सभी कणों के द्रव्यमान के केंद्र को स्टोर कर सकते हैं और औसत गुरुत्वाकर्षण प्रभाव की गणना करने के लिए इसका उपयोग कर सकते हैं इस शाखा के सभी कण अन्य, दूर के कणों पर। यह बार्न्स-हट एल्गोरिथ्म के रूप में जाना जाता है , और यह वही है जो पेशेवरों का उपयोग करता है
इल्मरी करोनें

10

ईमानदारी से, पहला बात जो आपको करनी चाहिए वह एक बेहतर एल्गोरिदम पर स्विच है।

अपने सिमुलेशन को समानांतर करते हुए, यहां तक ​​कि सर्वोत्तम संभव मामले में, अपने सिस्टम पर उपलब्ध सीपीयू × धागे प्रति कोर सीपीयू × कोर की संख्या के बराबर कारक द्वारा इसे गति दें - अर्थात कहीं आधुनिक पीसी के लिए 4 से 16 के बीच। (अपने कोड को GPU पर ले जाने से बहुत कुछ मिल सकता है , अतिरिक्त विकास जटिलता की लागत और कम प्रति-धागा आधारभूत गणना गति से अधिक प्रभावशाली बनता है कारकों।) एक ओ (n²) एल्गोरिथ्म के साथ, अपने उदाहरण कोड की तरह, यह आप जाने हैं वर्तमान में आपके पास जितने कण हैं, 2 से 4 गुना तक उपयोग करें।

इसके विपरीत, एक अधिक कुशल एल्गोरिदम पर स्विच करने से, आसानी से आपके सिमुलेशन को गति मिल सकती है, कहते हैं, 100 से 10000 का एक कारक (संख्या पूरी तरह से निर्देशित)। अच्छे n- बॉडी सिमुलेशन एल्गोरिदम की समय जटिलता, स्थानिक उपविभाजन तराजू का उपयोग करते हुए लगभग O (n log n) के रूप में है, जो "लगभग रैखिक" है, ताकि आप कणों की संख्या में वृद्धि के लगभग समान कारक की उम्मीद कर सकें, जिसे आप संभाल सकते हैं। इसके अलावा, यह अभी भी केवल एक धागे का उपयोग कर रहा है, इसलिए उस के शीर्ष पर समानांतरीकरण के लिए अभी भी जगह होगी ।

वैसे भी, जैसा कि अन्य जवाबों में उल्लेख किया गया है, बड़ी संख्या में अंतःक्रियात्मक कणों का कुशलता से अनुकरण करने की सामान्य चाल उन्हें चतुष्कोण में व्यवस्थित करना है। (2 डी में) या एक octree (3 डी में)। विशेष रूप से, गुरुत्वाकर्षण के अनुकरण के लिए, आप जिस मूल एल्गोरिथ्म का उपयोग करना चाहते हैं, वह बार्न्स-हट सिमुलेशन एल्गोरिथ्म है , जिसमें आप अपने क्वाड / ऑक्ट्री के प्रत्येक सेल में निहित सभी कणों के कुल द्रव्यमान (और द्रव्यमान का केंद्र) को स्टोर करते हैं और उस सेल में कणों के औसत गुरुत्वाकर्षण प्रभाव को लगभग दूसरे, दूर के कणों का अनुमान लगाने के लिए उपयोग करें।

आप इसके लिए Googling द्वारा बार्न्स-हट एल्गोरिथ्म पर बहुत सारे विवरण और ट्यूटोरियल पा सकते हैं , लेकिन यहाँ एक अच्छा और सरल है जो आपको आरंभ करने के लिए है , जबकि यहाँ आकाशगंगा टकरावों के GPU सिमुलेशन के लिए उपयोग किए गए एक उन्नत कार्यान्वयन का वर्णन है


6

एक और अनुकूलन उत्तर जिसका धागे से कोई लेना-देना नहीं है। उसके लिए माफ़ करना।

आप प्रत्येक जोड़ी की दूरी () की गणना कर रहे हैं। इसमें एक वर्गमूल लेना शामिल है, जो धीमा है। इसमें वास्तविक आकार प्राप्त करने के लिए कई ऑब्जेक्ट लुकअप भी शामिल हैं।

आप इसके बजाय डिस्ट्रेसक्वेर्ड () फ़ंक्शन का उपयोग करके इसे ऑप्टिमाइज़ कर सकते हैं। अधिकतम दूरी को रोकें जिस पर कोई भी दो ऑब्जेक्ट्स इंटरैक्ट कर सकते हैं, इसे स्क्वायर कर सकते हैं, और फिर डिस्ट्रेसक्वायर्ड () के साथ इसकी तुलना कर सकते हैं। यदि और केवल अगर दूरी चुकता अधिकतम के भीतर है, तो वर्गमूल लें और वास्तविक वस्तु आकारों के साथ तुलना करें।

संपादित करें : यह अनुकूलन अधिकतर तब होता है जब आप टक्करों के लिए परीक्षण कर रहे होते हैं, जिस पर मैंने अब गौर किया है कि वास्तव में आप क्या कर रहे हैं (हालांकि आप निश्चित रूप से कुछ बिंदु पर होंगे)। यह तब भी आपकी स्थिति पर लागू हो सकता है, यदि सभी कण समान आकार / द्रव्यमान के हों।


हाँ। यह समाधान ठीक हो सकता है (केवल नगण्य सटीकता हानि), लेकिन वस्तुओं के द्रव्यमान में बहुत अंतर होने पर मुसीबत में पड़ जाता है। यदि कुछ वस्तुओं का द्रव्यमान बहुत बड़ा है जबकि कुछ वस्तुओं का द्रव्यमान बहुत छोटा है, तो उचित के लिए अधिकतम दूरी अधिक है। उदा। एक छोटे धूल कण पर पृथ्वी के गुरुत्वाकर्षण का प्रभाव पृथ्वी के लिए नगण्य है, लेकिन धूल के कण के लिए (काफी दूरी के लिए)। लेकिन वास्तव में एक ही दूरी पर दो धूल के कण एक दूसरे को महत्वपूर्ण रूप से प्रभावित नहीं करते हैं।
एसडीवारफ्स

वास्तव में यह बहुत अच्छी बात है। मैं इसे टक्कर की परीक्षा के रूप में गलत मानता हूं, लेकिन यह वास्तव में विपरीत कर रहा है: यदि वे स्पर्श नहीं कर रहे हैं तो कण एक दूसरे को प्रभावित करते हैं।
एलिस्टेयर बुक्सटन

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 एफपीएस प्राप्त करते हैं, तो इसका मतलब यह होगा कि आपका प्रदर्शन इतना खराब है, कि आप केवल प्रति सेकंड आंतरिक लूप के 180 पुनरावृत्तियों को प्राप्त करते हैं।

मैं आपको निम्नलिखित करने का सुझाव देता हूं:

  1. तैयारी / बेंचमार्किंग: निश्चित रूप से यह जानने के लिए कि यह दिनचर्या समस्या है, एक छोटा बेंचमार्क रूटीन लिखें। यह Update()कई बार जैसे कि 1000 बार और समय को मापने के लिए गुरुत्वाकर्षण की विधि को निष्पादित करेगा । यदि आप 100 वस्तुओं के साथ 30 एफपीएस प्राप्त करना चाहते हैं, तो आपको 100 वस्तुओं का अनुकरण करना चाहिए और 30 निष्पादन के लिए समय को मापना चाहिए। यह 1 सेकंड से कम होना चाहिए। इस तरह के बेंचमार्क का उपयोग उचित अनुकूलन करने के लिए आवश्यक है। और आप शायद विपरीत को प्राप्त करेंगे और कोड को धीमा बना देंगे क्योंकि आपको लगता है कि यह तेज होना चाहिए ... इसलिए मैं वास्तव में आपको ऐसा करने के लिए प्रोत्साहित करता हूं!

  2. ऑप्टिमाइज़ेशन: जब आप O (N meaning) प्रयास समस्या के बारे में बहुत कुछ नहीं कर सकते (अर्थ: गणना का समय अनुकारित वस्तुओं N की संख्या के साथ चतुर्थांश बढ़ जाता है), आप कोड को ही सुधार सकते हैं।

    a) आप अपने कोड के भीतर बहुत सारे "साहचर्य सरणी" (डिक्शनरी) लुकअप का उपयोग करते हैं। ये धीमे हैं! उदाहरण के लिए entityEngine.Entities[e.Key].Position। तुम सिर्फ इस्तेमाल नहीं कर सकते e.Value.Position? यह एक खोज बचाता है। आप पूरे आंतरिक लूप में हर जगह ई और ई 2 द्वारा संदर्भित वस्तुओं के गुणों का उपयोग करने के लिए ऐसा करते हैं ... इसे बदलें! ख) आप लूप के भीतर एक नया वेक्टर बनाते हैं ) लूप के भीतर। यदि आपकी सटीकता को वास्तव में वास्तव में सटीक होने की आवश्यकता नहीं है, तो आप इसके बजाय लुकअप टेबल का उपयोग करने का प्रयास कर सकते हैं। ऐसा करने के लिए आप अपने मान को एक परिभाषित सीमा तक मापते हैं, इसे पूर्णांक मान पर राउंड करते हैं और इसे पूर्व-परिकलित परिणामों की तालिका में देखते हैं। अगर आपको इसके लिए मदद चाहिए, तो पूछिए। d) आप अक्सर उपयोग करते हैं । आप इसे पूर्व-गणना कर सकते हैं और परिणाम को स्टोर कर सकते हैं या यदि यह हमेशा एक धनात्मक पूर्णांक मान है - तो आप बिट ऑपरेशन का उपयोग कर सकते हैंnew Vector2( .... ) । सभी "नए" कॉल कुछ मेमोरी आवंटन (और बाद में: डीलक्लोलेशन) को निहित करते हैं। ये शब्दकोशों के लुक की तुलना में बहुत धीमी हैं। यदि आपको केवल इस वेक्टर की अस्थायी रूप से आवश्यकता है, तो इसे छोरों के बाहर आवंटित करें और -reuse- यह अपने मूल्यों को नए ऑब्जेक्ट बनाने के बजाय नए मानों को पुन: व्यवस्थित करके। ग) आप बहुत सारे त्रिकोणमितीय कार्यों का उपयोग करते हैं (जैसेatan2 औरcos.Texture.Width / 2.Texture.HalfWidth>> 1 दो से विभाजित करने के का ।

एक समय में केवल एक परिवर्तन करें और बेंचमार्क द्वारा परिवर्तन को देखें कि यह आपके रनटाइम को कैसे प्रभावित करता है! शायद एक बात अच्छी है जबकि दूसरा विचार बुरा था (यहां तक ​​कि मैंने उन्हें ऊपर का प्रस्ताव दिया था!) ​​...

मुझे लगता है कि ये अनुकूलन कई धागों का उपयोग करके बेहतर प्रदर्शन प्राप्त करने की कोशिश से बेहतर होंगे! आपको थ्रेड्स को समन्वयित करने में बहुत परेशानी होगी, इसलिए वे दूसरों के मूल्यों को अधिलेखित नहीं करेंगे। इसके अलावा वे समान स्मृति क्षेत्रों तक भी पहुंचने पर संघर्ष करेंगे। यदि आप इस काम के लिए 4 सीपीयू / थ्रेड्स का उपयोग करते हैं, तो आप फ्रेम दर के लिए केवल 2 से 3 की गति की उम्मीद कर सकते हैं।


0

क्या आप ऑब्जेक्ट निर्माण लाइनों के बिना इसे फिर से बनाने में सक्षम हैं?

वेक्टर 2 बल = नया वेक्टर 2 ();

वेक्टर 2 VecForce = नया वेक्टर 2 ((फ्लोट) मैथ.कोस (कोण), (फ्लोट) मैथ.सिन (कोण));

यदि आप प्रत्येक बार दो नई ऑब्जेक्ट बनाने के बजाय इकाई में बल मान रख सकते हैं, तो इससे प्रदर्शन को बेहतर बनाने में मदद मिल सकती है।


4
Vector2XNA में एक मूल्य प्रकार है । इसमें कोई जीसी ओवरहेड नहीं है और निर्माण ओवरहेड नगण्य है। यह समस्या का स्रोत नहीं है।
एंड्रयू रसेल

@ और रसेल: मुझे यकीन नहीं है, लेकिन क्या वास्तव में अभी भी मामला है अगर आप "नया वेक्टर 2" का उपयोग करते हैं? यदि आप "नया" के बिना वेक्टर 2 (....) का उपयोग करते हैं, तो यह संभवतः अलग होगा।
SDwarfs

1
@StefanK। C # में आप ऐसा नहीं कर सकते। नया चाहिए। क्या आप C ++ की सोच रहे हैं?
MrKWatkins
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.