एक O (N ^ 2) फ़ंक्शन में सुधार करना (अन्य सभी संस्थाओं पर चलने वाली सभी इकाइयाँ)


21

थोड़ी सी पृष्ठभूमि, मैं एक विकास गेम को कोड ++ में एक दोस्त के साथ कोडिंग कर रहा हूं , इकाई प्रणाली के लिए ENTT का उपयोग कर रहा हूं। जीव 2 डी मानचित्र में घूमते हैं, साग या अन्य जीव खाते हैं, प्रजनन करते हैं और उनके लक्षण बदलते हैं।

इसके अतिरिक्त, जब खेल वास्तविक समय में चलाया जाता है, तो प्रदर्शन ठीक रहता है (60fps कोई समस्या नहीं), लेकिन मैं चाहता हूं कि किसी भी महत्वपूर्ण बदलाव को देखने के लिए 4h का इंतजार न करने के लिए इसे गति देने में सक्षम होना चाहिए। इसलिए मैं इसे जल्दी से जल्दी प्राप्त करना चाहता हूं।

मैं प्राणियों के लिए अपने भोजन को खोजने के लिए एक कुशल विधि खोजने के लिए संघर्ष कर रहा हूँ। प्रत्येक प्राणी को सबसे अच्छे भोजन की तलाश करनी चाहिए जो उनके लिए पर्याप्त है।

उदाहरण के खेल का स्क्रीनशॉट

यदि यह खाना चाहता है, तो केंद्र में चित्रित प्राणी को अपने चारों ओर एक 149.64 त्रिज्या (इसकी दृश्य दूरी) और न्यायाधीश को देखना चाहिए कि वह किस भोजन का पीछा करना चाहिए, जो पोषण, दूरी और प्रकार (मांस या पौधे) पर आधारित है। ।

प्रत्येक प्राणी को अपना भोजन खोजने के लिए जिम्मेदार फ़ंक्शन रन-टाइम का लगभग 70% खा रहा है। वर्तमान में इसका लेखन कैसे सरल है, यह कुछ इस तरह से है:

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)
}

यह कार्य भोजन की तलाश करने वाले प्रत्येक प्राणी के लिए हर टिक को चलाया जाता है, जब तक कि जीव भोजन नहीं पाता है और अवस्था बदलता रहता है। प्राणियों के लिए हर बार नए खाद्य स्पॉन्ज भी चलाए जाते हैं, जो पहले से ही एक निश्चित भोजन का पीछा करते हैं, यह सुनिश्चित करने के लिए कि हर कोई उन्हें उपलब्ध सर्वोत्तम भोजन के बाद जाता है।

मैं इस प्रक्रिया को और अधिक कुशल बनाने के बारे में विचारों के लिए खुला हूं। मैं से जटिलता को कम करना पसंद करूंगा , लेकिन मुझे नहीं पता कि क्या यह संभव है।O(N2)

जिस तरह से मैं पहले से ही इसे बेहतर बनाता हूं, वह all_entities_with_food_valueसमूह को क्रमबद्ध करके किया जाता है ताकि जब कोई प्राणी खाने के लिए भोजन पर ध्यान केंद्रित करे, तो वह वहां रुक जाए। किसी भी अन्य सुधार स्वागत से अधिक हैं!

संपादित करें: उत्तर के लिए आप सभी का धन्यवाद! मैंने कई चीजों को विभिन्न उत्तरों से लागू किया है:

मैंने सबसे पहले और बस इसे बनाया इसलिए दोषी कार्य केवल एक बार हर पांच टिक्स को चलाता है, इससे खेल लगभग 4 गुना तेज हो गया, जबकि खेल के बारे में कुछ भी नहीं बदला।

उसके बाद मैंने भोजन की खोज प्रणाली में संग्रहीत भोजन के साथ सरणी को उसी टिक में रखा जिसे वह चलाता है। इस तरह मुझे केवल उस भोजन की तुलना करने की आवश्यकता है जो प्राणी नए खाद्य पदार्थों के साथ पीछा कर रहा है।

अंत में, अंतरिक्ष विभाजन में अनुसंधान और बीवीएच और क्वाडट्री पर विचार करने के बाद, मैं बाद के साथ चला गया, क्योंकि मुझे लगता है कि यह मेरे मामले के लिए बहुत सरल और बेहतर है। मैं इसे बहुत जल्दी लागू करता हूं और इसने बहुत बेहतर प्रदर्शन किया है, भोजन की खोज मुश्किल से किसी भी समय होती है!

