2016-12-09 34 views
9

मैं क्यों इस सी # कोडसंकलक ldc.i8 को अनुकूलित क्यों करता है और ldc.r8 नहीं?

long b = 20; 

ldc.i4.s 0x14 
conv.i8 

को संकलित किया गया है सोच रहा हूँ (क्योंकि यह बजाय 3 बाइट्स 9 ldc.i8 20 के लिए आवश्यक ले जाता है। अधिक जानकारी के लिए this देखें।)

जबकि यह कोड

double a = 20; 

9-बाइट अनुदेश

ldc.r8 20 
यह 3-बाइट क्रम के बजाय

को संकलित किया गया है

ldc.i4.s 0x14 
conv.r8 

(का उपयोग करते हुए मोनो 4.8।)

इस एक चूक अवसर या की लागत है conv.i8 कोड आकार में लाभ को असंतुलन देता है?

+0

रोज़लिन इसे या तो –

+2

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

+0

(मैं अनिवार्य रूप से मौजूदा उत्तरों को पहले से कह रहा हूं, लेकिन संभवतः थोड़ा अधिक प्रत्यक्ष है।) –

उत्तर

3

मुझे संदेह है कि आपको इससे अधिक संतोषजनक उत्तर मिलेगा "क्योंकि इसे किसी भी इसे लागू करने के लिए आवश्यक नहीं था।"

तथ्य यह है कि, वे इसे इस तरह से बना सकते थे, लेकिन जैसे ही एरिक लिपर्ट ने कई बार कहा है, सुविधाओं को लागू करने के बजाय चुने जाने के बजाय लागू किया जाना चुना जाता है। इस विशेष मामले में, इस सुविधा का लाभ लागत से अधिक नहीं था, उदा। अतिरिक्त परीक्षण, int और float के बीच गैर-तुच्छ रूपांतरण, जबकि ldc.i4.s के मामले में, यह बहुत अधिक परेशानी नहीं है। यह भी बेहतर है कि अधिक अनुकूलन नियमों के साथ जिटर को फूट न दें।

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

+0

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

+0

@ लुआन आप सही हैं कि जेआईटीटर स्तर पर इनलाइनिंग होती है, लेकिन मैंने उस पर विवाद नहीं किया है। एक विधि के लिए (बिना * AggresiveInlining *) के लिए इनलाइनिंग के लिए उम्मीदवार माना जाएगा, इसमें सबसे अधिक निश्चित निर्देश होना चाहिए। मेरा मतलब यही था। – IllidanS4

+0

मुझे इसके बारे में इतना यकीन नहीं है। एकमात्र चीज जो आकार के अनुसार मायने रखती है वह उत्पादित असेंबली कोड है - जिसका मूल आईएल कोड आकार (फिर से, इन दो स्निपेट्स द्वारा प्रमाणित किया गया है, जो सटीक उसी असेंबली के उत्पादन के दौरान आईएल आकार का काफी अलग है)। लेकिन मुझे पूरा यकीन है कि वे अभी भी कार्यान्वयन विवरण हैं, हालांकि मैं अभी spec digging जाने के मूड में नहीं हूं :) आईएल कोड आकार में बहुत कम लागत है, सब कुछ। – Luaan

7

क्योंकि फ्लोट एक छोटा डबल नहीं है, और पूर्णांक एक फ्लोट (या इसके विपरीत) नहीं है।

सभी int मानों में long मान पर 1: 1 मैपिंग है। float और double के लिए यह भी सच नहीं है - फ्लोटिंग पॉइंट ऑपरेशंस इस तरह से मुश्किल हैं। उल्लेख नहीं है कि इंट-फ्लोट रूपांतरण मुक्त नहीं हैं - स्टैक/रजिस्टर में 1 बाइट मान को धक्का देने के विपरीत; दोनों आईएल कोड न केवल दोनों दृष्टिकोणों द्वारा उत्पादित x86-64 कोड देखें। आईएल कोड का आकार अनुकूलन में विचार करने का एकमात्र कारक नहीं है।

यह decimal के विपरीत है, जो वास्तव में बेस-2 दशमलव फ़्लोटिंग पॉइंट नंबर की बजाय आधार -10 दशमलव संख्या है। वहाँ 20M20 और इसके विपरीत करने के लिए पूरी तरह से नक्शे, तो संकलक इस फेंकना करने के लिए स्वतंत्र है:

IL_0000: ldc.i4.s 0A 
IL_0002: newobj  System.Decimal..ctor 

एक ही दृष्टिकोण बस द्विआधारी चल बिन्दु संख्या के लिए सुरक्षित (या सस्ते) नहीं है।

आपको लगता है कि दोनों दृष्टिकोण आवश्यक रूप से सुरक्षित हैं, क्योंकि इससे कोई फर्क नहीं पड़ता कि हम एक पूर्णांक शाब्दिक ("एक स्ट्रिंग") से संकलन समय में एक डबल मान में रूपांतरण करते हैं या हम करते हैं या नहीं आईएल में

ECMA CLR कल्पना, III.1.1.1:

