क्या वास्तव में यह कहना तेजी से उपयोग किया जाता है (i << 3) + (i << 1) सीधे i * 10 का उपयोग करने की तुलना में 10 के साथ गुणा करने के लिए?
यह आपकी मशीन पर हो सकता है या नहीं भी हो सकता है - यदि आप परवाह करते हैं, तो अपने वास्तविक दुनिया के उपयोग में मापें।
एक केस स्टडी - 486 से कोर i7 तक
बेंचमार्किंग को सार्थक रूप से करना बहुत मुश्किल है, लेकिन हम कुछ तथ्यों को देख सकते हैं। से http://www.penguin.cz/~literakl/intel/s.html#SAL और http://www.penguin.cz/~literakl/intel/i.html#IMUL हम 86 घड़ी चक्र का एक विचार प्राप्त अंकगणितीय पारी और गुणन के लिए आवश्यक। मान लें कि हम "486" (सबसे नए सूचीबद्ध), 32 बिट रजिस्टरों और तुरंत चिपके रहते हैं, IMUL 13-42 चक्र और IDIV 44 लेता है। प्रत्येक SAL को 2 लगते हैं, और 1 जोड़ते हैं, तो उन सभी में से कुछ के साथ भी सतही रूप से दिखता है एक विजेता की तरह।
इन दिनों, कोर i7 के साथ:
( http://software.intel.com/en-us/forums/showthread.php?t=61481 से )
विलंबता पूर्णांक जोड़ के लिए 1 चक्र है और पूर्णांक गुणन के लिए 3 चक्र । आप "Intel® 64 और IA-32 आर्किटेक्चर ऑप्टिमाइज़ेशन रेफरेंस मैनुअल" के परिशिष्ट C में विलंबता और थ्रूपुट पा सकते हैं, जो http://www.intel.com/products/processor/manuals/ पर स्थित है ।
(कुछ इंटेल ब्लर्ब से)
SSE का उपयोग करते हुए, कोर i7 एक साथ ऐड और गुणा निर्देश जारी कर सकता है, जिसके परिणामस्वरूप प्रति घड़ी चक्र में 8 फ्लोटिंग-पॉइंट ऑपरेशंस (FLOP) की चरम दर होती है।
इससे आप अंदाजा लगा सकते हैं कि चीजें कितनी दूर आ चुकी हैं। अनुकूलन ट्रिविया - जैसे बिट शिफ्टिंग बनाम *
- जिसे 90 के दशक में भी गंभीरता से लिया गया था, अब सिर्फ अप्रचलित है। बिट-शिफ्टिंग अभी भी तेज है, लेकिन जब तक आप अपने सभी बदलाव नहीं करते हैं, तब तक गैर-शक्ति-दो-दो mul / div के लिए और फिर से धीमी होने वाले परिणामों को जोड़ें। फिर, अधिक निर्देशों का अर्थ है अधिक कैश दोष, पाइपलाइनिंग में अधिक संभावित मुद्दे, अस्थायी रजिस्टरों के अधिक उपयोग का मतलब स्टैक से रजिस्टर सामग्री की अधिक बचत और पुनर्स्थापना हो सकता है ... यह सभी प्रभावों को निश्चित रूप से निर्धारित करने के लिए बहुत जटिल हो जाता है लेकिन वे मुख्य रूप से नकारात्मक।
स्रोत कोड बनाम कार्यान्वयन में कार्यक्षमता
आम तौर पर, आपके प्रश्न को C और C ++ टैग किया जाता है। तीसरी पीढ़ी की भाषाओं के रूप में, वे विशेष रूप से अंतर्निहित सीपीयू अनुदेश सेट के विवरण को छिपाने के लिए डिज़ाइन किए गए हैं। अपने भाषा मानकों को संतुष्ट करने के लिए, उन्हें अंतर्निहित हार्डवेयर के न होने पर भी गुणा और स्थानांतरण कार्यों (और कई अन्य) का समर्थन करना चाहिए । ऐसे मामलों में, उन्हें कई अन्य निर्देशों का उपयोग करके आवश्यक परिणाम को संश्लेषित करना चाहिए। इसी तरह, उन्हें फ्लोटिंग पॉइंट ऑपरेशंस के लिए सॉफ्टवेयर सपोर्ट देना होगा अगर सीपीयू की कमी है और कोई एफपीयू नहीं है। आधुनिक सीपीयू सभी समर्थन करते हैं *
और<<
, इसलिए यह बेतुका सैद्धांतिक और ऐतिहासिक लग सकता है, लेकिन महत्वपूर्ण बात यह है कि कार्यान्वयन का चयन करने की स्वतंत्रता दोनों तरीकों से जाती है: भले ही सीपीयू में एक निर्देश है कि सामान्य मामले में स्रोत कोड में अनुरोधित ऑपरेशन को लागू करता है, संकलक मुक्त है कुछ और चुनें जो इसे पसंद करता है क्योंकि यह उस विशिष्ट मामले के लिए बेहतर है जो संकलक के साथ सामना किया गया है।
उदाहरण (एक काल्पनिक विधानसभा भाषा के साथ)
source literal approach optimised approach
#define N 0
int x; .word x xor registerA, registerA
x *= N; move x -> registerA
move x -> registerB
A = B * immediate(0)
store registerA -> x
...............do something more with x...............
अनन्य या ( xor
) जैसे निर्देशों का स्रोत कोड से कोई संबंध नहीं है, लेकिन स्वयं के साथ कुछ भी एक्स-आईएनजी सभी बिट्स को साफ करता है, इसलिए इसका उपयोग कुछ को सेट करने के लिए किया जा सकता है। 0. स्रोत कोड जो मेमोरी पतों का अर्थ रखता है वह किसी भी उपयोग नहीं किया जा सकता है।
इस तरह के हैक का उपयोग तब तक किया जाता रहा है जब तक कि कंप्यूटर आसपास रहे हैं। 3GL के शुरुआती दिनों में, डेवलपर को सुरक्षित करने के लिए कंपाइलर आउटपुट को मौजूदा हार्डकोर हैंड-ऑप्टिमाइज़िंग असेंबली-लैंग देव को संतुष्ट करना पड़ा। समुदाय जो उत्पादित कोड धीमा नहीं था, अधिक क्रिया या अन्यथा बदतर। कंपाइलरों ने जल्दी से बहुत सारी आशाएँ अपनाईं - वे किसी भी असेंबली लैंग्वेज प्रोग्रामर की तुलना में एक बेहतर केंद्रीकृत स्टोर बन गए, हालाँकि यह हमेशा हो सकता है कि हमेशा एक विशिष्ट अनुकूलन याद रहे जो एक विशिष्ट मामले में महत्वपूर्ण होता है - मानव कभी-कभी हो सकता है अखरोट इसे बाहर निकालें और कुछ बेहतर के लिए टटोलें, जबकि कंपाइलर वैसे ही करते हैं जैसे कि उन्हें तब तक बताया जाता है जब तक कोई व्यक्ति उस अनुभव को वापस फीड नहीं करता।
इसलिए, यहां तक कि अगर स्थानांतरण और जोड़ना अभी भी कुछ विशेष हार्डवेयर पर तेज है, तो संकलक के लेखक के काम करने की संभावना बिल्कुल ठीक है जब यह सुरक्षित और फायदेमंद दोनों है।
रख-रखाव
यदि आपका हार्डवेयर बदल जाता है, तो आप पुनः कनेक्ट कर सकते हैं और यह लक्ष्य CPU को देखेगा और एक और सर्वश्रेष्ठ विकल्प बना सकता है, जबकि आप कभी भी अपने "ऑप्टिमाइज़ेशन" या सूची को फिर से देखना नहीं चाहते हैं कि संकलन वातावरण में गुणा का उपयोग करना चाहिए और जिसे शिफ्ट करना चाहिए। 10+ साल पहले लिखे गए सभी गैर-शक्ति-दो-बिट बिट-शिफ्ट किए गए "अनुकूलन" के बारे में सोचें जो अब आधुनिक प्रोसेसर पर चलने वाले कोड को धीमा कर रहे हैं ...!
शुक्र है, जीसीसी जैसे अच्छे संकलक आम तौर पर बिटशिफ्ट्स और अंकगणित की एक श्रृंखला को एक प्रत्यक्ष गुणन के साथ बदल सकते हैं जब कोई अनुकूलन सक्षम होता है (यानी ...main(...) { return (argc << 4) + (argc << 2) + argc; }
-> imull $21, 8(%ebp), %eax
) तो कोड को ठीक किए बिना भी एक पुनर्संयोजन मदद कर सकता है, लेकिन इसकी गारंटी नहीं है।
स्ट्रेंथ बिटशफ्टिंग कोड लागू करने वाला गुणन या विभाजन, जो आप वैचारिक रूप से हासिल करने की कोशिश कर रहे थे, उससे बहुत कम अभिव्यंजक है, इसलिए अन्य डेवलपर्स इससे भ्रमित होंगे, और एक उलझन में प्रोग्रामर की बग को शुरू करने या पवित्रता को बहाल करने के प्रयास में कुछ आवश्यक हटाने की संभावना है। यदि आप केवल गैर-स्पष्ट चीजें करते हैं, जब वे वास्तव में लाभप्रद होते हैं, और फिर उन्हें अच्छी तरह से दस्तावेज करते हैं (लेकिन किसी अन्य सामान को वैसे भी दस्तावेज नहीं करते हैं), हर कोई खुश हो जाएगा।
सामान्य समाधान बनाम आंशिक समाधान
यदि आपके पास कुछ अतिरिक्त ज्ञान है, जैसे कि आपका int
वास्तव में केवल मूल्यों को संग्रहीत करना होगा x
, y
और z
, तो आप कुछ निर्देशों को काम करने में सक्षम हो सकते हैं जो उन मूल्यों के लिए काम करते हैं और जब कंपाइलर आपके पास नहीं है तो आपको अपना परिणाम अधिक तेज़ी से प्राप्त होगा। उस अंतर्दृष्टि और एक कार्यान्वयन की आवश्यकता है जो सभी int
मूल्यों के लिए काम करता है । उदाहरण के लिए, अपने प्रश्न पर विचार करें:
बिट ऑपरेटरों का उपयोग करके गुणा और भाग प्राप्त किया जा सकता है ...
आप गुणन का वर्णन करते हैं, लेकिन विभाजन के बारे में कैसे?
int x;
x >> 1; // divide by 2?
सी ++ मानक 5.8 के अनुसार:
-3- E1 >> E2 का मान E1 सही-स्थानांतरित E2 बिट स्थिति है। यदि E1 में एक अहस्ताक्षरित प्रकार है या यदि E1 में एक हस्ताक्षरित प्रकार और एक नॉनगेटिव वैल्यू है, तो परिणाम का मान E2 के भाग 2 से विभाजित E1 के भागफल का अभिन्न हिस्सा है जो कि बिजली E2 को बढ़ाता है। यदि E1 में एक हस्ताक्षरित प्रकार और एक नकारात्मक मूल्य है, तो परिणामी मूल्य कार्यान्वयन-परिभाषित है।
इसलिए, आपकी बिट शिफ्ट में x
नकारात्मक होने पर कार्यान्वयन परिभाषित परिणाम होता है: यह विभिन्न मशीनों पर एक ही तरह से काम नहीं कर सकता है। लेकिन, /
कहीं अधिक अनुमानित रूप से काम करता है। (यह पूरी तरह से संगत नहीं भी हो सकता है , क्योंकि विभिन्न मशीनों में नकारात्मक संख्याओं के अलग-अलग प्रतिनिधित्व हो सकते हैं, और इसलिए अलग-अलग रेंज भी हो सकती हैं जब प्रतिनिधित्व करने वाले बिट्स की समान संख्या होती है।)
आप कह सकते हैं "मुझे परवाह नहीं है ... जो int
कर्मचारी की उम्र का भंडारण कर रहा है, यह कभी भी नकारात्मक नहीं हो सकता है"। यदि आपके पास उस तरह की विशेष जानकारी है, तो हाँ - आपका >>
सुरक्षित अनुकूलन कंपाइलर द्वारा पारित किया जा सकता है जब तक कि आप स्पष्ट रूप से अपने कोड में नहीं करते। लेकिन, यह जोखिम भरा है और शायद ही कभी उपयोगी होता है जब आपके पास इस तरह की अंतर्दृष्टि नहीं होगी, और एक ही कोड पर काम करने वाले अन्य प्रोग्रामर यह नहीं जान पाएंगे कि आपने डेटा की कुछ असामान्य अपेक्षाओं पर घर को दांव पर लगा दिया है ' संभालना होगा ... क्या लगता है कि उन्हें पूरी तरह से सुरक्षित परिवर्तन आपके "अनुकूलन" के कारण हो सकता है।
क्या इस तरह का कोई इनपुट है जिसे इस तरह से गुणा या विभाजित नहीं किया जा सकता है?
हां ... जैसा कि ऊपर उल्लेख किया गया है, ऋणात्मक संख्याओं में बिट-शिफ्टिंग द्वारा "विभाजित" होने पर कार्यान्वयन परिभाषित व्यवहार होता है।