java - क्यों(**b!=0) जावा में(!=0 && b!=0) से अधिक तेज़ है?




performance processing-efficiency (4)

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

मैं इसके साथ मूल्यांकन कर सकता हूं

if (a != 0 && b != 0) { /* Some code */ }

या वैकल्पिक रूप से

if (a*b != 0) { /* Some code */ }

क्योंकि मुझे उम्मीद है कि कोड के उस टुकड़े को प्रति बार लाखों बार चलाना होगा, मैं सोच रहा था कि कौन सा तेज होगा। मैंने एक विशाल यादृच्छिक रूप से उत्पन्न सरणी पर उनकी तुलना करके प्रयोग किया था, और मैं यह देखने के लिए भी उत्सुक था कि सरणी की स्पार्सिटी (डेटा का अंश = 0) परिणामों को कैसे प्रभावित करेगा:

long time;
final int len = 50000000;
int arbitrary = 0;
int[][] nums = new int[2][len];

for (double fraction = 0 ; fraction <= 0.9 ; fraction += 0.0078125) {
    for(int i = 0 ; i < 2 ; i++) {
        for(int j = 0 ; j < len ; j++) {
            double random = Math.random();

            if(random < fraction) nums[i][j] = 0;
            else nums[i][j] = (int) (random*15 + 1);
        }
    }

    time = System.currentTimeMillis();

    for(int i = 0 ; i < len ; i++) {
        if( /*insert nums[0][i]*nums[1][i]!=0 or nums[0][i]!=0 && nums[1][i]!=0*/ ) arbitrary++;
    }
    System.out.println(System.currentTimeMillis() - time);
}

और परिणाम दिखाते हैं कि यदि आप "ए" या "बी" की अपेक्षा करते हैं कि यह समय के ~ 3% से अधिक 0 के बराबर है, तो a*b != 0 से तेज है a!=0 && b!=0

मैं यह जानने के लिए उत्सुक हूं कि क्यों। क्या कोई प्रकाश डाल सकता है? क्या यह संकलक है या यह हार्डवेयर स्तर पर है?

संपादित करें: जिज्ञासा से बाहर ... अब जब मैंने शाखा भविष्यवाणी के बारे में जाना, तो मैं सोच रहा था कि ओआर या बी के लिए एनालॉग तुलना क्या दिखाएगा, गैर-शून्य है:

हम उम्मीद के अनुसार शाखा भविष्यवाणी का एक ही प्रभाव देखते हैं, दिलचस्प है कि ग्राफ एक्स-एक्सिस के साथ कुछ हद तक फ़्लिप किया गया है।

अद्यतन करें

1- मैंने विश्लेषण किया कि क्या होता है यह देखने के लिए !(a==0 || b==0)

2- मैंने भी a != 0 || b != 0 शामिल किया a != 0 || b != 0 a != 0 || b != 0 , (a+b) != 0 और (a|b) != 0 शाखा संबंधी भविष्यवाणी के बारे में जानने के बाद, जिज्ञासा से बाहर। लेकिन वे तार्किक रूप से अन्य अभिव्यक्तियों के समतुल्य नहीं होते हैं, क्योंकि सत्य होने के लिए केवल ओ या बी को गैर-शून्य होने की आवश्यकता होती है, इसलिए उन्हें प्रसंस्करण दक्षता के लिए तुलना करने के लिए नहीं है।

3- मैंने उस वास्तविक बेंचमार्क को भी जोड़ा जो मैंने विश्लेषण के लिए इस्तेमाल किया था, जो कि एक मनमाना इंट वैरिएबल है।

4- कुछ लोग a != 0 & b != 0 को शामिल करने का सुझाव दे रहे थे a != 0 && b != 0 a != 0 & b != 0 के विपरीत, इस भविष्यवाणी के साथ कि यह a*b != 0 अधिक निकट व्यवहार करेगा क्योंकि हम निकाल देंगे a*b != 0 शाखा भविष्यवाणी प्रभाव। मुझे नहीं पता था कि & बूलियन चर के साथ इस्तेमाल किया जा सकता है, मुझे लगा कि यह केवल पूर्णांक के साथ द्विआधारी संचालन के लिए उपयोग किया गया था।

नोट: इस संदर्भ में कि मैं इस सब पर विचार कर रहा था, int अतिप्रवाह एक मुद्दा नहीं है, लेकिन यह सामान्य संदर्भों में एक महत्वपूर्ण विचार है।

CPU: Intel Core i7-3610QM @ 2.3GHz

जावा संस्करण: 1.8.0_45
जावा (TM) एसई रनटाइम एनवायरनमेंट (बिल्ड 1.8.0_45-b14)
जावा हॉटस्पॉट (TM) 64-बिट सर्वर VM (बिल्ड 25.45-b02, मिश्रित मोड)


आप यादृच्छिक इनपुट डेटा का उपयोग कर रहे हैं जो शाखाओं को अप्रत्याशित बनाता है। व्यवहारिक शाखाओं में प्रायः (~ 90%) पूर्वानुमेय होता है, इसलिए वास्तविक कोड में शाखात्मक कोड तेजी से होने की संभावना है।

