सी में शिफ्ट ऑपरेटरों का उपयोग करके गुणा और भाग वास्तव में तेज है?


288

उदाहरण के लिए, बिट ऑपरेटरों का उपयोग करके गुणन और विभाजन प्राप्त किया जा सकता है

i*2 = i<<1
i*3 = (i<<1) + i;
i*10 = (i<<3) + (i<<1)

और इसी तरह।

यह वास्तव में तेजी से सीधे (i<<3)+(i<<1)उपयोग i*10करने की तुलना में 10 के साथ गुणा करने के लिए उपयोग करने के लिए तेज़ है ? क्या इस तरह का कोई इनपुट है जिसे इस तरह से गुणा या विभाजित नहीं किया जा सकता है?


8
वास्तव में, दो की शक्ति के अलावा एक निरंतर अन्य द्वारा सस्ता विभाजन संभव है, लेकिन एक मुश्किल सबजेट जिससे आप अपने प्रश्न में "/ मंडल ... / विभाजित" के साथ न्याय नहीं कर रहे हैं। उदाहरण के लिए देखें hackersdelight.org/divcMore.pdf (या यदि आप कर सकते हैं तो पुस्तक "हैकर की खुशी" प्राप्त करें)।
पास्कल क्यूक

46
यह कुछ ऐसा लगता है जिसे आसानी से परखा जा सकता है।
जुआनकोपंजा १५'११

25
हमेशा की तरह - यह निर्भर करता है। एक बार मैंने एक इंटेल 8088 (आईबीएम पीसी / एक्सटी) पर असेंबलर में यह कोशिश की, जहां एक गुणा ने एक बिलियन घड़ियों को लिया। शिफ्ट्स और ऐड्स ने बहुत तेजी से क्रियान्वित किया, इसलिए यह एक अच्छा विचार था। हालाँकि, बस यूनिट को गुणा करते समय निर्देश कतार को भरने के लिए स्वतंत्र था और अगला निर्देश तुरंत शुरू हो सकता था। शिफ्ट की एक श्रृंखला के बाद और निर्देश कतार खाली हो जाएगी और सीपीयू को अगले निर्देश के लिए मेमोरी (एक बार में एक बाइट!) से प्राप्त करने के लिए इंतजार करना होगा। नाप, नाप, नाप!
बो पर्सन

19
इसके अलावा, इस बात से सावधान रहें कि अहस्ताक्षरित पूर्णांक के लिए राइट-शिफ्टिंग केवल अच्छी तरह से परिभाषित है । यदि आपके पास एक हस्ताक्षरित पूर्णांक है, तो यह परिभाषित नहीं है कि 0 या उच्चतम बिट बाईं ओर से गद्देदार हैं। (और एक साल बाद कोड पढ़ने के लिए किसी और (यहां तक ​​कि खुद के लिए) के समय को मत भूलना!)
केरेक एसबी

29
दरअसल, एक अच्छा अनुकूलन करने वाला कंपाइलर तेजी से होने पर शिफ्ट के साथ गुणा और भाग को लागू करेगा।
पीटर जी।

जवाबों:


487

संक्षिप्त उत्तर: संभावना नहीं है।

लंबा उत्तर: आपके कंपाइलर में एक ऑप्टिमाइज़र होता है जो जानता है कि आपके टारगेट प्रोसेसर आर्किटेक्चर को जितनी जल्दी हो सके उतनी तेज़ी से गुणा करना है। आपका सबसे अच्छा शर्त संकलक को आपके इरादे को स्पष्ट रूप से बताना है (यानी i <1 के बजाय 2 *) और यह तय करने दें कि सबसे तेज़ विधानसभा / मशीन कोड अनुक्रम क्या है। यह भी संभव है कि प्रोसेसर ने खुद को शिफ्ट के अनुक्रम के रूप में गुणा निर्देश लागू किया है और माइक्रोकोड में जोड़ता है।

नीचे पंक्ति - इस बारे में चिंता करने में बहुत समय खर्च न करें। अगर आपका मतलब शिफ्ट करना है, तो शिफ्ट करें। यदि आप गुणा करना चाहते हैं, तो गुणा करें। शब्दार्थ को स्पष्ट करें - आपके सहकर्मी आपको बाद में धन्यवाद देंगे। या, अधिक संभावना है, यदि आप अन्यथा करते हैं तो आप बाद में शाप दे सकते हैं।


31
हां, जैसा कि कहा गया है कि लगभग हर आवेदन के लिए संभावित लाभ पूरी तरह से अस्पष्टता को पेश करेगा। समय से पहले इस तरह के अनुकूलन के बारे में चिंता न करें। अर्धवार्षिक रूप से स्पष्ट करें, अड़चनों की पहचान करें और वहां से अनुकूलन करें ...
डेव

4
सहमत, पठनीयता और स्थिरता के लिए अनुकूलन शायद आपको अधिक समय वास्तव में उन चीजों को अनुकूलित करने में खर्च करने के लिए शुद्ध करेगा जो प्रोफाइलर कहते हैं कि गर्म रास्ते हैं।
doug65536 21

5
इन टिप्पणियों से ऐसा लगता है जैसे आप संकलक को अपना काम करने के तरीके से संभावित प्रदर्शन पर छोड़ रहे हैं। ऐसी बात नहीं है। आप वास्तव में x86 पर शिफ्ट संस्करण से बेहतर कोड प्राप्त gcc -O3करते हैंreturn i*10 । जैसा कि कोई व्यक्ति संकलक आउटपुट को बहुत देखता है (मेरे कई asm / ऑप्टिमाइज़ेशन उत्तर देखें), मैं आश्चर्यचकित नहीं हूं। कई बार ऐसा होता है कि यह कंपाइलर को काम करने के एक तरीके में मदद कर सकता है , लेकिन यह उनमें से एक नहीं है। पूर्णांक गणित में gcc अच्छा है, क्योंकि यह महत्वपूर्ण है।
पीटर कॉर्ड्स