अब प्रतिपादन यह है कि क्या मेरी गति धीमी हो रही है, लेकिन यह एक और दिन के लिए एक समस्या है। आप सभी को धन्यवाद!


2
क्या आपने एक साथ चलने वाले कई CPU कोर पर कई थ्रेड्स के साथ प्रयोग किया है?
एड मार्टी

6
आपके पास औसतन कितने जीव हैं? ऐसा लगता नहीं है कि स्नैपशॉट को देखते हुए यह उच्च है। यदि ऐसा हमेशा होता है, तो अंतरिक्ष विभाजन बहुत मदद नहीं करेगा। क्या आपने हर टिक पर इस फ़ंक्शन को नहीं चलाने पर विचार किया है ? आप इसे हर बार चला सकते हैं, कहते हैं, 10 टिक। अनुकार के परिणाम गुणात्मक रूप से नहीं बदलने चाहिए।
तुरहम

4
क्या आपने खाद्य मूल्यांकन के सबसे महंगे हिस्से का पता लगाने के लिए कोई विस्तृत रूपरेखा तैयार की है? समग्र जटिलता को देखने के बजाय, शायद आपको यह देखने की ज़रूरत है कि क्या कुछ विशिष्ट गणना या मेमोरी संरचना एक्सेस है जो आपको घुट रही है।
1892 में हरबेक

एक भोला सुझाव: आप ओ (एन ^ 2) के बजाय एक क्वाडट्री या संबंधित डेटा संरचना का उपयोग कर सकते हैं जो आप अभी कर रहे हैं।
सियारिया

3
जैसा कि @ हर्बेक ने सुझाव दिया था, मैं यह देखने के लिए गहराई से खुदाई करूंगा कि उस समय में लूप में कहां खर्च किया जा रहा है। यदि यह दूरी के लिए वर्गमूल गणना है, तो उदाहरण के लिए, आप पहले शेष उम्मीदवारों पर महंगा sqrt करने से पहले XY कोआर्डर्स की तुलना करने में सक्षम हो सकते हैं। जोड़ना if (food.x>creature.x+149.64 or food.x<creature.x-149.64) continue;"जटिल" भंडारण संरचना को लागू करने की तुलना में आसान होना चाहिए यदि यह पर्याप्त रूप से अच्छा है। (संबंधित: यदि आप अपने आंतरिक लूप में कोड का थोड़ा और पोस्ट करते हैं तो यह हमारी मदद कर सकता है)
एसी

जवाबों:


34

मुझे पता है कि आप इसे टकराव के रूप में नहीं मानते हैं, हालांकि आप जो कर रहे हैं वह सभी भोजन के साथ प्राणी पर केंद्रित एक चक्र से टकरा रहा है।

आप वास्तव में भोजन की जांच नहीं करना चाहते हैं जो आप जानते हैं कि दूर है, केवल वही है जो पास है। टक्कर अनुकूलन के लिए यह सामान्य सलाह है। मैं टकरावों का अनुकूलन करने के लिए तकनीकों की खोज करने के लिए प्रोत्साहित करना चाहूंगा, और खोज करते समय अपने आप को C ++ तक सीमित नहीं करूंगा।


भोजन खोजने वाला प्राणी

आपके परिदृश्य के लिए, मैं दुनिया को ग्रिड पर रखने का सुझाव दूंगा। उन मंडलियों के कम से कम त्रिज्याओं को बनाएं जिन्हें आप टकराना चाहते हैं। तब आप उस एक कोशिका को चुन सकते हैं जिस पर प्राणी स्थित है और उसके आठ पड़ोसी हैं और केवल नौ कोशिकाओं तक ही खोज कर सकते हैं।

ध्यान दें : आप छोटे सेल बना सकते हैं, जिसका अर्थ होगा कि आप जो सर्कल खोज रहे हैं वह अपरिपक्व पड़ोसियों से आगे बढ़ेगा, जिससे आपको वहां पुनरावृत्त होने की आवश्यकता होगी। हालाँकि, यदि समस्या यह है कि बहुत अधिक भोजन है, तो छोटी कोशिकाएँ कुल मिलाकर कम खाद्य संस्थाओं पर निर्भर हो सकती हैं, जो कुछ बिंदु पर आपके पक्ष में बदल जाती हैं। यदि आपको संदेह है कि यह मामला है, तो परीक्षण करें।

