ओपनसीएल: बहुमुखी प्रतिभा और उच्च प्रदर्शन या यह इतना सरल नहीं है?

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

प्रस्तावना


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

इस बिंदु पर GPU पर गणना के क्षेत्र में इतिहास और मामलों की स्थिति के बारे में एक बड़ा पैराग्राफ था, लेकिन समस्याओं का वर्णन करने के बाद पोस्ट बहुत लंबा हो गया और पैराग्राफ गंभीर रूप से कट गया (उम्मीद है कि यह अगले भाग में वापस आ जाएगा)। इसलिए, हम तुरंत इस बात पर ध्यान देंगे कि जीपीयू में पोर्ट किए गए एल्गोरिदम हमेशा तेजी से काम क्यों नहीं करते हैं, अर्थात्। सीपीयू के सापेक्ष वादा किए गए 20X-100X के बजाय 0.5X-10X प्रदर्शन लाभ का अभ्यास करें (अन्यथा प्रत्येक एप्लिकेशन ने इसका इस्तेमाल किया होगा)।

कितना धीरे-धीरे है?


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

पहली सीमा: 32 सूत्र (ताना) हमेशा एक आदेश पर अमल करते हैं

यदि इस कमांड से पहले कहीं शाखा थी और धागे अलग-अलग तरीकों से गए थे, तो GPU क्रमिक रूप से दोनों शाखाओं को निष्पादित करेगा।
इस प्रकार, किसी विशेष मामले के लिए गणनाओं को सरल बनाने का प्रयास (यदि समस्या का एक सामान्य और संक्षिप्त समाधान ज्ञात हो) से तेज गणनाएं नहीं हो सकती हैं (जो हमेशा सीपीयू पर होती हैं), लेकिन सामान्य और विशेष मामलों के लिए गणना समय के अलावा।
एक और उदाहरण: प्रत्येक कोर डेटा के प्रकार के आधार पर एक अलग एल्गोरिथ्म का चयन करता है, उदाहरण के लिए, आपको एक बिंदु से ज्यामितीय आकृति की दूरी की गणना करने की आवश्यकता है और प्रत्येक कोर एक अलग आकार प्राप्त करता है और, तदनुसार, एक अलग एल्गोरिथ्म। नतीजतन, कुल समय प्रत्येक वस्तु के लिए एल्गोरिथ्म के निष्पादन समय का योग होगा।
और यह पता चला है कि हम सब कुछ बिल्कुल सीपीयू (और कई नेस्टेड शाखाओं के साथ, हम सीपीयू की तुलना में जीपीयू पर अधिक गिनते हैं) पर विचार करते हैं, केवल जब अनुक्रमिक रूप से जीपीयू की गणना दसियों बार धीमी हो जाएगी। अपने कार्यक्रमों में कितने इफ्स पर ध्यान दें, हालांकि यदि सभी 32 धागे एक ही रास्ते में जाते हैं, तो सब कुछ ठीक है, लेकिन क्या यह अक्सर सभी शाखाओं में होता है?

दूसरी सीमा: प्रत्येक मेमोरी एक्सेस के साथ, 128 बाइट्स हमेशा क्रमिक रूप से पढ़े जाते हैं, भले ही हमें केवल 1 बाइट की आवश्यकता हो

और एक और धागा केवल एक बार में उन 128 बाइट्स के 16 बाइट्स तक पहुंच सकता है।
नतीजा यह है कि मेमोरी बैंडविड्थ 150GB / s से अधिक है, लेकिन केवल इस शर्त पर कि सभी 128 बाइट्स लगातार उपयोग किए जाते हैं। यदि प्रत्येक थ्रेड को एक बड़ी संरचना को पढ़ना चाहिए, जिसका वजन 40 बाइट्स है, तो प्रत्येक थ्रेड को 3 मेमोरी अनुरोध करना होगा और 3 * 128 बाइट्स डाउनलोड करना होगा। और यदि प्रत्येक स्ट्रीम का डेटा अलग-अलग स्थानों पर स्थित है (और स्ट्रीम उन्हें एक पॉइंटर प्राप्त करता है और फिर लोड करता है, तो यह सीपीयू के लिए सामान्य स्थिति है जब मेमोरी को तर्कसंगत रूप से खर्च किया जाता है), तो उपयोगी मेमोरी बैंडविड्थ 40 * 32 / (128 * 3 * 32) है , जो कि वास्तविक का लगभग 10% है।
और फिर, हम मेमोरी बैंडविड्थ उपलब्ध सीपीयू के करीब हैं। आप निश्चित रूप से याद रख सकते हैं कि कैश है, लेकिन यह केवल फर्मी पर दिखाई दिया और यह इतना बड़ा नहीं है, हालांकि यह काफी मदद करता है। दूसरी ओर, हम यह याद कर सकते हैं कि GPU के पहले संस्करणों पर, तब भी जब क्रमिक रूप से 128 बाइट्स पढ़ रहे हों, यदि वे क्रमिक रूप से नहीं पढ़े जाते हैं और / या कम से कम एक बाइट द्वारा ऑफसेट होते हैं, तो प्रत्येक थ्रेड के लिए एक अलग मेमोरी अनुरोध किया जाता है।