बस एक arduino स्केच डाउनलोड किया है जिसमें millis() >> 2; क्या सिर्फ बांटना पूछना बहुत ज्यादा होता?
पॉल विआलैंड

1
मैंने अनुकूलन बनाम -ओ 3 के साथ कोर्टेक्स-ए 9 (जिसमें कोई हार्डवेयर विभाजन नहीं है) के लिए i / 32बनाम i >> 5और i / 4बनाम i >> 2जीसीसी पर परीक्षण किया और परिणामस्वरूप विधानसभा बिल्कुल समान थी। मुझे पहले डिवीजनों का उपयोग करना पसंद नहीं था, लेकिन यह मेरे इरादे का वर्णन करता है और आउटपुट समान है।
रोज़

91

माप का एक ठोस बिंदु: कई साल पहले, मैंने अपने हैशिंग एल्गोरिथ्म के दो संस्करणों को बेंचमार्क किया:

unsigned
hash( char const* s )
{
    unsigned h = 0;
    while ( *s != '\0' ) {
        h = 127 * h + (unsigned char)*s;
        ++ s;
    }
    return h;
}

तथा

unsigned
hash( char const* s )
{
    unsigned h = 0;
    while ( *s != '\0' ) {
        h = (h << 7) - h + (unsigned char)*s;
        ++ s;
    }
    return h;
}

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

ध्यान दें कि यह कुछ 15 साल पहले की तरह था। उम्मीद है, तब से कंपाइलर्स केवल बेहतर हो गए हैं, इसलिए आप कंपाइलर पर सही काम कर सकते हैं, शायद आपके लिए बेहतर हो। (इसके अलावा, कारण यह है कि कोड ऐसा लगता है क्योंकि यह 15 साल पहले खत्म हो गया था। मैं स्पष्ट रूप से std::stringआज और पुनरावृत्तियों का उपयोग करूंगा ।)


5
आपको निम्नलिखित ब्लॉग पोस्ट में रुचि हो सकती है, जिसमें लेखक ध्यान देता है कि आधुनिक अनुकूलन कंपाइलर्स रिवर्स-इंजीनियर कॉमन पैटर्न से लगते हैं जो प्रोग्रामर उन्हें अपने गणितीय रूपों में अधिक कुशल सोचने के लिए उपयोग कर सकते हैं ताकि वास्तव में उनके लिए सबसे कुशल निर्देश अनुक्रम उत्पन्न हो सके। । शेप-of-code.coding-guidelines.com/2009/06/30/…
पास्कल कूक

@PascalCuoq इस बारे में वास्तव में कुछ भी नया नहीं है। मैंने 20 साल पहले सन सीसी के लिए बहुत समान चीज की खोज की थी।
जेम्स कांजे

67

यहां अन्य सभी अच्छे उत्तरों के अलावा, मुझे एक और कारण बताते हैं कि जब आप विभाजित या गुणा करते हैं तो पाली का उपयोग न करें। मैंने कभी नहीं देखा कि किसी ने गुणा और जोड़ के सापेक्ष पूर्वाग्रह को भूलकर बग का परिचय दिया हो। मैं शुरू की जब रखरखाव प्रोग्रामर भूल गया कि "गुणा" एक बदलाव के माध्यम से है कीड़े देखा है तार्किक एक गुणा नहीं बल्कि वाक्य रचना गुणन के रूप में ही वरीयता का। x * 2 + zऔर x << 1 + zबहुत अलग हैं!

यदि आप संख्याओं पर काम कर रहे हैं तो अंकगणितीय ऑपरेटरों का उपयोग करें + - * / %। यदि आप बिट्स के सरणियों पर काम कर रहे हैं, तो बिट ट्विडलिंग ऑपरेटरों का उपयोग करें & ^ | >>। उन्हें मिश्रण मत करो; एक अभिव्यक्ति जिसमें बिट ट्विडलिंग और अंकगणित होता है, एक बग होने की प्रतीक्षा करता है।


5
साधारण कोष्ठक से परहेज?
जोएल बी

21
@ जॉयल: ज़रूर। अगर आपको याद है कि आपको उनकी जरूरत है। मेरा कहना है कि यह भूलना आसान है कि आप क्या करते हैं। जो लोग "x << 1" पढ़ने की मानसिक आदत में पड़ जाते हैं, जैसे कि यह "x * 2" थे, यह सोचने की मानसिक आदत में मिलता है कि << गुणन के रूप में वही वरीयता है, जो यह नहीं है।
एरिक लिपर्ट

1
खैर, मैं अभिव्यक्ति "हाय (8 << 8) + लो" "हाय * 256 + लो" की तुलना में अधिक आशय-प्रकट करता हूं। संभवतः यह स्वाद की बात है, लेकिन कभी-कभी बिट-ट्विडलिंग लिखना अधिक स्पष्ट होता है। ज्यादातर मामलों में हालांकि मैं आपकी बात से पूरी तरह सहमत हूं।
इवान डानिलोव

32
@ इवान: और "(हाय << 8) | लो" और भी स्पष्ट है। बिट सरणी के निम्न बिट्स को सेट करना पूर्णांकों के अतिरिक्त नहीं है । यह बिट्स सेट कर रहा है , इसलिए बिट्स सेट करने वाला कोड लिखें।
एरिक लिपर्ट

1
वाह। पहले इस तरह से नहीं सोचा था। धन्यवाद।
इवान डानिलोव

50

यह प्रोसेसर और कंपाइलर पर निर्भर करता है। कुछ कंपाइलर पहले से ही इस तरह से कोड का अनुकूलन करते हैं, अन्य नहीं। इसलिए आपको हर बार अपने कोड को इस तरह से अनुकूलित करने की आवश्यकता है।

