जब मैं सी में स्थानांतरण और गुणा के बीच के समय में अंतर का परीक्षण करता हूं, तो कोई अंतर नहीं है। क्यूं कर?


28

मुझे सिखाया गया है कि बाइनरी में स्थानांतरण 2 ^ k से गुणा करने की तुलना में बहुत अधिक कुशल है। इसलिए मैं प्रयोग करना चाहता था, और मैंने इसका परीक्षण करने के लिए निम्न कोड का उपयोग किया:

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

int main() {
    clock_t launch = clock();
    int test = 0x01;
    int runs;

    //simple loop that oscillates between int 1 and int 2
    for (runs = 0; runs < 100000000; runs++) {


    // I first compiled + ran it a few times with this:
    test *= 2;

    // then I recompiled + ran it a few times with:
    test <<= 1;

    // set back to 1 each time
    test >>= 1;
    }

    clock_t done = clock();
    double diff = (done - launch);
    printf("%f\n",diff);
}

दोनों संस्करणों के लिए, प्रिंट आउट लगभग 440000 था, 10000 दें या लें। दो संस्करणों के आउटपुट के बीच कोई (दृष्टिगत, कम से कम) महत्वपूर्ण अंतर नहीं था। तो मेरा सवाल यह है कि क्या मेरी कार्यप्रणाली में कुछ गड़बड़ है? वहाँ भी एक दृश्य अंतर होना चाहिए? क्या यह मेरे कंप्यूटर की वास्तुकला, संकलक, या कुछ और के साथ कुछ करना है?


47
जिसे भी आपने सिखाया वह गलत था। आम तौर पर उपयोग किए जाने वाले आर्किटेक्चर पर आमतौर पर उपयोग किए जाने वाले कंपाइलरों के लिए यह विश्वास 1970 के दशक से सच नहीं है। इस दावे का परीक्षण करने के लिए आपके लिए अच्छा है। मैंने स्वर्ग के लिए जावास्क्रिप्ट के बारे में किए गए इस निरर्थक दावे को सुना है ।
एरिक लिपर्ट

21
इन सवालों के जवाब देने का सबसे अच्छा तरीका यह है कि कंपाइलर किस असेंबली कोड को देख रहा है। कंपाइलर्स में आमतौर पर असेंबली भाषा की एक प्रति उत्पन्न करने का विकल्प होता है, जिसे वे उत्पन्न कर रहे हैं। GNU GCC संकलक के लिए यह '-S' है।
चार्ल्स ई। ग्रांट

8
एक को इंगित करना चाहिए कि इसके साथ देखने के बाद gcc -S, कोड के लिए test *= 2वास्तव में संकलित किया जाता है, shll $1, %eax जब gcc -O3 -Sएक लूप भी नहीं होता है। दो घड़ी कॉल एक लाइन के अलावा हैं:callq _clock movq %rax, %rbx callq _clock

6
"मुझे सिखाया गया है कि बाइनरी में स्थानांतरण 2 ^ k से गुणा करने की तुलना में बहुत अधिक कुशल है"; हमें बहुत सी चीजें सिखाई जाती हैं जो गलत हो जाती हैं (या कम से कम तारीख से बाहर)। एक स्मार्ट कंपाइलर दोनों के लिए एक ही शिफ्ट ऑपरेशन का उपयोग करेगा।
जॉन बोडे

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

जवाबों:


44

जैसा कि अन्य उत्तर में कहा गया है, अधिकांश कंपाइलर बिटशिफ्ट्स के साथ किए जाने वाले गुणन को स्वचालित रूप से अनुकूलित करेंगे।

अनुकूलन करते समय यह एक बहुत ही सामान्य नियम है: अधिकांश 'अनुकूलन' वास्तव में इस संकलन को गुमराह करेंगे कि आपका वास्तव में क्या मतलब है, और प्रदर्शन को कम भी कर सकता है।

केवल तभी ऑप्टिमाइज़ करें जब आपने कोई प्रदर्शन समस्या देखी हो और मापा हो कि समस्या क्या है। (और हम जो सबसे अधिक कोड लिखते हैं वह अक्सर निष्पादित नहीं होता है, इसलिए हमें परेशान होने की आवश्यकता नहीं है)

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


20
हमेशा उस ऑपरेशन का उपयोग करें जो शब्दार्थ रूप से सही हो। यदि आप बिट मास्क में हेरफेर कर रहे थे, या बड़े पूर्णांकों के भीतर छोटे पूर्णांकों की स्थिति बदल रहे हैं, तो शिफ्ट उपयुक्त संचालन है।
ddyer