चल बिन्दु संख्या (स्टैटिक्स, सरणी तत्वों के लिए मेमोरी स्थान, लेकिन यह बस नहीं मामला है, विनिर्देश डाइविंग का खुलासा का एक सा रूप है और कक्षाओं के क्षेत्र) निश्चित आकार के हैं। समर्थित भंडारण आकार float32 और float64 हैं। हर जगह (मूल्यांकन स्टैक पर, तर्क के रूप में, वापसी प्रकार के रूप में, और स्थानीय चर के रूप में) फ़्लोटिंग-पॉइंट संख्याओं को एक आंतरिक फ़्लोटिंग-पॉइंट प्रकार का उपयोग करके दर्शाया जाता है। इस तरह के प्रत्येक उदाहरण में, परिवर्तनीय या अभिव्यक्ति का नाममात्र प्रकार या तो फ्लोट 32 या फ्लोट 64 है, लेकिन इसका मान अतिरिक्त सीमा और/या परिशुद्धता के साथ आंतरिक रूप से प्रदर्शित किया जा सकता है।

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

ldc.r8 0.1011E2 ; expanded to 0.10110E2 
ldc.r8 0.1E2 
mul    ; 0.10110E2 * 0.10000E2 == 0.10110E3 

conv.r8 एफ में बदलता है, float64 नहीं। इसलिए हम वास्तव में मिलता है:

ldc.i4.s theSameLiteral 
conv.r8 ; converted to 0.10111E2 
mul  ; 0.10111E2 * 0.10000E2 == 0.10111E3 

उफ़ :)

अब, मैं यकीन है कि यह किसी भी उचित मंच पर 0-255 की श्रेणी में एक पूर्णांक के साथ होने जा रहा नहीं कर रहा है कर रहा हूँ। लेकिन चूंकि हम सीएलआर विनिर्देश के खिलाफ कोडिंग कर रहे हैं, हम उस धारणा को नहीं बना सकते हैं। जेआईटी कंपाइलर कर सकता है, लेकिन यह बहुत देर हो चुकी है। भाषा संकलक दोनों को समकक्ष समझा सकता है, लेकिन सी # विनिर्देशन नहीं करता है - double स्थानीय को फ्लोट 64 माना जाता है, एफ नहीं। यदि आप चाहें तो आप अपनी खुद की भाषा बना सकते हैं।

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

मेरी मशीन पर चलने वाले वास्तविक कोड के लिए, दोनों दृष्टिकोण वास्तव में उसी x86-64 असेंबली के परिणामस्वरूप डेटा सेगमेंट से डबल सटीक मान लोड करते हैं। जेआईटी आसानी से इस अनुकूलन को कर सकता है, क्योंकि यह जानता है कि कोड वास्तव में किस आर्किटेक्चर पर चल रहा है।

+0

जांचना कि क्या डबल पूर्णांक है, 'd% 1 == 0' के रूप में छोटा है, मुझे लंबे समय तक लोड करने के लिए ऑप्टिमाइज़ेशन के साथ एक बड़ा अंतर नहीं दिखता है चेक चेक' l <256' है। । –

+0

ओह, भ्रम के लिए खेद है, मैंने मूल प्रश्न में r8 के बजाय i8 टाइप किया। फिक्स्ड –

+0

कनवर्टिंग और int से डबल की रनटाइम लागत का मुद्दा यह है कि मेरा असली सवाल क्या है। क्योंकि 2 आईएल प्रश्न में पोस्ट किया गया है (ldc.r8 और ldc.i4 + conv.r8) स्टैक पर समान मान उत्पन्न करता है। मेरे पास विचार है कि फ्लोटिंग पॉइंट नंबर कैसे काम करते हैं, लेकिन वैसे भी सुझाव के लिए धन्यवाद। –

0

सबसे पहले, आइए सहीता पर विचार करें। ldc.i4.s -128 से 127 के बीच पूर्णांक को संभाल सकता है, जिनमें से सभी को float32 में बिल्कुल दर्शाया जा सकता है। हालांकि, सीआईएल कुछ स्टोरेज स्थानों के लिए F नामक एक आंतरिक फ़्लोटिंग-पॉइंट प्रकार का उपयोग करता है। ECMA-335 मानक III.1.1.1 में कहते हैं:

... चर या अभिव्यक्ति की नाममात्र प्रकार है या तो float32 या float64 ... आंतरिक प्रतिनिधित्व निम्नलिखित विशेषताओं होगा:

  • आंतरिक प्रतिनिधित्व में नाममात्र प्रकार से अधिक या बराबर सटीकता और सीमा होगी।
  • आंतरिक प्रतिनिधित्व से और उससे रूपांतरण रूपांतरण को संरक्षित रखेंगे।

यह सब का मतलब है कि किसी भी float32 मूल्य सुरक्षित रूप से कोई बात नहीं क्या F है F में प्रतिनिधित्व करने की गारंटी है।

हम निष्कर्ष निकालते हैं कि आपके द्वारा प्रस्तावित निर्देशों का वैकल्पिक अनुक्रम सही है। अब सवाल यह है: क्या प्रदर्शन के मामले में यह बेहतर है?

इस प्रश्न का उत्तर देने के लिए, देखते हैं कि जेआईटी कंपाइलर क्या करता है जब यह कोड अनुक्रम दोनों को देखता है। ldc.r8 20 का उपयोग करते समय, आपके द्वारा संदर्भित लिंक में दिया गया उत्तर लंबे निर्देशों का उपयोग करने की विधियों को अच्छी तरह से बताता है।

के 3-बाइट क्रम पर ध्यान दें:

ldc.i4.s 0x14 
conv.r8 

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

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

+0

क्या डाउनवॉटर की व्याख्या क्यों की जाएगी? –

संबंधित मुद्दे