जब तक आप को अनुकूलित करने की आवश्यकता नहीं है, मैं अपने स्रोत कोड को केवल एक विधानसभा निर्देश या प्रोसेसर चक्र को बचाने के लिए हाथापाई नहीं करूंगा।


3
बस एक मोटा अनुमान जोड़ने के लिए: एक विशिष्ट 16-बिट प्रोसेसर (80C166) पर दो ints को जोड़ने पर 1-2 चक्र, 10 चक्र पर एक गुणा और 20 चक्र पर एक विभाजन आता है। यदि आप i * 10 को कई ऑप्स में चुनते हैं, तो प्रत्येक मूव-ऑपरेशन को प्लस करें (प्रत्येक एक और +1 चक्र)। सबसे आम संकलक (केइल / टास्किंग) 2 की शक्ति से गुणा / भाग के लिए अनुकूलन नहीं करते हैं
जेन्स

55
और सामान्य तौर पर, कंपाइलर आपके द्वारा किए गए कोड को बेहतर बनाता है।
user703016

मैं सहमत हूं कि "मात्रा" को गुणा करते समय, गुणा ऑपरेटर आमतौर पर बेहतर होता है, लेकिन जब 2 की शक्तियों द्वारा हस्ताक्षरित मूल्यों को विभाजित किया जाता है, तो >>ऑपरेटर की तुलना में तेज होता है /और, यदि हस्ताक्षर किए गए मान नकारात्मक हो सकते हैं, तो यह अक्सर शब्दार्थ से भी बेहतर होता है। यदि किसी को उस मूल्य की आवश्यकता होती है जो x>>4उत्पादन करता है, तो यह बहुत स्पष्ट है x < 0 ? -((-1-x)/16)-1 : x/16;, और मैं कल्पना नहीं कर सकता कि कैसे एक कंपाइलर उस बाद वाली अभिव्यक्ति को कुछ अच्छा कर सकता है।
सुपरकाट

38

क्या वास्तव में यह कहना तेजी से उपयोग किया जाता है (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कर्मचारी की उम्र का भंडारण कर रहा है, यह कभी भी नकारात्मक नहीं हो सकता है"। यदि आपके पास उस तरह की विशेष जानकारी है, तो हाँ - आपका >>सुरक्षित अनुकूलन कंपाइलर द्वारा पारित किया जा सकता है जब तक कि आप स्पष्ट रूप से अपने कोड में नहीं करते। लेकिन, यह जोखिम भरा है और शायद ही कभी उपयोगी होता है जब आपके पास इस तरह की अंतर्दृष्टि नहीं होगी, और एक ही कोड पर काम करने वाले अन्य प्रोग्रामर यह नहीं जान पाएंगे कि आपने डेटा की कुछ असामान्य अपेक्षाओं पर घर को दांव पर लगा दिया है ' संभालना होगा ... क्या लगता है कि उन्हें पूरी तरह से सुरक्षित परिवर्तन आपके "अनुकूलन" के कारण हो सकता है।

क्या इस तरह का कोई इनपुट है जिसे इस तरह से गुणा या विभाजित नहीं किया जा सकता है?

हां ... जैसा कि ऊपर उल्लेख किया गया है, ऋणात्मक संख्याओं में बिट-शिफ्टिंग द्वारा "विभाजित" होने पर कार्यान्वयन परिभाषित व्यवहार होता है।


2
बहुत अच्छा जवाब। कोर i7 बनाम 486 तुलना ज्ञानवर्धक है!
ड्रयू हॉल

सभी सामान्य आर्किटेक्चर पर, intVal>>1एक ही शब्दार्थ होगा जो उन intVal/2तरीकों से भिन्न होता है जो कभी-कभी उपयोगी होते हैं। यदि किसी को पोर्टेबल फैशन में गणना करने की आवश्यकता होती है, तो जो सामान्य आर्किटेक्चर के लिए उपज होगी intVal >> 1, अभिव्यक्ति को और अधिक जटिल और पढ़ने में कठिन होने की आवश्यकता होगी, और इसके लिए उत्पादित करने के लिए पर्याप्त रूप से अवर कोड उत्पन्न करने की संभावना होगी intVal >> 1
सुपरकैट

35

बस इसे संकलित करने वाली मेरी मशीन पर आज़माया गया है:

int a = ...;
int b = a * 10;

जब यह अलग हो रहा है उत्पादन

MOV EAX,DWORD PTR SS:[ESP+1C] ; Move a into EAX
LEA EAX,DWORD PTR DS:[EAX+EAX*4] ; Multiply by 5 without shift !
SHL EAX, 1 ; Multiply by 2 using shift

यह संस्करण शुद्ध शिफ्टिंग और जोड़ के साथ आपके हाथ से अनुकूलित कोड से तेज है।

आप वास्तव में कभी नहीं जानते हैं कि कंपाइलर के साथ क्या होने वाला है, इसलिए यह बेहतर है कि केवल एक सामान्य गुणन लिखें और उसे अपने इच्छित तरीके को अनुकूलित करने दें, बहुत सटीक मामलों को छोड़कर जहां आप जानते हैं कि कंपाइलर ऑप्टिमाइज़ नहीं कर सकता है।


1
यदि आप वेक्टर के बारे में भाग को छोड़ देते हैं तो आपको इसके लिए एक बड़ा उत्थान मिलेगा। यदि कंपाइलर गुणा को ठीक कर सकता है तो यह भी देख सकता है कि वेक्टर नहीं बदलता है।
बो पर्सन

एक संकलक कैसे जान सकता है कि एक वेक्टर आकार कुछ बहुत खतरनाक धारणाएं बनाए बिना नहीं बदलेगा? या क्या आपने कभी संगामिति के बारे में नहीं सुना है ...
चार्ल्स गुडविन

1
ठीक है, तो आप बिना किसी ताले के एक वैश्विक वेक्टर पर लूप करते हैं? और मैं एक स्थानीय वेक्टर पर लूप करता हूं जिसका पता नहीं लिया गया है, और केवल कॉन्स्टेबल सदस्य फ़ंक्शन को कॉल करते हैं। कम से कम मेरे संकलक को पता चलता है कि वेक्टर आकार नहीं बदलेगा। (और जल्द ही कोई व्यक्ति शायद हमें चैटिंग के लिए झंडा देगा)।
बो पर्सन

1
@BoPersson अंत में, इस समय के बाद, मैंने कंपाइलर को अनुकूलित करने में सक्षम नहीं होने के बारे में अपना बयान हटा दिया vector<T>::size()। मेरा कंपाइलर काफी प्राचीन था! :)
user703016