2
क्या कभी भी (व्यावहारिक रूप से बोलना) एक उच्च स्तरीय सॉफ्टवेयर एप्लीकेशन में शिफ्ट ऑपरेटर के गुणन को अनुकूलित करने की आवश्यकता होगी? ऐसा लगता है, क्योंकि संकलक पहले से ही अनुकूलन करता है, कि यह ज्ञान होने के लिए एकमात्र समय उपयोगी है जब बहुत कम स्तर (कम से कम संकलक के नीचे) पर प्रोग्रामिंग की जाती है।
निकोलसफोल्क

11
@ निकोलसफोक नप। वह करें जो समझने में सबसे सरल है। यदि आप सीधे असेंबली लिख रहे थे तो यह उपयोगी हो सकता है ... या यदि आप एक अनुकूलन कंपाइलर लिख रहे थे, तो फिर से यह उपयोगी हो सकता है। लेकिन उन दो मामलों के बाहर इसकी एक चाल है जो यह बताती है कि आप क्या कर रहे हैं और अगला प्रोग्रामर बनाता है (जो एक कुल्हाड़ी हत्या है जो जानता है कि आप कहाँ रहते हैं ) अपना नाम अभिशाप देते हैं और एक शौक लेने के बारे में सोचते हैं।

2
@ नाइकोलाफ़ॉक: इस स्तर पर अनुकूलन वैसे भी सीपीयू आर्किटेक्चर द्वारा लगभग हमेशा अस्पष्ट या रेंडर म्यूट हैं। अगर आप 50 चक्रों को बचाते हैं, तो सिर्फ स्मृति से तर्कों को लाने और उन्हें लिखने में 100 से अधिक का समय लगता है? माइक्रो-ऑप्टिमाइज़ेशन इस तरह से बना है जब मेमोरी सीपीयू की गति पर (या उसके करीब) चलती थी, लेकिन आज इतनी नहीं।
TMN

2
क्योंकि मैं उस बोली के 10% को देखकर थक गया हूं, और क्योंकि यह यहां सिर पर कील मारता है: "इसमें कोई संदेह नहीं है कि दक्षता की कब्र दुरुपयोग की ओर ले जाती है। प्रोग्रामर समय के बारे में सोचने, या चिंता करने के लिए भारी मात्रा में बर्बाद करते हैं। के बारे में, उनके कार्यक्रमों के गैर-राजनीतिक हिस्सों की गति, और दक्षता पर इन प्रयासों का वास्तव में एक मजबूत नकारात्मक प्रभाव पड़ता है जब डिबगिंग और रखरखाव पर विचार किया जाता है। हमें छोटी क्षमता के बारे में भूलना चाहिए , लगभग 97% समय के बारे में कहना चाहिए : समय से पहले अनुकूलन की जड़ है। सभी बुराई। ...
cHao

25

कंपाइलर स्थिरांक को पहचानता है और जहां उपयुक्त हो वहां बदलाव को गुणक में परिवर्तित करता है।


संकलक उन स्थिरांक को पहचानता है जो 2 की शक्तियां हैं .... और परिवर्तन में परिवर्तित होती हैं। सभी स्थिरांक को पाली में नहीं बदला जा सकता है।
जल्‍दी से जल्‍दी हो

4
@quickly_now: उन्हें बदलाव और जोड़ / घटाव के संयोजन में परिवर्तित किया जा सकता है।
मेहरदाद

2
एक क्लासिक कंपाइलर ऑप्टिमाइज़र बग, डिवाइडर को सही शिफ्ट में बदलने के लिए है, जो पॉजिटिव डिविडेंड के लिए काम करता है लेकिन नेगेटिव के लिए 1 से बंद है।
दिलेर

1
@quickly_now मेरा मानना ​​है कि शब्द 'जहां उपयुक्त' इस विचार को शामिल करता है कि कुछ स्थिरांक को शिफ्ट के रूप में फिर से नहीं लिखा जा सकता है।
छत्र

21

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

a *= 320;               // Slower
a = (a<<7) + (a<<9);    // Faster

लेकिन अगर आपके पास दो से अधिक बिट्स थे ...

a *= 324;                        // About same speed
a = (a<<2) + (a<<7) + (a<<9);    // About same speed

a *= 340;                                 // Faster
a = (a<<2) + (a<<4) + (a<<7) + (a<<9);    // Slower