यदि भोजन नहीं चलता है, तो आप निर्माण पर खाद्य संस्थाओं को ग्रिड में जोड़ सकते हैं, ताकि आपको यह पता लगाने की आवश्यकता न हो कि सेल में कौन सी संस्थाएं हैं। इसके बजाय आप सेल को क्वेरी करते हैं और इसकी सूची है।

यदि आप कोशिकाओं के आकार को दो की शक्ति बनाते हैं, तो आप उस सेल को पा सकते हैं जिस पर जीव अपने निर्देशांक को काटकर बस स्थित है।

आप निकटतम दूरी खोजने के लिए तुलना करते समय स्क्वर्ड दूरी के साथ काम कर सकते हैं (उर्फ स्कवार्ट नहीं करते हैं)। कम sqrt संचालन का अर्थ है तेजी से निष्पादन।


नया भोजन जोड़ा

जब नया भोजन जोड़ा जाता है, केवल आस-पास के जीवों को जागृत करने की आवश्यकता होती है। यह एक ही विचार है, इसके अलावा अब आपको कोशिकाओं में प्राणियों की सूची प्राप्त करने की आवश्यकता है।

बहुत अधिक दिलचस्प बात यह है कि यदि आप जीव में एनोटेट करते हैं तो यह उस भोजन से कितनी दूर है, जिसका पीछा कर रहा है ... आप सीधे उस दूरी की जांच कर सकते हैं।

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

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


अनुकरण अनुकरण

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

यदि आप अधिक भोजन जोड़ने तक का समय भी जानते हैं ... और उम्मीद है कि आपके पास प्रजनन और मृत्यु के लिए समान पूर्वानुमान है, तो आप अगली घटना (या तो जोड़ा भोजन या एक प्राणी खाने) के लिए समय जानते हैं।

उस क्षण को छोड़ दें। आपको घूमने वाले जीवों को अनुकरण करने की आवश्यकता नहीं है।


1
"और केवल वहाँ खोजें।" और तत्काल पड़ोसी कोशिकाएं - कुल मिलाकर 9 कोशिकाएं। क्यों 9? क्योंकि क्या होगा अगर प्राणी एक कोशिका के कोने में सही है।
UKMonkey

1
@UKMonkey "कोशिकाओं को कम से कम उन हलकों के त्रिज्या से बनाएं जिन्हें आप टकराना चाहते हैं", यदि कोशिका पक्ष त्रिज्या है और जीव कोने में है ... ठीक है, मुझे लगता है कि आपको केवल उस मामले में चार खोजने की आवश्यकता है। हालाँकि, निश्चित रूप से, हम कोशिकाओं को छोटा बना सकते हैं, जो कि बहुत अधिक भोजन और बहुत कम जीवों के लिए उपयोगी हो सकता है। संपादित करें: मैं स्पष्ट करूँगा।
थरोट

2
ज़रूर - अगर आप अतिरिक्त कोशिकाओं में खोज करना चाहते हैं, तो काम करना चाहते हैं ... लेकिन यह देखते हुए कि अधिकांश कोशिकाओं में भोजन नहीं दिया जाएगा (दी गई छवि से); यह केवल 9 कोशिकाओं को खोजने के लिए तेज़ होगा, जो आपको खोजने के लिए 4 की आवश्यकता है।
UKMonkey

@UKMonkey जिसके कारण मैंने शुरू में इसका उल्लेख नहीं किया।
थरोट

16

आपको जटिलता को कम करने के लिए बीवीएच जैसे अंतरिक्ष विभाजन एल्गोरिथ्म को अपनाना चाहिए । अपने मामले के लिए विशिष्ट होने के लिए, आपको एक पेड़ बनाने की ज़रूरत है जिसमें अक्ष-संरेखित बाउंडिंग बॉक्स होते हैं जिसमें भोजन के टुकड़े होते हैं।