21

शिफ्टिंग आमतौर पर एक निर्देश स्तर पर गुणा करने की तुलना में बहुत तेज है, लेकिन आप समय से पहले अनुकूलन कर अपना समय बर्बाद कर सकते हैं। कंपाइलर इन आशाओं को अच्छी तरह से संकलन कर सकते हैं। इसे स्वयं करने से पठनीयता प्रभावित होगी और संभवतः प्रदर्शन पर कोई प्रभाव नहीं पड़ेगा। यह शायद केवल इस तरह की चीजों को करने के लिए लायक है यदि आपने प्रोफाइल किया है और इसे एक अड़चन पाया है।

वास्तव में डिवीजन ट्रिक, जिसे 'मैजिक डिवीजन' के रूप में जाना जाता है, वास्तव में बड़ी अदायगी दे सकता है। फिर से आपको पहले यह देखना चाहिए कि क्या इसकी जरूरत है। लेकिन अगर आप इसका उपयोग करते हैं तो आपको यह जानने में मदद करने के लिए आस-पास उपयोगी प्रोग्राम हैं कि एक ही डिवीजन के शब्दार्थ के लिए क्या निर्देश आवश्यक हैं। यहाँ एक उदाहरण है: http://www.masm32.com/board/index.php?topic=12421.0

एक उदाहरण जो मैंने MASM32 पर ओपी के धागे से उठाया है:

include ConstDiv.inc
...
mov eax,9999999
; divide eax by 100000
cdiv 100000
; edx = quotient

उत्पन्न करेगा:

mov eax,9999999
mov edx,0A7C5AC47h
add eax,1
.if !CARRY?
    mul edx
.endif
shr edx,16

7
@ किसी कारण से आपकी टिप्पणी ने मुझे हंसाया और मेरी कॉफी उगल दी। धन्यवाद।
असावियर

30
गणित को पसंद करने के बारे में कोई यादृच्छिक मंच सूत्र नहीं हैं। जो कोई भी गणित पसंद करता है वह जानता है कि एक सच्चे "यादृच्छिक" फोरम थ्रेड को उत्पन्न करना कितना कठिन है।
जोएल बी

1
यह शायद केवल इस तरह की चीजों को करने के लिए लायक है यदि आपने प्रोफाइल किया है और इसे एक अड़चन के रूप में पाया है और विकल्प और प्रोफाइल को फिर से लागू किया है और कम से कम 10 बार प्रदर्शन लाभ प्राप्त करें
रेयान

12

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

पूर्णांक विभाजन अभी भी अपेक्षाकृत धीमा है, इसलिए 2 की शक्ति से विभाजन के बजाय एक बदलाव का उपयोग करना अभी भी एक जीत है, और अधिकांश कंपाइलर इसे अनुकूलन के रूप में लागू करेंगे। ध्यान दें कि इस अनुकूलन को मान्य करने के लिए लाभांश को या तो अहस्ताक्षरित होना चाहिए या सकारात्मक होना चाहिए। एक नकारात्मक लाभांश के लिए पारी और विभाजन बराबर नहीं हैं!

#include <stdio.h>

int main(void)
{
    int i;

    for (i = 5; i >= -5; --i)
    {
        printf("%d / 2 = %d, %d >> 1 = %d\n", i, i / 2, i, i >> 1);
    }
    return 0;
}

आउटपुट:

5 / 2 = 2, 5 >> 1 = 2
4 / 2 = 2, 4 >> 1 = 2
3 / 2 = 1, 3 >> 1 = 1
2 / 2 = 1, 2 >> 1 = 1
1 / 2 = 0, 1 >> 1 = 0
0 / 2 = 0, 0 >> 1 = 0
-1 / 2 = 0, -1 >> 1 = -1
-2 / 2 = -1, -2 >> 1 = -1
-3 / 2 = -1, -3 >> 1 = -2
-4 / 2 = -2, -4 >> 1 = -2
-5 / 2 = -2, -5 >> 1 = -3

इसलिए यदि आप संकलक की मदद करना चाहते हैं तो सुनिश्चित करें कि लाभांश में चर या अभिव्यक्ति स्पष्ट रूप से अहस्ताक्षरित है।


4
PlayStation 3 के PPU पर उदाहरण के लिए इंटीगर मल्टीप्लेक्स को माइक्रोकोड किया गया है, और पूरी पाइपलाइन को स्टाल किया गया है। यह अभी भी कुछ प्लेटफार्मों पर पूर्णांक गुणकों से बचने की सिफारिश की गई है :)
मिस्टर

2
कई अहस्ताक्षरित विभाजन हैं - मानकर संकलक जानता है कि - अहस्ताक्षरित गुणकों का उपयोग करके कैसे कार्यान्वित किया गया। एक या दो गुणा @ कुछ घड़ी चक्र प्रत्येक एक विभाजन के रूप में एक ही काम कर सकते हैं @ 40 चक्र प्रत्येक और ऊपर।
ओलोफ फोर्शेल

1
@ ऑलोफ: सच, लेकिन केवल एक संकलन-समय के आधार पर विभाजन के लिए मान्य है
पॉल आर।

4