एकल चक्र के साथ PIC18 की तरह थोड़ा माइक्रोकंट्रोलर पर , लेकिन कोई बैरल शिफ्टर नहीं , गुणा अधिक तेज़ है यदि आप 1 बिट से अधिक स्थानांतरण कर रहे हैं।

a  *= 2;   // Exactly the same speed
a <<= 1;   // Exactly the same speed

a  *= 4;   // Faster
a <<= 2;   // Slower

ध्यान दें कि पुराने इंटेल सीपीयू पर जो सच था, उसके विपरीत है।

लेकिन यह अभी भी इतना आसान नहीं है। यदि मुझे सही ढंग से याद है, तो सुपरस्क्लेर आर्किटेक्चर के कारण, एक पेंटियम एक या तो एक से अधिक निर्देश या दो शिफ्ट निर्देश को एक साथ संसाधित करने में सक्षम था (जब तक वे एक दूसरे पर निर्भर नहीं थे)। इसका मतलब है कि यदि आप दो चर को 2 की शक्ति से गुणा करना चाहते हैं , तो स्थानांतरण बेहतर हो सकता है।

a  *= 4;   // 
b  *= 4;   // 

a <<= 2;   // Both lines execute in a single cycle
b <<= 2;   // 

5
+1 "क्या शिफ्टिंग गुणा से अधिक तेज है यह आपके सीपीयू की वास्तुकला पर निर्भर करता है।" वास्तव में इतिहास में थोड़ा सा जाने और यह दिखाने के लिए धन्यवाद कि अधिकांश कंप्यूटर मिथकों का वास्तव में कुछ तार्किक आधार है।
चरण

11

आपको अपने परीक्षण कार्यक्रम के साथ कई समस्याएं हैं।

सबसे पहले, आप वास्तव में के मूल्य का उपयोग नहीं कर रहे हैं test। सी मानक के भीतर कोई रास्ता नहीं है, कि testमामलों का मूल्य । आशावादी इसे हटाने के लिए पूरी तरह से स्वतंत्र है। एक बार इसे हटा देने के बाद, आपका लूप वास्तव में खाली है। केवल दिखने वाला प्रभाव सेट करने के लिए होगा runs = 100000000, लेकिन runsइसका उपयोग नहीं किया जाता है। तो अनुकूलक पूरे लूप को हटा सकता है (और चाहिए!)। आसान तय: गणना किए गए मूल्य को भी प्रिंट करें। ध्यान दें कि एक पर्याप्त रूप से निर्धारित ऑप्टिमाइज़र अभी भी लूप को अनुकूलित कर सकता है (यह पूरी तरह से संकलन समय पर ज्ञात स्थिरांक पर निर्भर करता है)।

दूसरा, आप दो ऑपरेशन करते हैं जो एक दूसरे को रद्द करते हैं। आशावादी को यह नोटिस करने और उन्हें रद्द करने की अनुमति है । फिर से एक खाली लूप छोड़कर, और हटा दिया गया। इसे ठीक करना कठिन है। आप एक unsigned int(इसलिए अतिप्रवाह अपरिभाषित व्यवहार नहीं है) पर स्विच कर सकते हैं , लेकिन निश्चित रूप से इसका परिणाम केवल 0. होता है और test += 1ऑप्टिमाइज़र को पता लगाने के लिए सरल चीजें (जैसे, कहते हैं) पर्याप्त होती हैं, और यह करता है।

अंत में, आप मान लेते हैं कि test *= 2वास्तव में एक बहुतायत से संकलित होने जा रहा है। यह एक बहुत ही सरल अनुकूलन है; यदि बिटशिफ्ट तेज है, तो अनुकूलक इसके बजाय इसका उपयोग करेगा। इसके आस-पास जाने के लिए, आपको कार्यान्वयन-विशिष्ट असेंबली इनलाइन की तरह कुछ का उपयोग करना होगा।

या, मुझे लगता है, जो तेज है उसे देखने के लिए बस अपने माइक्रोप्रोसेसर डेटा शीट की जाँच करें।

जब मैंने gcc -S -O3संस्करण 4.9 का उपयोग करके आपके प्रोग्राम को संकलित करने के असेंबली आउटपुट की जांच की , तो ऑप्टिमाइज़र ने वास्तव में ऊपर प्रत्येक सरल भिन्नता के माध्यम से देखा, और कई और। सभी मामलों में, इसने लूप को हटा दिया (एक स्थिर को असाइन करते हुए test), केवल एक चीज को छोड़ दिया गया था clock(), कन्वर्ट / घटाना, और printf