तीसरी सीमा: स्मृति विलंबता प्रत्येक अनुरोध के लिए लगभग 800 चक्र है

और अंतिम उदाहरण में, सभी प्रक्रियाओं द्वारा डेटा प्राप्त करने के लिए, आपको 3 * 32 प्रश्न बनाने की आवश्यकता है ... लगभग 80 हजार चक्र ... मुझे इस समय क्या करना चाहिए? अन्य थ्रेड्स निष्पादित करें और यहां नए प्रतिबंध दिखाई देते हैं।

चौथा सीमा: 32k रजिस्टरों को मल्टीप्रोसेसर के सभी सक्रिय थ्रेड्स के लिए आवंटित किया जाता है

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

पांचवां प्रतिबंध: फर्मी थ्रेड अनुसूचक केवल 512 टुकड़ों के समूहों में धागे शुरू कर सकता है (फर्मी से पहले यह आसान था, लगभग 128)

यही है, केवल 3 विकल्प उपलब्ध हैं: 1536 थ्रेड्स यदि प्रत्येक 21 रजिस्टरों से कम का उपयोग करता है, 1024 थ्रेड्स यदि 32 रजिस्टरों से कम या 512 थ्रेड्स का उपयोग किया जाता है, तो किसी भी तरह से कम। इसके अलावा, थ्रेड्स की एक छोटी संख्या का अर्थ है कि हजारों चक्रों के लिए मेमोरी की विलंबता और पूरे मल्टीप्रोसेसर के डाउनटाइम को छिपाने की कोशिश करने के साथ गंभीर समस्याएं।
और यह CPU की तुलना में बहुत खराब है। और सबसे खराब बात यह है कि यदि प्रत्येक थ्रेड 64 से अधिक रजिस्टरों का उपयोग करता है।

छठी सीमा: यदि धारा 64 से अधिक रजिस्टरों का उपयोग करती है, तो वे वैश्विक मेमोरी में संग्रहीत होते हैं

मैं अभी भी विश्वास नहीं कर सकता कि वैश्विक स्मृति में, और स्थानीय में नहीं, लेकिन प्रलेखन ऐसा कहता है। यही है, अतिरिक्त मेमोरी अनुरोध दिखाई देते हैं। वैसे, फ़ंक्शन को कॉल करने के लिए एक स्टैक का उपयोग किया जाता है, जो रजिस्टरों को भी लेता है (हां हां, फ़ंक्शन खराब हैं)।

रजिस्टरों के उपयोग और लोड अनुकूलन का मुकाबला करने के लिए, अभी भी एक साझा मेमोरी (साझा की गई है, जो मुझे याद नहीं है कि रूसी में सही तरीके से कैसे करें)। लेकिन यह केवल 16 / 48Kb है और यह सभी सक्रिय समूहों के बीच विभाजित है, अर्थात, यदि प्रत्येक समूह 25kb मेमोरी खाता है, तो सभी आगामी परिणामों के साथ एक से अधिक समूह लॉन्च नहीं किया जा सकता है।

सातवां प्रतिबंध: प्रत्येक कोर का प्रक्षेपण थोड़ी देरी के साथ होता है

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

एक निष्कर्ष होना चाहिए था, लेकिन यह अगली बार होगा

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

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

Source: https://habr.com/ru/post/In125398/


All Articles