यह पूरी तरह से लक्ष्य डिवाइस, भाषा, उद्देश्य आदि पर निर्भर करता है।

वीडियो कार्ड ड्राइवर में पिक्सेल क्रंचिंग? बहुत संभावना है, हाँ!

आपके विभाग के लिए .NET व्यावसायिक अनुप्रयोग? पूरी तरह से कोई कारण भी इसे देखने के लिए।

एक मोबाइल डिवाइस के लिए एक उच्च प्रदर्शन खेल के लिए यह देखने लायक हो सकता है, लेकिन केवल आसान अनुकूलन के बाद ही प्रदर्शन किया गया है।


2

जब तक आपको पूरी तरह से जरूरत न हो और आपके कोड इरादे को गुणा / भाग के बजाय शिफ्टिंग की आवश्यकता न हो।

ठेठ दिन में - आप संभावित रूप से कुछ मशीन चक्र (या ढीले को बचा सकते हैं, क्योंकि कंपाइलर बेहतर जानता है कि क्या अनुकूलित करना है), लेकिन लागत इसके लायक नहीं है - आप वास्तविक नौकरी के बजाय मामूली विवरण पर समय व्यतीत करते हैं, जिससे कोड कठिन हो जाता है और आपके सहकर्मी आपको शाप देंगे।

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


1

जहाँ तक मुझे पता है कि कुछ मशीनों में 16 से 32 मशीन चक्र तक गुणा की आवश्यकता हो सकती है। तो हां , मशीन के प्रकार के आधार पर, बिटशिफ्ट ऑपरेटर कई गुना / विभाजन से अधिक तेज़ होते हैं।

हालाँकि कुछ मशीन में अपना गणित प्रोसेसर होता है, जिसमें गुणन / विभाजन के लिए विशेष निर्देश होते हैं।


7
उन मशीनों के लिए कंपाइलर लिखने वाले लोगों ने भी हैकर्स डिलाइट को पढ़ने और तदनुसार अनुकूलित करने की संभावना जताई है।
बो पर्सन

1

मैं आकर्षित हॉल द्वारा चिह्नित जवाब से सहमत हूं। उत्तर हालांकि कुछ अतिरिक्त नोटों का उपयोग कर सकता है।

सॉफ्टवेयर डेवलपर्स के विशाल बहुमत के लिए प्रोसेसर और कंपाइलर अब सवाल के लिए प्रासंगिक नहीं हैं। हम में से अधिकांश 8088 और एमएस-डॉस से परे हैं। यह शायद केवल उन लोगों के लिए प्रासंगिक है जो अभी भी एम्बेडेड प्रोसेसर के लिए विकसित कर रहे हैं ...

मेरी सॉफ्टवेयर कंपनी में गणित (ऐड / सब / म्यू / डिव) का उपयोग सभी गणित के लिए किया जाना चाहिए। जबकि डेटा प्रकारों के बीच परिवर्तित करते समय Shift का उपयोग किया जाना चाहिए जैसे। n के रूप में बाइट >> 8 ushort और नहीं n / 256।


मैं भी आपसे सहमत हूँ। मैं उसी दिशानिर्देश का अवचेतन रूप से पालन करता हूं, हालांकि मुझे ऐसा करने की कभी औपचारिक आवश्यकता नहीं थी।
ड्रू हॉल

0

हस्ताक्षरित पूर्णांक और दाएं बदलाव बनाम विभाजन के मामले में, यह एक अंतर बना सकता है। ऋणात्मक संख्याओं के लिए, शिफ्ट राउंड नकारात्मक अनंत की ओर गोल होता है जबकि विभाजन शून्य की ओर गोल होता है। बेशक कंपाइलर डिवीजन को कुछ सस्ता में बदल देगा, लेकिन यह आमतौर पर इसे किसी ऐसी चीज में बदल देगा जिसमें डिवीजन के समान ही गोल व्यवहार होता है, क्योंकि यह या तो यह साबित करने में असमर्थ है कि वेरिएबल नकारात्मक नहीं होगा या यह बस नहीं करता है देखभाल। इसलिए यदि आप यह साबित कर सकते हैं कि कोई संख्या ऋणात्मक नहीं होगी या यदि आप परवाह नहीं करते हैं कि यह किस तरह से गोल होगा, तो आप उस अनुकूलन को इस तरह से कर सकते हैं जिससे अंतर होने की अधिक संभावना है।


या नंबर unsigned
रेयान

4
क्या आप सुनिश्चित हैं कि स्थानांतरण व्यवहार मानकीकृत है? मैं इस धारणा के तहत था कि नकारात्मक चींटियों पर सही बदलाव कार्यान्वयन-परिभाषित है।
केरेक एसबी

1
जबकि आपको शायद उस कोड का उल्लेख करना चाहिए जो सही-शिफ्टिंग नकारात्मक संख्याओं के लिए किसी विशेष व्यवहार पर निर्भर करता है, उस आवश्यकता को दस्तावेज़ित करना चाहिए, राइट-शिफ्टिंग का लाभ उन मामलों में बहुत बड़ा है जहां यह स्वाभाविक रूप से सही मूल्य प्राप्त करता है और डिवीजन ऑपरेटर कोड को बर्बाद करने के लिए उत्पन्न करेगा। समय एक अवांछित मूल्य की गणना करता है, जो उपयोगकर्ता कोड को तब अतिरिक्त समय बर्बाद करने के लिए समायोजित करना होगा जो पहले स्थान पर दी गई शिफ्ट को दे सकता है। वास्तव में, अगर मेरे पास मेरे शराबी थे, तो संकलक के पास हस्ताक्षर किए गए विभाजन को करने के प्रयासों में
स्क्वॉक