1
यह भी ध्यान दें कि ऑप्टिमाइज़र कर सकते हैं (और करेगा) स्थिरांक (यहां तक ​​कि एक लूप में) के संचालन को ऑप्टिमाइज़ करता है जैसा कि sqrt c # बनाम sqrt c ++ में दिखाया गया है जहां ऑप्टिमाइज़र एक लूप को प्रतिस्थापित करने में सक्षम था वास्तविक मूल्य के साथ एक मूल्य। उस अनुकूलन को हराने के लिए आपको रनटाइम पर निर्धारित किसी चीज़ का उपयोग करने की आवश्यकता होती है (जैसे कमांड लाइन तर्क)।

@ मिचेल्ट येप। यही मेरा मतलब है "ध्यान दें कि एक पर्याप्त रूप से निर्धारित ऑप्टिमाइज़र अभी भी लूप को दूर कर सकता है (यह पूरी तरह से संकलन समय पर ज्ञात स्थिरांक पर निर्भर करता है)।"
जुलूस

मुझे वही मिल रहा है जो आप कह रहे हैं, लेकिन मुझे नहीं लगता कि कंपाइलर पूरे लूप को हटा रहा है। आप आसानी से पुनरावृत्तियों की संख्या बढ़ाकर इस सिद्धांत का परीक्षण कर सकते हैं। आप देखेंगे कि पुनरावृत्तियों को बढ़ाने से कार्यक्रम को अधिक समय लगता है। यदि लूप पूरी तरह से हटा दिया गया था तो यह मामला नहीं होगा।
डॉलर अक्षय

@ अक्षयलध्या मैं यह नहीं कह सकता कि आपका कंपाइलर क्या कर रहा है, लेकिन मैंने फिर पुष्टि की कि gcc -O3(अब 7.3 के साथ) अभी भी पूरी तरह से लूप को हटा देता है। (यदि आवश्यक हो तो इंट के बजाय लंबे समय तक स्विच करना सुनिश्चित करें, अन्यथा यह अतिप्रवाह के कारण अनंत लूप में अनुकूलन करता है)।
derobert

8

मुझे लगता है कि प्रश्नकर्ता के लिए अधिक विभेदित उत्तर के लिए यह अधिक उपयोगी होगा, क्योंकि मुझे प्रश्नों में और कुछ उत्तरों या टिप्पणियों में कई अलिखित मान्यताएँ दिखाई देती हैं।

शिफ्टिंग और गुणा के परिणामी सापेक्ष रनटाइम का सी के साथ कोई लेना-देना नहीं है। जब मैं सी कहता हूं, तो मेरा मतलब किसी विशिष्ट कार्यान्वयन का उदाहरण नहीं है, जैसे कि जीसीसी का वह संस्करण या संस्करण, लेकिन भाषा। मेरा मतलब यह नहीं है कि यह विज्ञापन अनुपस्थित है, लेकिन उदाहरण के लिए एक चरम उदाहरण का उपयोग करने के लिए: आप सी मानकों का पूरी तरह से मानकों को लागू कर सकते हैं और गुणा करने में एक घंटे का समय लग सकता है, जबकि शिफ्टिंग में मिलीसेकंड - या दूसरे स्थान पर ले जाता है। मुझे C या C ++ में ऐसे किसी भी प्रदर्शन प्रतिबंध के बारे में पता नहीं है।

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

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

