संकलक को किसी मशीन के लिए कोडांतरक (और अंततः मशीन कोड) का उत्पादन करना चाहिए, और आमतौर पर C ++ उस मशीन के लिए सहानुभूति रखने की कोशिश करता है।
अंतर्निहित मशीन के लिए सहानुभूति होने का अर्थ है मोटे तौर पर: सी ++ कोड लिखना आसान बनाता है जो मशीन के संचालन पर कुशलता से मैप कर सकता है। इसलिए, हम अपने हार्डवेयर प्लेटफ़ॉर्म पर तेज़ और "प्राकृतिक" डेटा प्रकार और संचालन तक पहुँच प्रदान करना चाहते हैं।
लगातार, एक विशिष्ट मशीन वास्तुकला पर विचार करें। आइए वर्तमान इंटेल x86 परिवार को लेते हैं।
Intel® 64 और IA-32 आर्किटेक्चर सॉफ्टवेयर डेवलपर मैनुअल 1 ( लिंक ), खंड 3.4.1 कहता है:
32-बिट सामान्य-उद्देश्य रजिस्टर में EAX, EBX, ECX, EDX, ESI, EDI, EBP और ESP निम्नलिखित आइटम रखने के लिए दिए गए हैं:
• तार्किक और अंकगणितीय परिचालनों के लिए प्रचालन करता है
• पता गणना के लिए परिचालनों
• मेमोरी पॉइंटर्स
इसलिए, हम चाहते हैं कि कंपाइलर इन EAX, EBX आदि का उपयोग करें, जब यह सरल C ++ पूर्णांक अंकगणित को संकलित करता है। इसका मतलब यह है कि जब मैं एक की घोषणा करता हूं int, तो यह इन रजिस्टरों के साथ संगत होना चाहिए, ताकि मैं उन्हें कुशलतापूर्वक उपयोग कर सकूं।
रजिस्टर हमेशा एक ही आकार के होते हैं (यहां, 32 बिट्स), इसलिए मेरे intचर हमेशा 32 बिट्स भी होंगे। मैं उसी लेआउट (लिटिल-एंडियन) का उपयोग करूंगा, ताकि मुझे हर बार एक रूपांतरण को एक रजिस्टर में लोड करने, या एक रजिस्टर को एक चर में संग्रहीत करने की आवश्यकता न हो।
Godbolt का उपयोग करके हम देख सकते हैं कि संकलक कुछ तुच्छ कोड के लिए क्या करता है:
int square(int num) {
return num * num;
}
संकलन (जीसीसी 8.1 और -fomit-frame-pointer -O3सादगी के लिए) के साथ:
square(int):
imul edi, edi
mov eax, edi
ret
इसका मतलब है की:
int numपैरामीटर रजिस्टर ईडीआई में पारित कर दिया है, जिसका अर्थ यह बिल्कुल आकार है और इंटेल एक देशी रजिस्टर के लिए उम्मीद लेआउट था। फ़ंक्शन को कुछ भी परिवर्तित करने की आवश्यकता नहीं है
- गुणन एक एकल निर्देश (
imul) है, जो बहुत तेज़ है
- परिणाम वापस करना बस इसे दूसरे रजिस्टर में कॉपी करने की बात है (कॉलर को परिणाम EAX में डालने की उम्मीद है)
संपादित करें: हम एक गैर-देशी लेआउट का उपयोग करके अंतर दिखाने के लिए एक प्रासंगिक तुलना जोड़ सकते हैं। सबसे सरल मामला मूल चौड़ाई के अलावा किसी अन्य चीज़ में मूल्यों को संग्रहीत करना है।
फिर से गॉडबोल्ट का उपयोग करके , हम एक साधारण देशी गुणन की तुलना कर सकते हैं
unsigned mult (unsigned x, unsigned y)
{
return x*y;
}
mult(unsigned int, unsigned int):
mov eax, edi
imul eax, esi
ret
गैर-मानक चौड़ाई के लिए समान कोड के साथ
struct pair {
unsigned x : 31;
unsigned y : 31;
};
unsigned mult (pair p)
{
return p.x*p.y;
}
mult(pair):
mov eax, edi
shr rdi, 32
and eax, 2147483647
and edi, 2147483647
imul eax, edi
ret
सभी अतिरिक्त निर्देश इनपुट प्रारूप (दो 31-बिट अहस्ताक्षरित पूर्णांक) को उस प्रारूप में परिवर्तित करने से संबंधित हैं जो प्रोसेसर मूल रूप से संभाल सकता है। यदि हम परिणाम को वापस 31-बिट मान में संग्रहीत करना चाहते हैं, तो ऐसा करने के लिए एक या दो निर्देश होंगे।
यह अतिरिक्त जटिलता का मतलब है कि आप केवल इससे परेशान होंगे जब अंतरिक्ष की बचत बहुत महत्वपूर्ण है। इस मामले में हम मूल unsignedया uint32_tप्रकार का उपयोग करने की तुलना में केवल दो बिट्स बचा रहे हैं , जो बहुत सरल कोड उत्पन्न करेगा।
गतिशील आकारों पर एक नोट:
उपरोक्त उदाहरण अभी भी चर-चौड़ाई के बजाय निश्चित-चौड़ाई मान है, लेकिन चौड़ाई (और संरेखण) अब मूल रजिस्टरों से मेल नहीं खाते हैं।
X86 प्लेटफ़ॉर्म में कई मूल आकार हैं, जिनमें मुख्य 32-बिट के अलावा 8-बिट और 16-बिट शामिल हैं (मैं 64-बिट मोड पर चमक रहा हूं और सादगी के लिए अन्य कई चीजें)।
ये प्रकार (चार, int8_t, uint8_t, int16_t आदि) भी सीधे वास्तुकला द्वारा समर्थित हैं - आंशिक रूप से पुराने 8086/286/386 / आदि के साथ पिछड़े संगतता के लिए। आदि निर्देश सेट।
यह निश्चित रूप से ऐसा मामला है जो सबसे छोटे प्राकृतिक निश्चित आकार के प्रकार का चयन करेगा जो पर्याप्त होगा, अच्छा अभ्यास हो सकता है - वे अभी भी त्वरित हैं, एकल निर्देश लोड और स्टोर करते हैं, आपको अभी भी पूर्ण-गति देशी अंकगणित मिलता है, और आप भी प्रदर्शन में सुधार कर सकते हैं कैश की कमी को पूरा करता है।
यह चर-लंबाई एन्कोडिंग के लिए बहुत अलग है - मैंने इनमें से कुछ के साथ काम किया है, और वे भयानक हैं। हर लोड एक निर्देश के बजाय एक लूप बन जाता है। हर दुकान एक लूप भी है। हर संरचना परिवर्तनशील है, इसलिए आप स्वाभाविक रूप से सरणियों का उपयोग नहीं कर सकते।
दक्षता पर एक और ध्यान दें
बाद की टिप्पणियों में, आप "कुशल" शब्द का उपयोग कर रहे हैं, जहां तक मैं भंडारण आकार के संबंध में बता सकता हूं। हम कभी-कभी भंडारण आकार को कम करने के लिए चुनते हैं - यह महत्वपूर्ण हो सकता है जब हम बहुत बड़ी संख्या में मानों को फाइलों में सहेज रहे हैं, या उन्हें एक नेटवर्क पर भेज रहे हैं। व्यापार-बंद यह है कि हमें उन मूल्यों को उनके साथ कुछ भी करने के लिए रजिस्टरों में लोड करने की आवश्यकता है , और रूपांतरण करना मुफ्त नहीं है।
जब हम दक्षता पर चर्चा करते हैं, तो हमें यह जानना होगा कि हम क्या अनुकूलन कर रहे हैं, और व्यापार-बंद क्या हैं। गैर-देशी भंडारण प्रकारों का उपयोग करना अंतरिक्ष के लिए प्रसंस्करण गति का व्यापार करने का एक तरीका है, और कभी-कभी समझ में आता है। अंतरिक्ष की अक्सर कम से कम आगे की बचत के लिए परिवर्तनीय-लंबाई के भंडारण (कम से कम अंकगणितीय प्रकारों के लिए) का उपयोग करके अधिक प्रसंस्करण गति (और कोड जटिलता और डेवलपर समय) का व्यापार करता है।
इसके लिए आप जिस पेनल्टी का भुगतान करते हैं उसका मतलब है कि यह तभी सार्थक है जब आपको बैंडविड्थ या दीर्घकालिक भंडारण को पूरी तरह से कम करने की आवश्यकता होती है, और उन मामलों के लिए जिन्हें आमतौर पर एक सरल और प्राकृतिक प्रारूप का उपयोग करना आसान होता है - और फिर इसे एक सामान्य-उद्देश्य प्रणाली के साथ संपीड़ित करें। (जैसे ज़िप, gzip, bzip2, xy या जो भी हो)।
tl; डॉ
प्रत्येक प्लेटफ़ॉर्म में एक आर्किटेक्चर है, लेकिन आप डेटा का प्रतिनिधित्व करने के लिए विभिन्न तरीकों से अनिवार्य रूप से असीमित संख्या में आ सकते हैं। किसी भी भाषा के लिए अंतर्निहित डेटा प्रकारों की असीमित संख्या प्रदान करना उचित नहीं है। इसलिए, C ++ प्लेटफ़ॉर्म के मूल, डेटा सेटों के प्राकृतिक सेट तक अंतर्निहित पहुंच प्रदान करता है, और आपको किसी अन्य (गैर-मूल निवासी) का प्रतिनिधित्व करने की अनुमति देता है।
unsingedमूल्य, जिसे 1 बाइट के साथ दर्शाया जा सकता है255। 2) मूल्य परिवर्तन के रूप में, एक चर के इष्टतम भंडारण आकार की गणना के ओवरहेड, और भंडारण क्षेत्र का विस्तार / विस्तार करना।