1
... कोड जो जानता है कि ऑपरेंड पॉजिटिव हैं, ऑप्टिमाइज़ेशन में सुधार कर सकता है अगर यह डिवीजन से पहले बिना डिसाइन किया गया (संभवतः बाद में हस्ताक्षर करने के लिए वापस कास्टिंग), और कोड जो जानता है कि ऑपरेशनल नेगेटिव हो सकता है आम तौर पर उस मामले से किसी भी तरह से निपटना चाहिए (जिस स्थिति में एक के रूप में अच्छी तरह से उन्हें सकारात्मक होने के लिए मान सकते हैं)।
सुपरकाट

0

समान यादृच्छिक संख्याओं के मुकाबले पायथन टेस्ट 100 गुना गुणा एक ही गुणा करता है।

>>> from timeit import timeit
>>> setup_str = 'import scipy; from scipy import random; scipy.random.seed(0)'
>>> N = 10*1000*1000
>>> timeit('x=random.randint(65536);', setup=setup_str, number=N)
1.894096851348877 # Time from generating the random #s and no opperati

>>> timeit('x=random.randint(65536); x*2', setup=setup_str, number=N)
2.2799630165100098
>>> timeit('x=random.randint(65536); x << 1', setup=setup_str, number=N)
2.2616429328918457

>>> timeit('x=random.randint(65536); x*10', setup=setup_str, number=N)
2.2799630165100098
>>> timeit('x=random.randint(65536); (x << 3) + (x<<1)', setup=setup_str, number=N)
2.9485139846801758

>>> timeit('x=random.randint(65536); x // 2', setup=setup_str, number=N)
2.490908145904541
>>> timeit('x=random.randint(65536); x / 2', setup=setup_str, number=N)
2.4757170677185059
>>> timeit('x=random.randint(65536); x >> 1', setup=setup_str, number=N)
2.2316000461578369

तो अजगर में दो की शक्ति से गुणा / भाग के बजाय एक बदलाव करने में, थोड़ा सुधार होता है (विभाजन के लिए ~ 10%; गुणा के लिए ~ 1%)। यदि इसकी दो की गैर-शक्ति है, तो काफी मंदी की संभावना है।

फिर से ये # आपके प्रोसेसर के आधार पर बदल जाएंगे, आपका कंपाइलर (या दुभाषिया - सादगी के लिए अजगर में किया गया)।

हर किसी के साथ के रूप में, समय से पहले अनुकूलन नहीं है। बहुत पठनीय कोड, प्रोफ़ाइल लिखें यदि इसकी गति पर्याप्त नहीं है, और फिर धीमे भागों को अनुकूलित करने का प्रयास करें। याद रखें, आपका कंपाइलर ऑप्टिमाइज़ेशन में आपसे बेहतर है।


0

ऐसे अनुकूलन हैं जो कंपाइलर नहीं कर सकते क्योंकि वे केवल इनपुट के कम सेट के लिए काम करते हैं।

नीचे c ++ नमूना कोड है जो 64 बिट्स "पारस्परिक द्वारा गुणन" करते हुए एक तेज विभाजन कर सकता है। दोनों अंश और हर कुछ निश्चित सीमा से नीचे होना चाहिए। ध्यान दें कि यह 64 बिट्स निर्देशों का उपयोग करने के लिए संकलित किया जाना चाहिए, जो वास्तव में सामान्य विभाजन की तुलना में अधिक तेज है।

#include <stdio.h>
#include <chrono>

static const unsigned s_bc = 32;
static const unsigned long long s_p = 1ULL << s_bc;
static const unsigned long long s_hp = s_p / 2;

static unsigned long long s_f;
static unsigned long long s_fr;

static void fastDivInitialize(const unsigned d)
{
    s_f = s_p / d;
    s_fr = s_f * (s_p - (s_f * d));
}

static unsigned fastDiv(const unsigned n)
{
    return (s_f * n + ((s_fr * n + s_hp) >> s_bc)) >> s_bc;
}

static bool fastDivCheck(const unsigned n, const unsigned d)
{
    // 32 to 64 cycles latency on modern cpus
    const unsigned expected = n / d;

    // At least 10 cycles latency on modern cpus
    const unsigned result = fastDiv(n);

    if (result != expected)
    {
        printf("Failed for: %u/%u != %u\n", n, d, expected);
        return false;
    }

    return true;
}

int main()
{
    unsigned result = 0;

    // Make sure to verify it works for your expected set of inputs
    const unsigned MAX_N = 65535;
    const unsigned MAX_D = 40000;

    const double ONE_SECOND_COUNT = 1000000000.0;

    auto t0 = std::chrono::steady_clock::now();
    unsigned count = 0;
    printf("Verifying...\n");
    for (unsigned d = 1; d <= MAX_D; ++d)
    {
        fastDivInitialize(d);
        for (unsigned n = 0; n <= MAX_N; ++n)
        {
            count += !fastDivCheck(n, d);
        }
    }
    auto t1 = std::chrono::steady_clock::now();
    printf("Errors: %u / %u (%.4fs)\n", count, MAX_D * (MAX_N + 1), (t1 - t0).count() / ONE_SECOND_COUNT);

    t0 = t1;
    for (unsigned d = 1; d <= MAX_D; ++d)
    {
        fastDivInitialize(d);
        for (unsigned n = 0; n <= MAX_N; ++n)
        {
            result += fastDiv(n);
        }
    }
    t1 = std::chrono::steady_clock::now();
    printf("Fast division time: %.4fs\n", (t1 - t0).count() / ONE_SECOND_COUNT);

    t0 = t1;
    count = 0;
    for (unsigned d = 1; d <= MAX_D; ++d)
    {
        for (unsigned n = 0; n <= MAX_N; ++n)
        {
            result += n / d;
        }
    }
    t1 = std::chrono::steady_clock::now();
    printf("Normal division time: %.4fs\n", (t1 - t0).count() / ONE_SECOND_COUNT);

    getchar();
    return result;
}

0