वे कारण मुख्य रूप से इस तरह के अनुकूलन की घटी हुई पठनीयता और निरर्थकता का एक संयोजन है, जैसा कि आपको उनके सापेक्ष प्रदर्शन की तुलना करने से पहले ही पता चल गया होगा। हालांकि, मुझे नहीं लगता है कि लोगों की प्रतिक्रिया के रूप में मजबूत होगा यदि गुणा के लिए बदलाव का प्रतिस्थापन इस तरह के अनुकूलन का एकमात्र उदाहरण था। आपके जैसे प्रश्न अक्सर विभिन्न रूपों में और विभिन्न संदर्भों में सामने आते हैं। मुझे लगता है कि अधिक वरिष्ठ इंजीनियर वास्तव में इतनी दृढ़ता से प्रतिक्रिया करते हैं, कम से कम मेरे पास कई बार है, यह है कि जब लोग इस तरह के माइक्रो-ऑप्टिमाइज़ेशन को कोड आधार पर उदारतापूर्वक नियोजित करते हैं, तो बहुत अधिक नुकसान की संभावना है। यदि आप एक बड़े कोड आधार पर Microsoft जैसी कंपनी में काम करते हैं, तो आप अन्य इंजीनियरों के स्रोत कोड को पढ़ने में बहुत समय बिताएंगे, या उसमें कुछ कोड खोजने का प्रयास करेंगे। यह आपका अपना कोड भी हो सकता है कि आप कुछ वर्षों के समय में, विशेष रूप से कुछ सबसे अधिक समय पर, जैसे कि जब आपको पेजर पर आपको कॉल मिल रहा हो, एक प्रोडक्शन आउटेज को ठीक करना होगा। शुक्रवार की रात को ड्यूटी, दोस्तों के साथ मस्ती की एक रात के लिए बाहर जाने के लिए ... यदि आप कोड पढ़ने में इतना समय बिताते हैं, तो आप इसे यथासंभव पठनीय होने की सराहना करेंगे। अपने पसंदीदा उपन्यास को पढ़ने की कल्पना करें, लेकिन प्रकाशक ने एक नया संस्करण जारी करने का फैसला किया है, जहां वे एब्बर का उपयोग करते हैं। सभी ovr th plc bcs तेरा thnk यह svs spc। यह प्रतिक्रिया के समान है कि अन्य इंजीनियरों को आपके कोड में होना चाहिए, यदि आप उन्हें इस तरह के अनुकूलन के साथ छिड़कते हैं। जैसा कि अन्य जवाबों में कहा गया है, यह स्पष्ट करना बेहतर है कि आपका क्या मतलब है,

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

आपने C में गति अंतर क्यों नहीं देखा? सबसे संभावित उत्तर यह है कि यह दोनों एक ही विधानसभा कोड के परिणामस्वरूप थे:

int shift(int i) { return i << 2; }
int multiply(int i) { return i * 2; }

दोनों में संकलित कर सकते हैं

shift(int):
    lea eax, [0+rdi*4]
    ret

अनुकूलन के बिना GCC पर, अर्थात "-O0" ध्वज का उपयोग करके, आपको यह मिल सकता है:

shift(int):
    push    rbp
    mov rbp, rsp
    mov DWORD PTR [rbp-4], edi
    mov eax, DWORD PTR [rbp-4]
    sal eax, 2
    pop rbp
    ret
multiply(int):
    push    rbp
    mov rbp, rsp
    mov DWORD PTR [rbp-4], edi
    mov eax, DWORD PTR [rbp-4]
    add eax, eax
    pop rbp
    ret

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

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

यदि आप संकलक के उन स्मार्टों में से कुछ को पराजित करना चाहते हैं, तो आप अधिक सामान्य पारी और गुणा पद्धति को परिभाषित कर सकते हैं और कुछ इस तरह से समाप्त करेंगे:

int shift(int i, int j) { return i << j; }
int multiply(int i, int j) { return i * j; }

निम्नलिखित विधानसभा कोड प्राप्त कर सकते हैं:

shift(int, int):
    mov eax, edi
    mov ecx, esi
    sal eax, cl
    ret
multiply(int, int):
    mov eax, edi
    imul    eax, esi
    ret