ने कहा कि। मैं नहीं देखता कि कैसे a*b != 0 इससे (a|b) != 0 से तेज हो सकता है। आम तौर पर पूर्णांक गुणन बिटवाइस या की तुलना में अधिक महंगा होता है। लेकिन इस तरह की चीजें कभी-कभी अजीब हो जाती हैं। उदाहरण के लिए देखें "उदाहरण 7: हार्डवेयर जटिलताएँ" उदाहरण कैस्टर इफेक्ट्स की गैलरी से


जब हम गुणन को लेते हैं, भले ही एक संख्या 0 हो, तब उत्पाद 0. होता है जबकि लिखते हैं

    (a*b != 0)

यह उत्पाद के परिणाम का मूल्यांकन करता है जिससे 0. से शुरू होने वाली पुनरावृत्ति की पहली कुछ घटनाओं को समाप्त किया जाता है। इसके परिणामस्वरूप तुलना उस स्थिति से कम है जब

   (a != 0 && b != 0)

जहां हर तत्व की तुलना 0 से की जाती है और उसका मूल्यांकन किया जाता है। इसलिए आवश्यक समय कम है। लेकिन मेरा मानना ​​है कि दूसरी स्थिति आपको अधिक सटीक समाधान दे सकती है।


मैं इस मुद्दे की अनदेखी कर रहा हूं कि आपकी बेंचमार्किंग त्रुटिपूर्ण हो सकती है, और परिणाम का सामना कर सकते हैं।

क्या यह संकलक है या यह हार्डवेयर स्तर पर है?

मुझे लगता है कि बाद में:

  if (a != 0 && b != 0)

2 मेमोरी लोड और दो सशर्त शाखाओं को संकलित करेगा

  if (a * b != 0)

2 मेमोरी लोड, एक गुणा और एक सशर्त शाखा के लिए संकलित करेगा।

यदि हार्डवेयर स्तर की शाखा की भविष्यवाणी अप्रभावी है, तो गुणा दूसरी सशर्त शाखा से तेज होने की संभावना है। जैसे-जैसे आप अनुपात बढ़ाते हैं ... शाखा की भविष्यवाणी कम प्रभावी होती जा रही है।

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

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

हालांकि, एक बात यह है कि इस अनुकूलन के साथ आपको सावधान रहने की आवश्यकता है। क्या कोई मान है जहाँ a * b != 0 गलत उत्तर देगा। उन मामलों पर विचार करें जहां कंप्यूटिंग के परिणाम से पूर्णांक ओवरफ़्लो होता है।

अद्यतन करें

आपके ग्राफ़ मैं क्या कहा पुष्टि करने के लिए करते हैं।

  • सशर्त शाखा में एक "शाखा भविष्यवाणी" प्रभाव भी है a * b != 0 मामला, और यह रेखांकन में सामने आता है।

  • यदि आप X- अक्ष पर 0.9 से अधिक वक्रों को प्रोजेक्ट करते हैं, तो यह 1 जैसा दिखता है) वे लगभग 1.0 और 2 पर मिलेंगे) मीटिंग बिंदु लगभग X = 0.0 के समान Y मान होगा।

अद्यतन २

मुझे समझ में नहीं आता है कि कर्व्स a + b != 0 लिए अलग क्यों हैं a + b != 0 और a | b != 0 a | b != 0 मामले। शाखा के भविष्यवक्ताओं के तर्क में कुछ चतुर हो सकता है। या यह कुछ और संकेत कर सकता है।

(ध्यान दें कि इस तरह की चीज एक विशेष चिप मॉडल नंबर या यहां तक ​​कि संस्करण के लिए विशिष्ट हो सकती है। आपके बेंचमार्क के परिणाम अन्य सिस्टम पर हो सकते हैं।)

हालांकि, उन दोनों को a और b सभी गैर-नकारात्मक मूल्यों के लिए काम करने का फायदा है।


यहाँ उत्तर अच्छे हैं, हालाँकि मुझे इस बात का अंदाज़ा था कि चीज़ों में सुधार हो सकता है।

चूँकि दो शाखाएँ और संबद्ध शाखा भविष्यवाणी संभावित अपराधी हैं, इसलिए हम तर्क को बदले बिना एक ही शाखा में शाखाकरण को कम करने में सक्षम हो सकते हैं।

bool aNotZero = (nums[0][i] != 0);
bool bNotZero = (nums[1][i] != 0);
if (aNotZero && bNotZero) { /* Some code */ }

यह करने के लिए भी काम कर सकते हैं

int a = nums[0][i];
int b = nums[1][i];
if (a != 0 && b != 0) { /* Some code */ }

कारण यह है कि शॉर्ट सर्किटिंग के नियमों के अनुसार, यदि पहला बूलियन गलत है, तो दूसरे का मूल्यांकन नहीं किया जाना चाहिए। यह nums[1][i] मूल्यांकन से बचने के लिए एक अतिरिक्त शाखा का प्रदर्शन करना है nums[1][i] यदि nums[0][i] गलत था। अब, आप इस बात की परवाह नहीं कर सकते हैं कि nums[1][i] मूल्यांकन किया जाता है, लेकिन संकलक निश्चित नहीं हो सकता है कि जब आप करते हैं तो यह सीमा या अशक्त रेफरी को बाहर नहीं nums[1][i] । यदि ब्लॉक को सरल बूल्स पर कम करके, कंपाइलर को यह महसूस करने के लिए पर्याप्त स्मार्ट हो सकता है कि दूसरे बूलियन का मूल्यांकन अनावश्यक रूप से नकारात्मक दुष्प्रभाव नहीं होगा।






branch-prediction