मुझे लगता है कि एक मामले में जिसे आप दो की शक्ति से गुणा या विभाजित करना चाहते हैं, आप बिटशिफ्ट ऑपरेटरों का उपयोग करके गलत नहीं हो सकते, भले ही कंपाइलर उन्हें MUL / DIV में परिवर्तित कर दे, क्योंकि कुछ प्रोसेसर माइक्रोकोड (वास्तव में, एक मैक्रो) उन्हें वैसे भी, इसलिए उन मामलों के लिए आप एक सुधार प्राप्त करेंगे, खासकर अगर शिफ्ट 1 से अधिक है। या अधिक स्पष्ट रूप से, अगर सीपीयू के पास कोई बिटशिफ्ट ऑपरेटर नहीं है, तो यह वैसे भी एक MUL / DIV होगा, लेकिन अगर सीपीयू है बिटशिफ्ट ऑपरेटर, आप एक माइक्रोकोड शाखा से बचते हैं और यह कुछ निर्देश कम है।

मैं अभी कुछ कोड लिख रहा हूं जिसके लिए बहुत अधिक दोहरीकरण / संचालन की आवश्यकता है क्योंकि यह एक घने बाइनरी ट्री पर काम कर रहा है, और एक और ऑपरेशन है जो मुझे संदेह है कि इसके अलावा अधिक इष्टतम हो सकता है - एक बाईं ओर (दो की शक्ति गुणा ) शिफ्ट के साथ। इसे लेफ्ट शिफ्ट और एक्सोर से बदला जा सकता है यदि शिफ्ट आपके द्वारा जोड़े जाने वाले बिट्स की संख्या से अधिक व्यापक है, तो आसान उदाहरण है (i << 1) ^ 1, जो एक को दोगुना करने के लिए जोड़ता है। यह निश्चित रूप से एक सही शिफ्ट (दो डिवाइड की शक्ति) पर लागू नहीं होता है क्योंकि केवल एक लेफ्ट (थोड़ा एंडियन) शिफ्ट शून्य के साथ अंतर को भरता है।

मेरे कोड में, इन दो / दो से गुणा / विभाजित करें और दो ऑपरेशन की शक्तियां बहुत गहन रूप से उपयोग की जाती हैं और क्योंकि सूत्र पहले से ही बहुत कम हैं, प्रत्येक निर्देश जिसे समाप्त किया जा सकता है, एक पर्याप्त लाभ हो सकता है। यदि प्रोसेसर इन बिटशिफ्ट ऑपरेटरों का समर्थन नहीं करता है, तो कोई लाभ नहीं होगा, लेकिन न ही कोई नुकसान होगा।

इसके अलावा, मैं जो एल्गोरिदम लिख रहा हूं, वे नेत्रहीन रूप से उन आंदोलनों का प्रतिनिधित्व करते हैं जो इस अर्थ में होते हैं कि वे वास्तव में अधिक स्पष्ट हैं। एक बाइनरी ट्री के बाएं हाथ बड़ा है, और दायां छोटा है। इसके साथ ही, मेरे कोड में, विषम और सम संख्याओं का एक विशेष महत्व है, और पेड़ में सभी बाएं हाथ के बच्चे विषम और सभी दाहिने हाथ के बच्चे हैं, और जड़ भी हैं। कुछ मामलों में, जो मैंने अभी तक सामना नहीं किया है, लेकिन हो सकता है, ओह, वास्तव में, मैंने यह भी नहीं सोचा था, x & 1, x% 2 की तुलना में अधिक इष्टतम ऑपरेशन हो सकता है। x और 1 सम संख्या पर शून्य का उत्पादन करेगा, लेकिन विषम संख्या के लिए 1 का उत्पादन करेगा।

केवल विषम / यहां तक ​​कि पहचान से थोड़ा आगे जाने पर, अगर मुझे x और 3 के लिए शून्य मिलता है, तो मुझे पता है कि 4 हमारी संख्या का एक कारक है, और 8 के लिए x% 7 के लिए समान है, और इसी तरह। मुझे पता है कि इन मामलों को शायद सीमित उपयोगिता मिली है, लेकिन यह जानना अच्छा है कि आप मापांक ऑपरेशन से बच सकते हैं और इसके बजाय बिटवाइज़ लॉजिक ऑपरेशन का उपयोग कर सकते हैं, क्योंकि बिटवाइज़ ऑपरेशन लगभग हमेशा सबसे तेज़ होते हैं, और कम से कम कंपाइलर के अस्पष्ट होने की संभावना होती है।

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



0

यदि आप gcc संकलक पर x + x, x * 2 और x << 1 वाक्यविन्यास के लिए आउटपुट की तुलना करते हैं, तो आपको x86 असेंबली में समान परिणाम मिलेगा: https://godbolt.org/z/JLpp0j

        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     eax, DWORD PTR [rbp-4]
        add     eax, eax
        pop     rbp
        ret

तो आप जो टाइप करते हैं, उससे स्वतंत्र रूप से अपना सर्वश्रेष्ठ समाधान निर्धारित करने के लिए जीसीसी को पर्याप्त स्मार्ट समझ सकते हैं ।


0

मैं भी यह देखना चाहता था कि क्या मैं सदन को हरा सकता हूं। यह किसी भी संख्या के लिए किसी भी संख्या गुणा से अधिक सामान्य बिटवाइज़ है। मैक्रोज़ मैंने बनाया है जो सामान्य * गुणन की तुलना में दोगुने से लगभग 25% अधिक है। जैसा कि दूसरों ने कहा है कि अगर यह 2 के कई के करीब है या 2 के कुछ गुणकों से बना है तो आप जीत सकते हैं। जैसे X * 23 का बना (X << 4) + (X << 2) + (X << 1) + X धीमा होने वाला है तो X * 65 (X << 6) + X से बना है।

#include <stdio.h>
#include <time.h>