यहां हमारे पास अंततः GCC 4.9 के उच्चतम अनुकूलन स्तर पर भी है, विधानसभा निर्देशों में वह अभिव्यक्ति जो आपने उम्मीद की होगी जब आप शुरू में अपने परीक्षण के लिए निर्धारित करेंगे। मुझे लगता है कि प्रदर्शन अनुकूलन में एक महत्वपूर्ण सबक हो सकता है। हम अपने कोड में ठोस स्थिरांक के लिए चर को प्रतिस्थापित करने के लिए किए गए अंतर को देख सकते हैं, स्मार्टरों के संदर्भ में जो कंपाइलर लागू करने में सक्षम है। माइक्रो-ऑप्टिमाइज़ेशन जैसे शिफ्ट-मल्टीली प्रतिस्थापन कुछ बहुत ही निम्न-स्तरीय अनुकूलन हैं जो एक कंपाइलर आमतौर पर आसानी से कर सकता है। अन्य अनुकूलन जो प्रदर्शन पर बहुत अधिक प्रभाव डालते हैं , उन्हें कोड के इरादे की समझ की आवश्यकता होती हैयह अक्सर संकलक द्वारा सुलभ नहीं होता है या केवल कुछ अनुमान द्वारा अनुमान लगाया जा सकता है। यह वह जगह है जहाँ आप एक सॉफ्टवेयर इंजीनियर के रूप में आते हैं और यह निश्चित रूप से पाली के साथ गुणा गुणकों को शामिल नहीं करता है। इसमें ऐसे कारकों को शामिल किया गया है जो एक ऐसी सेवा से बचते हैं जो I / O का उत्पादन करती है और एक प्रक्रिया को अवरुद्ध कर सकती है। यदि आप अपनी हार्ड डिस्क या, भगवान के पास जाते हैं, तो कुछ अतिरिक्त डेटा के लिए एक दूरस्थ डेटाबेस के लिए, जो आपके पास पहले से ही मेमोरी में मौजूद है, जो आपके द्वारा प्रतीक्षा किए जा रहे समय को एक लाख निर्देशों के निष्पादन के लिए खर्च करता है। अब, मुझे लगता है कि हम आपके मूल प्रश्न से थोड़ा दूर भटक गए हैं, लेकिन मुझे लगता है कि यह एक प्रश्नकर्ता को इंगित करता है, खासकर यदि हम किसी ऐसे व्यक्ति को मान लेते हैं जो अभी अनुवाद और कोड के निष्पादन पर समझ हासिल करना शुरू कर रहा है,

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

तो क्या होगा अगर शिफ्ट गुणा से ज्यादा तेज हो? निश्चित रूप से संकेत हैं कि यह सच क्यों होगा। जीसीसी, जैसा कि आप ऊपर देख सकते हैं, लगता है कि (अनुकूलन के बिना भी) प्रतीत होता है कि अन्य निर्देशों के पक्ष में प्रत्यक्ष गुणा से बचना एक अच्छा विचार है। इंटेल 64 और IA-32 आर्किटेक्चर अनुकूलन संदर्भ मैनुअल आप सीपीयू निर्देश के सापेक्ष मूल्य का अनुमान दे देंगे। एक अन्य संसाधन, जो निर्देश विलंबता और थ्रूपुट पर अधिक ध्यान केंद्रित करता है, वह है http://www.agner.org/optimize/instruction_tables.pdf। ध्यान दें कि वे पूर्ण क्रम के अच्छे प्रेडिक्टर नहीं हैं, लेकिन एक दूसरे के सापेक्ष निर्देशों के प्रदर्शन के। एक तंग पाश में, जैसा कि आपका परीक्षण अनुकरण कर रहा है, "थ्रूपुट" का मीट्रिक सबसे अधिक प्रासंगिक होना चाहिए। यह चक्रों की संख्या है जो किसी दिए गए निर्देश को निष्पादित करते समय एक निष्पादन इकाई को आमतौर पर बांधा जाएगा।

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

नहीं, जहां तक ​​मुझे पता है कि हमने 1970 में या जब भी अचानक गुणन इकाई की लागत के अंतर और बिट शिफ्टर में कुछ गुप्त इंजीनियरिंग सॉस का आविष्कार नहीं किया था। तार्किक गेट्स के संदर्भ में एक सामान्य गुणन, और निश्चित रूप से तार्किक संचालन के संदर्भ में, कई आर्किटेक्चर पर कई परिदृश्यों में बैरल शिफ्टर के साथ एक पारी की तुलना में अभी भी अधिक जटिल है। यह डेस्कटॉप कंप्यूटर पर समग्र रनटाइम में कैसे परिवर्तित होता है, यह थोड़ा अपारदर्शी हो सकता है। मुझे यह पता नहीं है कि वे विशिष्ट प्रोसेसर में कैसे कार्यान्वित किए जाते हैं, लेकिन यहां एक गुणन का विवरण दिया गया है: क्या पूर्णांक गुणन वास्तव में आधुनिक सीपीयू के अलावा एक ही गति है

जबकि यहाँ एक बैरल शिफ्टर की व्याख्या है । पिछले पैराग्राफ में मैंने जिन दस्तावेजों को संदर्भित किया है, वे सीपीयू निर्देशों के प्रॉक्सी द्वारा, संचालन की सापेक्ष लागत पर एक और दृष्टिकोण देते हैं। इंटेल पर इंजीनियरों को अक्सर समान प्रश्न मिलते हैं: इंटेल डेवलपर ज़ोन फ़ोरम पूर्णांक गुणन के लिए घड़ी चक्र और कोर कोर डुअल प्रोसेसर में जोड़

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