एक पदानुक्रम बनाने के लिए, AABBs में भोजन के टुकड़े को एक दूसरे के करीब रखें, फिर उन AABB को बड़े AABB में रखें, फिर से, उनके बीच की दूरी से। ऐसा तब तक करें जब तक आपके पास रूट नोड न हो।

पेड़ का उपयोग करने के लिए, पहले एक रूट नोड के खिलाफ एक सर्कल-एएबीबी चौराहे का परीक्षण करें, फिर यदि टक्कर होती है, तो प्रत्येक लगातार नोड के बच्चों के खिलाफ परीक्षण करें। अंत में आपके पास भोजन के टुकड़ों का एक समूह होना चाहिए।

आप AABB.cc लाइब्रेरी का उपयोग भी कर सकते हैं ।


1
यह वास्तव में एन लॉग एन में जटिलता को कम करेगा, लेकिन विभाजन करना भी महंगा होगा। यह देखने के लिए कि मुझे हर टिक का विभाजन करने की आवश्यकता होगी (क्योंकि जीव हर टिक को हिलाता है) क्या यह अभी भी इसके लायक होगा? क्या विभाजन को कम करने में मदद करने के लिए अक्सर समाधान होते हैं?
एलेक्जेंडर रोड्रिग्स

3
@AlexandreRodrigues आपको पूरे पेड़ को हर टिक के पुनर्निर्माण की ज़रूरत नहीं है, केवल उन भागों को अपडेट करें जो चलते हैं, और केवल तब जब कुछ विशेष AABB कंटेनर के बाहर जाता है। प्रदर्शन को और बेहतर बनाने के लिए, आप नोड्स (बच्चों के बीच कुछ जगह छोड़कर) को फेटना चाह सकते हैं ताकि आपको लीफ अपडेट पर पूरी शाखा का पुनर्निर्माण न करना पड़े।
Ocelot

6
मुझे लगता है कि बीवीएच यहां बहुत जटिल हो सकता है - हैश टेबल के रूप में कार्यान्वित एक समान ग्रिड काफी अच्छा है।
स्टीवन

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

2

जबकि वास्तव में वर्णित अंतरिक्ष विभाजन विधियाँ समय को कम कर सकती हैं आपकी मुख्य समस्या केवल लुकअप नहीं है। इसके द्वारा आपके द्वारा किए जाने वाले लुकअप की मात्रा आपके कार्य को धीमा कर देती है। तो आपके आंतरिक लूप का अनुकूलन, लेकिन आप बाहरी लूप को भी अनुकूलित कर सकते हैं।

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

इसके बजाय आपको प्रयास करना चाहिए, यदि संभव हो, तो प्रत्येक कार्रवाई को पूरा करने के लिए हल करने के लिए इसे एक कतार में रखें और उन बुलबुला घटनाओं को बाहर करने दें, इससे कतार में बदलाव हो सकता है लेकिन यह ठीक है। इसे असतत घटना सिमुलेशन कहा जाता है। यदि आप अपने सिमुलेशन को इस तरह से लागू कर सकते हैं तो आप एक बड़े आकार के स्पीडअप की तलाश कर रहे हैं, जिससे आप बेहतर स्पेस विभाजन लुकअप से प्राप्त कर सकते हैं।

पिछले कैरियर में बिंदु को रेखांकित करने के लिए मैंने कारखाना सिमुलेटर बनाया। हमने एक घंटे से भी कम समय में इस विधि के साथ प्रत्येक स्तर पर बड़े कारखानों / हवाई अड्डों के संपूर्ण सामग्री प्रवाह का अनुकरण किया। जबकि टाइमस्टेप आधारित सिमुलेशन वास्तविक समय की तुलना में केवल 4-5 गुना तेजी से अनुकरण कर सकता है।

इसके अलावा एक बहुत कम लटके हुए फल के रूप में अपने सिमुलेशन से अपने ड्राइंग रूटीन को डिकूप करने पर विचार करें। यद्यपि आपका अनुकरण सरल है, फिर भी कुछ चीजों को खींचने का कुछ ओवरहेड है। इससे भी बदतर है कि डिस्प्ले ड्राइवर आपको प्रति सेकंड x अपडेट तक सीमित कर सकता है जबकि वास्तव में आपके प्रोसेसर 100 गुना तेजी से काम कर सकते हैं। यह uderlines रूपरेखा की आवश्यकता को बताता है।


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