#define MULTIPLYINTBYMINUS(X,Y) (-((X >> 30) & 1)&(Y<<30))+(-((X >> 29) & 1)&(Y<<29))+(-((X >> 28) & 1)&(Y<<28))+(-((X >> 27) & 1)&(Y<<27))+(-((X >> 26) & 1)&(Y<<26))+(-((X >> 25) & 1)&(Y<<25))+(-((X >> 24) & 1)&(Y<<24))+(-((X >> 23) & 1)&(Y<<23))+(-((X >> 22) & 1)&(Y<<22))+(-((X >> 21) & 1)&(Y<<21))+(-((X >> 20) & 1)&(Y<<20))+(-((X >> 19) & 1)&(Y<<19))+(-((X >> 18) & 1)&(Y<<18))+(-((X >> 17) & 1)&(Y<<17))+(-((X >> 16) & 1)&(Y<<16))+(-((X >> 15) & 1)&(Y<<15))+(-((X >> 14) & 1)&(Y<<14))+(-((X >> 13) & 1)&(Y<<13))+(-((X >> 12) & 1)&(Y<<12))+(-((X >> 11) & 1)&(Y<<11))+(-((X >> 10) & 1)&(Y<<10))+(-((X >> 9) & 1)&(Y<<9))+(-((X >> 8) & 1)&(Y<<8))+(-((X >> 7) & 1)&(Y<<7))+(-((X >> 6) & 1)&(Y<<6))+(-((X >> 5) & 1)&(Y<<5))+(-((X >> 4) & 1)&(Y<<4))+(-((X >> 3) & 1)&(Y<<3))+(-((X >> 2) & 1)&(Y<<2))+(-((X >> 1) & 1)&(Y<<1))+(-((X >> 0) & 1)&(Y<<0))
#define MULTIPLYINTBYSHIFT(X,Y) (((((X >> 30) & 1)<<31)>>31)&(Y<<30))+(((((X >> 29) & 1)<<31)>>31)&(Y<<29))+(((((X >> 28) & 1)<<31)>>31)&(Y<<28))+(((((X >> 27) & 1)<<31)>>31)&(Y<<27))+(((((X >> 26) & 1)<<31)>>31)&(Y<<26))+(((((X >> 25) & 1)<<31)>>31)&(Y<<25))+(((((X >> 24) & 1)<<31)>>31)&(Y<<24))+(((((X >> 23) & 1)<<31)>>31)&(Y<<23))+(((((X >> 22) & 1)<<31)>>31)&(Y<<22))+(((((X >> 21) & 1)<<31)>>31)&(Y<<21))+(((((X >> 20) & 1)<<31)>>31)&(Y<<20))+(((((X >> 19) & 1)<<31)>>31)&(Y<<19))+(((((X >> 18) & 1)<<31)>>31)&(Y<<18))+(((((X >> 17) & 1)<<31)>>31)&(Y<<17))+(((((X >> 16) & 1)<<31)>>31)&(Y<<16))+(((((X >> 15) & 1)<<31)>>31)&(Y<<15))+(((((X >> 14) & 1)<<31)>>31)&(Y<<14))+(((((X >> 13) & 1)<<31)>>31)&(Y<<13))+(((((X >> 12) & 1)<<31)>>31)&(Y<<12))+(((((X >> 11) & 1)<<31)>>31)&(Y<<11))+(((((X >> 10) & 1)<<31)>>31)&(Y<<10))+(((((X >> 9) & 1)<<31)>>31)&(Y<<9))+(((((X >> 8) & 1)<<31)>>31)&(Y<<8))+(((((X >> 7) & 1)<<31)>>31)&(Y<<7))+(((((X >> 6) & 1)<<31)>>31)&(Y<<6))+(((((X >> 5) & 1)<<31)>>31)&(Y<<5))+(((((X >> 4) & 1)<<31)>>31)&(Y<<4))+(((((X >> 3) & 1)<<31)>>31)&(Y<<3))+(((((X >> 2) & 1)<<31)>>31)&(Y<<2))+(((((X >> 1) & 1)<<31)>>31)&(Y<<1))+(((((X >> 0) & 1)<<31)>>31)&(Y<<0))
int main()
{
    int randomnumber=23;
    int randomnumber2=23;
    int checknum=23;
    clock_t start, diff;
    srand(time(0));
    start = clock();
    for(int i=0;i<1000000;i++)
    {
        randomnumber = rand() % 10000;
        randomnumber2 = rand() % 10000;
        checknum=MULTIPLYINTBYMINUS(randomnumber,randomnumber2);
        if (checknum!=randomnumber*randomnumber2)
        {
            printf("s %i and %i and %i",checknum,randomnumber,randomnumber2);
        }
    }
    diff = clock() - start;
    int msec = diff * 1000 / CLOCKS_PER_SEC;
    printf("MULTIPLYINTBYMINUS Time %d milliseconds", msec);
    start = clock();
    for(int i=0;i<1000000;i++)
    {
        randomnumber = rand() % 10000;
        randomnumber2 = rand() % 10000;
        checknum=MULTIPLYINTBYSHIFT(randomnumber,randomnumber2);
        if (checknum!=randomnumber*randomnumber2)
        {
            printf("s %i and %i and %i",checknum,randomnumber,randomnumber2);
        }
    }
    diff = clock() - start;
    msec = diff * 1000 / CLOCKS_PER_SEC;
    printf("MULTIPLYINTBYSHIFT Time %d milliseconds", msec);
    start = clock();
    for(int i=0;i<1000000;i++)
    {
        randomnumber = rand() % 10000;
        randomnumber2 = rand() % 10000;
        checknum= randomnumber*randomnumber2;
        if (checknum!=randomnumber*randomnumber2)
        {
            printf("s %i and %i and %i",checknum,randomnumber,randomnumber2);
        }
    }
    diff = clock() - start;
    msec = diff * 1000 / CLOCKS_PER_SEC;
    printf("normal * Time %d milliseconds", msec);
    return 0;
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.