6

आप जो देखते हैं वह आशावादी का प्रभाव है।

ऑप्टिमाइज़र का काम परिणामी संकलित कोड को या तो छोटा, या तेज़ करना है (लेकिन शायद ही कभी एक ही समय में दोनों ... लेकिन कई चीजें पसंद हैं ... कोड क्या है इस पर IT DEPENDS)।

PRINCIPLE में, किसी गुणक लाइब्रेरी के लिए कोई भी कॉल, या, अक्सर, यहां तक ​​कि एक हार्डवेयर गुणक के उपयोग से बिटवाइज़ शिफ्ट करने की तुलना में धीमी हो जाएगी।

इसलिए ... यदि भोले संकलक ने ऑपरेशन * 2 के लिए एक लाइब्रेरी को कॉल किया, तो निश्चित रूप से यह बिटवाइज़ शिफ्ट * की तुलना में धीमी गति से चलेगा।

हालाँकि, ऑप्टिमाइज़र पैटर्न का पता लगाने और यह पता लगाने के लिए हैं कि कोड को कैसे छोटा / तेज़ / जो भी बनाया जाए। और जो आपने देखा है वह संकलक का पता लगा रहा है कि * 2 एक पारी के समान है।

बस ब्याज की बात के रूप में मैं आज * 5 की तरह कुछ संचालन के लिए उत्पन्न कोडांतरक को देख रहा था ... वास्तव में उस पर नहीं बल्कि अन्य चीजों को देख रहा था, और जिस तरह से मैंने देखा कि संकलक ने * 5 में बदल दिया था:

  • खिसक जाना
  • खिसक जाना
  • मूल संख्या जोड़ें

इसलिए मेरे कंपाइलर का ऑप्टिमाइज़र इनलाइन शिफ्ट जनरेट करने के लिए पर्याप्त (कम से कम कुछ स्थिरांक के लिए) स्मार्ट था और कॉल के बजाय एक सामान्य प्रयोजन के लिए लाइब्रेरी को गुणा करता है।

कंपाइलर ऑप्टिमाइज़र की कला एक अलग विषय है, जो जादू से भरा है, और पूरे ग्रह पर लगभग 6 लोगों द्वारा वास्तव में ठीक से समझा गया है :)


3

इसके साथ समय की कोशिश करें:

for (runs = 0; runs < 100000000; runs++) {
      ;
}

कंपाइलर को पहचानना चाहिए कि testलूप के प्रत्येक पुनरावृत्ति के बाद मान अपरिवर्तित है, और अंतिम मान testअप्रयुक्त है, और लूप को पूरी तरह से समाप्त कर रहा है।


2

गुणन पारियों और परिवर्धन का एक संयोजन है।

आपके द्वारा उल्लेख किए गए मामले में, मुझे विश्वास नहीं है कि यह मायने रखता है कि संकलक इसका अनुकूलन करता है या नहीं - " xदो से गुणा करें " या तो हो सकता है:

  • xएक जगह के बिट्स को बाईं ओर शिफ्ट करें ।
  • जोड़े xको x

ये प्रत्येक बुनियादी परमाणु संचालन हैं; एक दूसरे से तेज नहीं है।

इसे " xचार से गुणा करें ", (या किसी भी 2^k, k>1) में बदलें और यह थोड़ा अलग है:

  • xबाईं ओर के दो स्थानों को शिफ्ट करें ।
  • जोड़े xको xऔर इसे कहते y, जोड़ने yके लिए y

एक बुनियादी वास्तुकला पर, यह सरल देखने के लिए कि बदलाव और अधिक कुशल है - एक बनाम दो आपरेशन ले, क्योंकि हम नहीं जोड़ सकते हैं yकरने के लिए yजब तक हम जानते हैं कि yहै।

अपने 2^k, k>1विकल्पों को क्रियान्वयन में समान बनाने के लिए उन्हें अनुकूलित करने से रोकने के लिए उपयुक्त विकल्पों के साथ उत्तरार्द्ध (या कोई भी ) आजमाएँ । O(1)बार-बार जोड़ने की तुलना में , आपको लगता है कि शिफ्ट तेज़ है O(k)

जाहिर है, जहां गुणक दो की शक्ति नहीं है, वहां बदलाव और परिवर्धन का संयोजन (प्रत्येक जहां की संख्या गैर-शून्य है) आवश्यक है।