1

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

तथाकथित फॉर्च्यून का एल्गोरिथ्म आपके लिए Nlog (N) में है, और उस पर विकी पृष्ठ में इसे लागू करने के लिए छद्म कोड है। मुझे यकीन है कि वहाँ भी पुस्तकालय कार्यान्वयन हैं। https://en.wikipedia.org/wiki/Fortune%27s_algorithm


GDSE में आपका स्वागत है और उत्तर देने के लिए धन्यवाद। ओपी की स्थिति पर आप इसे कैसे लागू करेंगे? समस्या वर्णन कहता है कि एक इकाई को अपने दृश्य दूरी के भीतर सभी भोजन पर विचार करना चाहिए और सबसे अच्छे का चयन करना चाहिए। एक पारंपरिक वोरोनोई रेंज फूड में शामिल होगा जो किसी अन्य इकाई के करीब था। मैं यह नहीं कह रहा हूं कि एक वोरोनॉय काम नहीं करेगा, लेकिन यह आपके विवरण से स्पष्ट नहीं है कि ओपी को समस्या के लिए किसी एक का उपयोग कैसे करना चाहिए।
पिकालेक

मुझे यह विचार पसंद है, मैं इसे विस्तार से देखना चाहूंगा। आप वोरोनोई आरेख (स्मृति डेटा संरचना में) का प्रतिनिधित्व कैसे करते हैं? आप इसे कैसे क्वेरी करते हैं?
थारोट

@Theraot आपको वोरोनोई आरेख की आवश्यकता नहीं है बस उसी स्वीपलाइन विचार को छेड़ना है।
पूजा

-2

सबसे आसान समाधान एक भौतिकी इंजन को एकीकृत करना और केवल टकराव का पता लगाने वाले एल्गोरिथ्म का उपयोग करना होगा। बस प्रत्येक इकाई के चारों ओर एक घेरा / गोला बनाएँ, और भौतिकी इंजन को टक्करों की गणना करने दें। 2D के लिए मैं Box2D या चिपमंक और 3 डी के लिए बुलेट का सुझाव दूंगा

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

  • व्यापक चरण का पता लगाना: इस चरण का लक्ष्य उन वस्तुओं के जोड़े की सूची प्राप्त करना है जो टकरा सकते हैं, जितनी जल्दी हो सके। दो सामान्य विकल्प हैं:
    • स्वीप और प्रून : एक्स अक्ष के साथ बाउंडिंग बॉक्स को सॉर्ट करें, और उन जोड़े की वस्तुओं को चिह्नित करें जो प्रतिच्छेद करते हैं। हर दूसरे अक्ष के लिए दोहराएँ। यदि एक उम्मीदवार जोड़ी सभी परीक्षण पास करती है, तो यह अगले चरण में जाता है। यह एल्गोरिथम अस्थायी सामंजस्य का फायदा उठाने में बहुत अच्छा है: आप क्रमबद्ध संस्थाओं की सूची रख सकते हैं और उन्हें हर फ्रेम को अपडेट कर सकते हैं, लेकिन जैसा कि वे लगभग हल कर रहे हैं, यह बहुत तेज़ होगा। यह स्थानिक सुसंगतता का भी शोषण करता है: क्योंकि संस्थाओं को स्थानिक क्रम में क्रमबद्ध किया जाता है, जब आप टकराव की जाँच कर रहे होते हैं, तो आप जैसे ही एक इकाई से टकराते हैं, रुक सकते हैं, क्योंकि अगले सभी आगे और दूर होंगे।
    • द्विघात, अष्टक और ग्रिड जैसे स्थानिक विभाजन डेटा संरचनाएं। ग्रिड को लागू करना आसान है, लेकिन इकाई का घनत्व कम होने पर, और बेकार जगह के लिए लागू करना बहुत मुश्किल हो सकता है। स्थैतिक स्थानिक पेड़ों को भी लागू करना आसान है, लेकिन जगह में संतुलन या अपडेट करना मुश्किल है, इसलिए आपको प्रत्येक फ्रेम को फिर से बनाना होगा।
  • संकीर्ण चरण: व्यापक चरण पर पाए जाने वाले उम्मीदवार जोड़े को और अधिक सटीक एल्गोरिदम के साथ आगे परीक्षण किया जाता है।
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.