मेरे मित्र अरस
ने हाल ही में C ++, C #, और यूनिटी बर्स्ट कंपाइलर सहित विभिन्न भाषाओं में एक ही किरण अनुरेखक
लिखा है। बेशक, यह उम्मीद करना स्वाभाविक है कि C # C ++ की तुलना में धीमा होगा, लेकिन मुझे यह दिलचस्प लगा कि मोनो कोर .NET कोर की तुलना में बहुत धीमी है।
उनके प्रकाशित
संकेतक खराब थे:
- सी # (.NET कोर): मैक 17.5 मेर / एस,
- सी # (एकता, मोनो): मैक 4.6 मेर / एस,
- सी # (एकता, IL2CPP): मैक 17.1 मेरा / s
मैंने यह देखने का निर्णय लिया कि क्या हो रहा है और दस्तावेज़ स्थानों को बेहतर बनाया जा सकता है।
इस मानदंड और इस समस्या का अध्ययन करने के परिणामस्वरूप, हमने तीन क्षेत्रों को पाया जिसमें सुधार संभव है:
- सबसे पहले, आपको डिफ़ॉल्ट मोनो सेटिंग्स में सुधार करने की आवश्यकता है, क्योंकि उपयोगकर्ता आमतौर पर अपनी सेटिंग्स को कॉन्फ़िगर नहीं करते हैं
- दूसरे, हमें दुनिया को मोनो में एलएलवीएम कोड अनुकूलन के बैकेंड में सक्रिय रूप से पेश करने की आवश्यकता है
- तीसरा, हमने कुछ मोनो मापदंडों की ट्यूनिंग में सुधार किया।
इस परीक्षण का संदर्भ बिंदु मेरी मशीन पर चलने वाले
किरण अनुरेखक के परिणाम थे, और चूंकि मेरे पास अलग-अलग हार्डवेयर हैं, इसलिए हम संख्याओं की तुलना नहीं कर सकते हैं।
मोनो और .NET कोर के लिए मेरे घर iMac पर परिणाम इस प्रकार थे:
काम करने का माहौल | परिणाम, एमआरई / सेकंड |
---|
.NET कोर 2.1.4, dotnet run डिबग बिल्ड | 3.6 |
.NET कोर 2.1.4 रिलीज बिल्ड dotnet run -c Release | 21.7 |
वेनिला मोनो, mono Maths.exe | 6.6 |
LLVM और float32 के साथ वेनिला मोनो | 15.5 |
इस समस्या का अध्ययन करने की प्रक्रिया में, हमें कुछ समस्याएं मिलीं, जिनमें सुधार के बाद निम्नलिखित परिणाम प्राप्त हुए:
काम करने का माहौल | परिणाम, एमआरई / सेकंड |
---|
LLVM और float32 के साथ मोनो | 15.5 |
LLVM, float32 और नियत इनलाइन के साथ उन्नत मोनो | 29.6 |
बड़ी तस्वीर:
बस एलएलवीएम और फ्लोट 32 को लागू करके, आप फ्लोटिंग पॉइंट कोड के प्रदर्शन को लगभग 2.3 गुना बढ़ा सकते हैं। और ट्यूनिंग के बाद, जिसे हमने इन प्रयोगों के परिणामस्वरूप मोनो में जोड़ा, आप मानक मोनो की तुलना में उत्पादकता को 4.4 गुना बढ़ा सकते हैं - मोनो के भविष्य के संस्करणों में ये पैरामीटर डिफ़ॉल्ट पैरामीटर बन जाएंगे।
इस लेख में मैं अपने निष्कर्षों की व्याख्या करूंगा।
32-बिट और 64-बिट फ्लोट
Aras गणना के मुख्य भाग के लिए 32-बिट फ़्लोटिंग-पॉइंट नंबरों का उपयोग करता है (प्रकार C # में
float
या .NET में
System.Single
)। मोनो में, हमने बहुत समय पहले एक गलती की थी - सभी 32-बिट फ़्लोटिंग पॉइंट गणना 64-बिट के रूप में की गई थी, और डेटा अभी भी 32-बिट क्षेत्रों में संग्रहीत किया गया था।
आज, मेरी याददाश्त पहले की तरह तेज नहीं है, और मुझे ठीक से याद नहीं है कि हमने ऐसा निर्णय क्यों लिया।
मैं केवल यह मान सकता हूं कि यह उस समय के रुझानों और विचारों से प्रभावित था।
फिर एक सकारात्मक आभा बढ़ी हुई सटीकता के साथ फ्लोट कंप्यूटिंग के आसपास मंडराने लगी। उदाहरण के लिए, इंटेल x87 प्रोसेसर ने फ्लोटिंग पॉइंट गणनाओं के लिए 80-बिट परिशुद्धता का उपयोग किया, यहां तक कि जब ऑपरेंड डबल थे, जो उपयोगकर्ताओं को अधिक सटीक परिणाम प्रदान करते थे।
उस समय, यह विचार भी प्रासंगिक था कि मेरी पिछली परियोजनाओं में से एक - Gnumeric स्प्रेडशीट - सांख्यिकीय कार्यों को एक्सेल की तुलना में अधिक कुशलता से लागू किया गया था। इसलिए, कई समुदाय इस विचार से अच्छी तरह परिचित हैं कि बढ़ी हुई सटीकता के साथ अधिक सटीक परिणामों का उपयोग किया जा सकता है।
मोनो विकास के प्रारंभिक चरणों में, सभी प्लेटफार्मों पर किए गए अधिकांश गणितीय संचालन इनपुट पर केवल दोगुना प्राप्त कर सकते थे। 32-बिट संस्करणों को C99, पॉज़िक्स और आईएसओ में जोड़ा गया था, लेकिन उन दिनों में वे पूरे उद्योग के लिए व्यापक रूप से उपलब्ध नहीं थे (उदाहरण के लिए,
sinf
sin
का फ्लोट संस्करण है,
fabsf
का संस्करण है, और इसी तरह)।
संक्षेप में, 2000 के दशक की शुरुआत आशावाद का समय था।
कंप्यूटिंग समय बढ़ाने के लिए अनुप्रयोगों ने भारी कीमत का भुगतान किया, लेकिन मोनो का उपयोग मुख्य रूप से डेस्कटॉप लिनक्स अनुप्रयोगों के लिए किया गया था जो एचटीटीपी पेज और कुछ सर्वर प्रक्रियाओं की सेवा कर रहे थे, इसलिए फ्लोटिंग-पॉइंट स्पीड वह समस्या नहीं थी जिसका हम रोज सामना करते थे। यह केवल कुछ वैज्ञानिक बेंचमार्क में ध्यान देने योग्य हो गया, और 2003 में उन्हें शायद ही कभी .NET पर विकसित किया गया था।
आज, गेम्स, 3 डी एप्लिकेशन, इमेज प्रोसेसिंग, वीआर, एआर, और मशीन लर्निंग ने फ्लोटिंग पॉइंट ऑपरेशंस को अधिक सामान्य प्रकार का डेटा बना दिया है। मुसीबत अकेले नहीं आती है, और कोई अपवाद नहीं हैं। फ्लोट अब केवल कुछ स्थानों में कोड में उपयोग किए जाने वाले अनुकूल डेटा प्रकार नहीं था। वे एक हिमस्खलन में बदल गए, जिसमें से कहीं भी छिपाना नहीं है। उनमें से बहुत सारे हैं और उनके प्रसार को रोका नहीं जा सकता है।
कार्यक्षेत्र ध्वज फ़्लोट 32
इसलिए, कुछ साल पहले हमने 32-बिट फ्लोट संचालन का उपयोग करने के लिए समर्थन जोड़ने का फैसला किया, अन्य सभी मामलों की तरह। हमने कार्यक्षेत्र की इस विशेषता को "फ्लोट 32" कहा। मोनो में, यह काम के माहौल में विकल्प
--O=float32
को जोड़कर सक्षम होता है, और Xamarin अनुप्रयोगों में यह पैरामीटर प्रोजेक्ट सेटिंग्स में बदल जाता है।
यह नया झंडा हमारे मोबाइल उपयोगकर्ताओं द्वारा अच्छी तरह से प्राप्त किया गया था, क्योंकि मूल रूप से मोबाइल डिवाइस अभी भी बहुत शक्तिशाली नहीं हैं, और वे सटीकता बढ़ाने की तुलना में तेजी से डेटा संसाधित करने के लिए बेहतर हैं। हमने सिफारिश की कि मोबाइल उपयोगकर्ता एक ही समय में एलएलवीएम ऑप्टिमाइज़िंग कंपाइलर और फ्लोट 32 फ्लैग को चालू करें।
हालांकि यह ध्वज कई वर्षों के लिए लागू किया गया है, हमने उपयोगकर्ताओं के लिए अप्रिय आश्चर्य से बचने के लिए इसे डिफ़ॉल्ट नहीं बनाया। हालांकि, हमने ऐसे मामलों का सामना करना शुरू कर दिया, जिनमें मानक 64-बिट व्यवहार के कारण आश्चर्य उत्पन्न होता
है, एकता उपयोगकर्ता द्वारा प्रस्तुत इस
बग रिपोर्ट को देखें।
अब हम
float32
मोनो
float32
उपयोग करेंगे, प्रगति को यहां ट्रैक किया जा सकता है:
https://github.com/mono/mono/issues/6985 ।
इस बीच, मैं अपने दोस्त अरास के प्रोजेक्ट पर लौट आया। उन्होंने .NET API में जोड़े गए नए API का उपयोग किया। हालाँकि .NET कोर ने हमेशा 32-बिट फ़्लोट ऑपरेशन को 32-बिट फ़्लोट के रूप में निष्पादित किया है,
System.Math
API अभी भी
float
से
double
में प्रक्रिया में रूपांतरण करता है। उदाहरण के लिए, यदि आपको फ्लोट मान के लिए साइन फ़ंक्शन की गणना करने की आवश्यकता है, तो एकमात्र विकल्प
Math.Sin (double)
को कॉल
Math.Sin (double)
, और आपको फ्लोट से डबल में बदलना होगा।
इसे ठीक करने के लिए, .NET Core में एक नए प्रकार का
System.MathF
जोड़ा गया, जिसमें एकल परिशुद्धता फ़्लोटिंग पॉइंट मैथ ऑपरेशंस शामिल हैं, और अब हमने
मोनो में केवल इस
[System.MathF]
को पोर्ट किया है।
64-बिट से 32-बिट फ्लोट में संक्रमण से प्रदर्शन में काफी सुधार होता है, जिसे इस तालिका से देखा जा सकता है:
काम का माहौल और विकल्प | श्रीमती / सेकंड |
---|
सिस्टम के साथ मोनो। मैथ | 6.6 |
-O=float32 साथ मोनो। -O=float32 और -O=float32 | 8.1 |
System.MathF के साथ मोनो | 6.5 |
मोनो के साथ System.MathF और -O=float32 | 8.2 |
यही है, इस परीक्षण में
float32
का उपयोग करने से वास्तव में प्रदर्शन में सुधार होता है, और मैथएफ पर बहुत कम प्रभाव पड़ता है।
एलएलवीएम सेटअप
इस शोध की प्रक्रिया में, हमने पाया कि हालांकि फास्ट जेआईटी मोनो कंपाइलर में
float32
समर्थन है, लेकिन हमने इस समर्थन को एलएलवीएम बैकएंड में नहीं जोड़ा। इसका मतलब यह था कि एलएलवीएम के साथ मोनो अभी भी फ्लोट से डबल तक महंगा रूपांतरण कर रहा था।
इसलिए, Zoltan ने LLVM कोड जनरेशन इंजन में
float32
सपोर्ट को जोड़ा।
तब उन्होंने देखा कि हमारे इनलाइनर फास्ट जेआईटी के लिए उसी हीरॉस्टिक का उपयोग करते हैं, जो एलएलवीएम के लिए उपयोग किया जाता है। फास्ट जेआईटी के साथ काम करते समय, जेआईटी की गति और निष्पादन की गति के बीच एक संतुलन बनाना आवश्यक है, इसलिए हमने जेआईटी इंजन के काम की मात्रा को कम करने के लिए एम्बेडेड कोड की मात्रा को सीमित कर दिया है।
लेकिन यदि आप मोनो में एलएलवीएम का उपयोग करने का निर्णय लेते हैं, तो आप जितनी जल्दी हो सके कोड के लिए प्रयास करते हैं, इसलिए हमने तदनुसार सेटिंग्स को बदल दिया। आज, इस पैरामीटर को
MONO_INLINELIMIT
वातावरण
MONO_INLINELIMIT
का उपयोग करके बदला जा सकता है, लेकिन वास्तव में इसे डिफ़ॉल्ट मानों को लिखने की आवश्यकता है।
यहां संशोधित LLVM सेटिंग्स के साथ परिणाम दिए गए हैं:
काम का माहौल और विकल्प | श्रीमती / सेकंड |
---|
मोनो के साथ System.Math --llvm -O=float32 | 16.0 |
--llvm -O=float32 साथ मोनो। --llvm -O=float32 , निरंतर --llvm -O=float32 | 29.1 |
मोनो के साथ System.MathF --llvm -O=float32 , निरंतर --llvm -O=float32 | 29.6 |
अगले चरण
इन सभी सुधारों के लिए बहुत कम प्रयास की आवश्यकता थी। ये बदलाव स्लैक में आवधिक चर्चाओं के कारण हुए। मैंने भी
System.MathF
को मोनो में पोर्ट करने के लिए एक शाम को कुछ घंटे बनाने में कामयाबी हासिल की।
अरस रे ट्रेसर कोड अध्ययन के लिए एक आदर्श विषय बन गया है क्योंकि यह आत्मनिर्भर था, यह एक वास्तविक अनुप्रयोग था, न कि एक सिंथेटिक स्टेपमार्क। हम अन्य समान सॉफ़्टवेयर खोजना चाहते हैं जिनका उपयोग हम उत्पन्न होने वाले बाइनरी कोड का अध्ययन करने के लिए कर सकते हैं, और यह सुनिश्चित कर सकते हैं कि हम एलएलवीएम को उसके काम के इष्टतम निष्पादन के लिए सबसे अच्छा डेटा पास करते हैं।
हम अपने एलएलवीएम को अपडेट करने और नए जोड़े गए अनुकूलन का उपयोग करने पर भी विचार कर रहे हैं।
अलग नोट
अतिरिक्त परिशुद्धता के अच्छे दुष्प्रभाव हैं। उदाहरण के लिए, गोडोट इंजन के पूल अनुरोधों को पढ़ते हुए, मैंने देखा कि संकलन समय पर फ्लोटिंग पॉइंट ऑपरेशन की सटीकता को बनाने के लिए एक सक्रिय चर्चा है (
https://github.com/godotengine/godot/pull/17134 )।
मैंने जुआन से पूछा कि यह किसी के लिए क्यों जरूरी हो सकता है, क्योंकि मेरा मानना है कि 32-बिट फ्लोटिंग-पॉइंट ऑपरेशन गेम्स के लिए काफी पर्याप्त हैं।
जुआन ने बताया कि सामान्य स्थिति में, फ़्लैट्स बहुत बढ़िया काम करते हैं, लेकिन अगर आप केंद्र से "दूर" चले जाते हैं, तो कहते हैं, खेल के केंद्र से 100 किलोमीटर आगे बढ़ें, एक गणना त्रुटि जमा होने लगती है, जिससे दिलचस्प ग्राफिकल ग्लिट्स हो सकते हैं। आप इस समस्या के प्रभाव को कम करने के लिए विभिन्न रणनीतियों का उपयोग कर सकते हैं, और उनमें से एक बढ़ी सटीकता के साथ काम कर रहा है, जिसके लिए आपको प्रदर्शन के लिए भुगतान करना होगा।
मेरे ट्विटर फीड पर हमारी बातचीत के कुछ समय बाद, मैंने इस समस्या को प्रदर्शित करने वाली एक पोस्ट देखी:
http://pharr.org/matt/blog/2018/03/02/rendering-in-camera-space.htmlसमस्या नीचे दी गई छवियों में दिखाई गई है। यहाँ हम pbrt-v3-दृश्यों **
पैकेज से एक स्पोर्ट्स कार मॉडल देखते हैं । कैमरा और दृश्य दोनों मूल के पास हैं, और सब कुछ बहुत अच्छा लग रहा है।**
( यासुतोशी मोरी के लेखक ।)फिर हम मूल से xx, yy और zz पर कैमरा और दृश्य 200,000 इकाइयों को स्थानांतरित करते हैं। यह देखा जा सकता है कि मशीन का मॉडल काफी खंडित हो गया है; यह पूरी तरह से फ्लोटिंग पॉइंट नंबरों में सटीकता की कमी के कारण है।
यदि हम आगे 5 × 5 × 5 गुना आगे बढ़ते हैं, तो मूल से 1 मिलियन यूनिट, मॉडल का विघटन शुरू हो जाता है; मशीन अपने आप में एक बहुत क्रूड वैक्सिल सन्निकटन में बदल जाती है, दिलचस्प और भयानक दोनों। (कीनू ने सवाल पूछा: क्या Minecraft सिर्फ इतना क्यूबिक है क्योंकि सब कुछ मूल से बहुत दूर प्रदान किया गया है?)**
(मैंने अपने खूबसूरत मॉडल के साथ जो किया उसके लिए मैं यसुतोशी मोरी से माफी मांगता हूं ।)