1
"बुनियादी परमाणु ऑपरेशन" क्या है? क्या कोई तर्क नहीं दे सकता है कि एक बदलाव में, ऑपरेशन को समानांतर में हर बिट पर लागू किया जा सकता है, जबकि इसके अलावा सबसे बाईं ओर के बिट्स अन्य बिट्स पर निर्भर करता है?
बरगी

2
@ बर्गी: मैं अनुमान लगा रहा हूं कि उनका मतलब है कि शिफ्ट और ऐड दोनों सिंगल मशीन निर्देश हैं। प्रत्येक के लिए चक्र की गणना देखने के लिए आपको निर्देश सेट प्रलेखन को देखना होगा, लेकिन हां, एक ऐड अक्सर एक बहु-चक्र ऑपरेशन होता है, जबकि एक बदलाव आमतौर पर एक ही चक्र में किया जाता है।
टीएमएन

हां, यह मामला हो सकता है, लेकिन गुणन एक एकल मशीन निर्देश है (हालांकि निश्चित रूप से इसे और अधिक चक्रों की आवश्यकता हो सकती है)
बर्गी

@ बर्गी, वह भी आर्क पर निर्भर है। 32-बिट जोड़ (या एक्स-बिट के रूप में लागू होता है) की तुलना में आप कम चक्रों में किस चाप के बारे में सोच रहे हैं?
OJFord

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

1

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

हालांकि, यह ध्यान दिया जाना चाहिए कि संभावित-नकारात्मक हस्ताक्षरित मूल्यों का विभाजन सही-स्थानांतरण के बराबर नहीं है । जैसी अभिव्यक्ति (x+8)>>4समतुल्य नहीं है (x+8)/16। पूर्व में, 99% कंपाइलरों में, -24 से -9 से -1, -8 से +7 से 0 तक मान और +8 से 1 से 1 के बीच के मानों को दर्शाया जाएगा [राउंडिंग नंबर लगभग सममित रूप से लगभग शून्य]। बाद का नक्शा -39 से -24 से -1, -23 से +7 से 0 तक, और +8 से +1 से +1 [काफी विषमता, और संभवतः वह नहीं था जो इरादा था]। ध्यान दें कि जब मूल्यों को नकारात्मक होने की उम्मीद नहीं होती है, तब भी >>4संभवत: तेजी से कोड का उपयोग करेगा /16जब तक कि कंपाइलर साबित नहीं कर सकता कि मान नकारात्मक नहीं हो सकते।


0

कुछ और जानकारी जो मैंने अभी चेक की है।

X86_64 पर, MUL opcode में 10 चक्र विलंबता और 1/2 चक्र प्रवाह होता है। MOV, ADD और SHL में 1 चक्र की विलंबता है, जिसमें 2.5, 2.5 और 1.7 चक्र थ्रूपुट है।

15 से गुणा करने पर न्यूनतम 3 SHL और 3 ADD ऑप्स की आवश्यकता होगी और शायद MOV के कुछ जोड़े।

https://gmplib.org/~tege/x86-timing.pdf


0

आपकी कार्यप्रणाली त्रुटिपूर्ण है। अपने पाश वृद्धि और हालत की जाँच में ही इतना समय लग रहा है।

  • खाली लूप चलाने का प्रयास करें और समय को मापें (इसे कॉल करें base)।
  • अब 1 शिफ्ट ऑपरेशन जोड़ें और समय को मापें (इसे कॉल करें s1)।
  • अगला 10 शिफ्ट ऑपरेशन जोड़ें और समय को मापें (इसे कॉल करें s2)

अगर सब कुछ सही base-s2हो रहा है तो 10 गुना अधिक होना चाहिए base-s1। वरना यहां कुछ और ही चलन में आ रहा है।

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

int main(){

    int test = 2;
    clock_t launch = clock();

    test << 6;
    test << 6;
    test << 6;
    test << 6;
    //.... 1 million times
    test << 6;

    clock_t done = clock();
    printf("Time taken : %d\n", done - launch);
    return 0;
}

और वहां आपका परिणाम है

1 मिलीसेकंड के तहत 1 मिलियन शिफ्ट ऑपरेशन?

मैंने 64 से गुणा करने के लिए एक ही काम किया और एक ही परिणाम मिला। इसलिए शायद संकलक ऑपरेशन को पूरी तरह से अनदेखा कर रहा है क्योंकि दूसरों ने उल्लेख किया है कि परीक्षण का मूल्य कभी नहीं बदला गया है।

Shiftwise ऑपरेटर परिणाम

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.