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




performance processing-efficiency (4)

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

    (a*b != 0)

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

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

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

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

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

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, मिश्रित मोड)


मुझे लगता है कि आपके बेंचमार्क में कुछ खामियां हैं और वास्तविक कार्यक्रमों के बारे में बताने के लिए यह उपयोगी नहीं हो सकता है। यहाँ मेरे विचार हैं:

  • (a+b)!=0 शून्य के योग में सकारात्मक और नकारात्मक मानों के लिए गलत काम करेगा, इसलिए आप इसे सामान्य मामले में उपयोग नहीं कर सकते, भले ही यह यहां काम करता हो।

  • इसी तरह, (a*b)!=0 उन मूल्यों के लिए गलत काम करेगा जो अतिप्रवाह करते हैं। (रैंडम उदाहरण: 196608 * 327680 0 है क्योंकि सही परिणाम 2 32 से विभाज्य होता है, इसलिए इसके कम 32 बिट्स 0 हैं, और ये बिट्स आप सभी को मिलेंगे यदि यह एक int ऑपरेशन है।)

  • (a|b)!=0 और (a+b)!=0 परीक्षण यदि कोई भी मूल्य गैर-शून्य है, जबकि a != 0 && b != 0 और (a*b)!=0 परीक्षण यदि दोनों गैर हैं। शून्य। तो आप केवल अंकगणित के समय की तुलना नहीं कर रहे हैं: यदि स्थिति अधिक बार सच है, तो यह शरीर के अधिक निष्पादन का कारण बनता है, जिसमें अधिक समय भी लगता है।

  • वीएम बाहरी ( fraction ) लूप के पहले कुछ रन के दौरान अभिव्यक्ति का अनुकूलन करेगा, जब fraction 0 होता है, जब शाखाएं लगभग कभी नहीं ली जाती हैं। यदि आप 0.5 पर fraction प्रारंभ करते हैं, तो अनुकूलक अलग-अलग कार्य कर सकता है।

  • जब तक वीएम यहां कुछ सरणी सीमाओं की जांच को समाप्त करने में सक्षम नहीं है, तब तक सीमा की जाँच के कारण अभिव्यक्ति में चार अन्य शाखाएं हैं, और यह एक जटिल कारक है जब यह पता लगाने की कोशिश की जा रही है कि निम्न स्तर पर क्या हो रहा है। आप भिन्न परिणाम प्राप्त कर सकते हैं यदि आप दो समतल सरणियों में दो आयामी सरणी को विभाजित करते हैं, तो nums[0][i] और nums[1][i] को nums0[i] और nums1[i]

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

  • शर्त पूरी होने के बाद निष्पादित होने वाला विशेष कोड स्थिति का मूल्यांकन करने के प्रदर्शन को प्रभावित कर सकता है, क्योंकि यह उन चीजों को प्रभावित करता है जैसे लूप को nums किया जा सकता है, जो कि सीपीयू रजिस्टर उपलब्ध हैं, और यदि किसी को प्राप्त nums की आवश्यकता है स्थिति का मूल्यांकन करने के बाद पुन: उपयोग किया जाना। बेंचमार्क में एक काउंटर बढ़ाना वास्तविक कोड क्या होगा के लिए एक सही प्लेसहोल्डर नहीं है।

  • System.currentTimeMillis() अधिकांश प्रणालियों पर +/- 10 एमएस से अधिक सटीक नहीं है। System.nanoTime() आमतौर पर अधिक सटीक होता है।

बहुत सारी अनिश्चितताएं हैं, और इन प्रकार के सूक्ष्म-अनुकूलन के साथ कुछ भी निश्चित रूप से कहना हमेशा कठिन होता है क्योंकि एक वीएम या सीपीयू पर तेजी से एक चाल दूसरे पर धीमी हो सकती है। यदि 64-बिट संस्करण के बजाय 32-बिट हॉटस्पॉट JVM चल रहा है, तो ध्यान रखें कि यह दो स्वादों में आता है: "क्लाइंट" वीएम के पास "सर्वर" वीएम की तुलना में अलग (कमजोर) अनुकूलन है।

यदि आप वीएम द्वारा उत्पन्न मशीन कोड को अलग कर सकते हैं, तो यह अनुमान लगाने की कोशिश करें कि यह क्या करता है!


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

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


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

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

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