यहाँ एक वास्तविक दुनिया उदाहरण है: पुराने संकलक पर फिक्स्ड बिंदु गुणक।
ये केवल फ्लोटिंग पॉइंट के बिना उपकरणों पर काम नहीं करते हैं, वे सटीक होने पर चमकते हैं क्योंकि वे आपको अनुमानित त्रुटि के साथ 32 बिट्स सटीक देते हैं (फ्लोट केवल 23 बिट है और सटीक नुकसान की भविष्यवाणी करना कठिन है)। यानी करीब-करीब एकसमान सापेक्ष परिशुद्धता ( ) के बजाय संपूर्ण सीमा पर एकसमान पूर्ण परिशुद्धता ।float
आधुनिक संकलक इस निश्चित-बिंदु उदाहरण को अच्छी तरह से अनुकूलित करते हैं, इसलिए अधिक आधुनिक उदाहरणों के लिए जो अभी भी संकलक-विशिष्ट कोड की आवश्यकता है, देखें
- 64 बिट पूर्णांक गुणन का उच्च भाग प्राप्त करना :
uint64_t
32x32 => 64-बिट गुणकों का उपयोग करने वाला एक पोर्टेबल संस्करण 64-बिट सीपीयू पर अनुकूलन करने में विफल रहता है, इसलिए आपको आंतरिक या __int128
64-बिट सिस्टम पर कुशल कोड की आवश्यकता होती है।
- Windows 32 बिट्स पर _um128 : MSVC हमेशा एक अच्छा काम नहीं करता है, जब 32-बिट पूर्णांकों को 64 में गुणा किया जाता है, इसलिए आंतरिक लोगों ने बहुत मदद की।
C के पास पूर्ण-गुणन ऑपरेटर नहीं है (N-बिट इनपुट्स से 2N-बिट परिणाम)। सी में इसे व्यक्त करने का सामान्य तरीका इनपुट को व्यापक प्रकार में डालना है और उम्मीद है कि संकलक पहचानता है कि इनपुट के ऊपरी बिट्स दिलचस्प हैं:
// on a 32-bit machine, int can hold 32-bit fixed-point integers.
int inline FixedPointMul (int a, int b)
{
long long a_long = a; // cast to 64 bit.
long long product = a_long * b; // perform multiplication
return (int) (product >> 16); // shift by the fixed point bias
}
इस कोड के साथ समस्या यह है कि हम कुछ ऐसा करते हैं जिसे सीधे सी-भाषा में व्यक्त नहीं किया जा सकता है। हम दो 32 बिट संख्याओं को गुणा करना चाहते हैं और एक 64 बिट परिणाम प्राप्त करते हैं, जिसमें से हम मध्य 32 बिट को वापस करते हैं। हालांकि, सी में यह बहुतायत से मौजूद नहीं है। आप केवल इतना कर सकते हैं कि पूर्णांकों को 64 बिट तक बढ़ावा दें और 64 * 64 = 64 गुणा करें।
x86 (और ARM, MIPS और अन्य) हालांकि एकल निर्देश में गुणा कर सकते हैं। कुछ संकलक इस तथ्य को अनदेखा करते थे और कोड उत्पन्न करते थे जो गुणा करने के लिए रनटाइम लाइब्रेरी फ़ंक्शन को कॉल करता है। 16 से बदलाव भी अक्सर एक पुस्तकालय दिनचर्या द्वारा किया जाता है (x86 इस तरह के बदलाव भी कर सकता है)।
तो हम एक या दो पुस्तकालय कॉल के साथ छोड़ रहे हैं बस एक गुणा के लिए। इसके गंभीर परिणाम हैं। न केवल शिफ्ट धीमा है, रजिस्टरों को फ़ंक्शन कॉल में संरक्षित किया जाना चाहिए और यह इनलाइनिंग और कोड-अनरोलिंग में भी मदद नहीं करता है।
यदि आप कोड (इनलाइन) कोड में समान कोड को फिर से लिखते हैं तो आप एक महत्वपूर्ण गति को बढ़ावा दे सकते हैं।
इसके अतिरिक्त: एएसएम का उपयोग समस्या को हल करने का सबसे अच्छा तरीका नहीं है। अधिकांश कंपाइलर आपको आंतरिक रूप में कुछ कोडांतरक निर्देशों का उपयोग करने की अनुमति देते हैं यदि आप उन्हें सी में व्यक्त नहीं कर सकते हैं। उदाहरण के लिए VS.NET2008 कंपाइलर __emul के रूप में 32 * 32 = 64 बिट mul और 64 बिट शिफ्ट __ll_rshift के रूप में उजागर करता है।
आंतरिक का उपयोग करके आप फ़ंक्शन को इस तरह से फिर से लिख सकते हैं कि सी-कंपाइलर को समझने का मौका है कि क्या हो रहा है। इससे कोड को इनबिल्ड किया जा सकता है, रजिस्टर आबंटित किया जा सकता है, सामान्य सबप्रेसेशन एलिमिनेशन और निरंतर प्रचार भी किया जा सकता है। आपको इस तरह हाथ से लिखे कोडांतरक कोड पर एक बड़ा प्रदर्शन सुधार मिलेगा ।
संदर्भ के लिए: वीएस.नेट कंपाइलर के लिए नियत-बिंदु mul के लिए अंतिम परिणाम है:
int inline FixedPointMul (int a, int b)
{
return (int) __ll_rshift(__emul(a,b),16);
}
निश्चित बिंदु विभाजन का प्रदर्शन अंतर और भी बड़ा है। मैंने असम्-लाइनों के एक जोड़े को लिखकर डिवीजन हेवी फिक्स्ड पॉइंट कोड के लिए कारक 10 तक सुधार किया था।
दृश्य C ++ 2013 का उपयोग करना दोनों तरीकों के लिए एक ही असेंबली कोड देता है।
2007 से gcc4.1 शुद्ध सी संस्करण को भी अच्छी तरह से अनुकूलित करता है। (गॉडबॉल्ट कंपाइलर एक्सप्लोरर में gcc का कोई भी पुराना संस्करण स्थापित नहीं है, लेकिन संभवतः पुराने GCC संस्करण बिना इन्टिरिनिक्स के भी ऐसा कर सकते हैं।)
God86t संकलक एक्सप्लोरर पर x86 (32-बिट) और एआरएम के लिए स्रोत + एएसएम देखें । (दुर्भाग्य से यह सरल पुराने सी संस्करण से खराब कोड का उत्पादन करने के लिए पुराना कोई भी कंपाइलर नहीं है।)
आधुनिक CPUs बातें सी के लिए ऑपरेटरों की जरूरत नहीं है क्या कर सकते हैं सब पर की तरह, popcnt
या थोड़ा-स्कैन प्रथम या अंतिम सेट सा लगता है । (POSIX में एक ffs()
फ़ंक्शन है, लेकिन इसका शब्दार्थ x86 bsf
/ से मेल नहीं खाता है bsr
। https://en.wikipedia.org/wiki/Find_first_set देखें )।
कुछ संकलक कभी-कभी एक लूप को पहचान सकते हैं जो एक पूर्णांक में सेट बिट्स की संख्या को गिनता है और इसे एक popcnt
निर्देश पर संकलित करता है (यदि संकलन समय पर सक्षम है), लेकिन यह __builtin_popcnt
GNU C, या x86 पर उपयोग करने के लिए बहुत अधिक विश्वसनीय है यदि आप केवल हैं SSE4.2 के साथ हार्डवेयर लक्षित करना: _mm_popcnt_u32
से<immintrin.h>
।
या C ++ में, a std::bitset<32>
और use को असाइन करें .count()
। (यह एक ऐसा मामला है जहां भाषा ने मानक पुस्तकालय के माध्यम से पॉपकाउंट के एक अनुकूलित कार्यान्वयन को आंशिक रूप से उजागर करने का एक तरीका पाया है, एक तरह से जो हमेशा कुछ सही संकलन करेगा, और जो भी लक्ष्य का समर्थन करता है उसका लाभ उठा सकता है।) https भी देखें : //en.wikipedia.org/wiki/ Hamming_weight#Language_support ।
इसी तरह, कुछ सी कार्यान्वयनों पर (x86 32-बिट बाइट स्वैप फॉर एंडियन रूपांतरण) के लिए ntohl
संकलन कर सकते हैं bswap
।
आंतरिक या हाथ से लिखे हुए एसएसएम के लिए एक अन्य प्रमुख क्षेत्र SIMD निर्देशों के साथ मैनुअल वेक्टरकरण है। सरल लूप जैसे कंपाइलर खराब नहीं होते हैं dst[i] += src[i] * 10.0;
, लेकिन जब चीजें अधिक जटिल हो जाती हैं, तो अक्सर बुरी तरह से या ऑटो-वेक्टर नहीं करते हैं। उदाहरण के लिए, आपको कुछ भी प्राप्त होने की संभावना नहीं है कि SIMD का उपयोग करके Atoi कैसे लागू किया जाए? स्केलर कोड से संकलक द्वारा स्वचालित रूप से उत्पन्न।