java - जावा अपवाद कितनी धीमी है?




performance exception-handling (12)

प्रश्न: जावा में अपवाद हैंडलिंग वास्तव में धीमी है?

पारंपरिक ज्ञान, साथ ही साथ कई Google परिणाम, कहते हैं कि जावा में सामान्य प्रोग्राम प्रवाह के लिए असाधारण तर्क का उपयोग नहीं किया जाना चाहिए। आमतौर पर दो कारण दिए जाते हैं,

  1. यह वास्तव में धीमा - यहां तक ​​कि नियमित कोड की तुलना में धीमी गति का क्रम भी है (कारण अलग-अलग कारण हैं)

तथा

  1. इसकी गन्दा क्योंकि लोग असाधारण कोड में केवल त्रुटियों को संभालने की अपेक्षा करते हैं।

यह सवाल लगभग # 1 है।

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

मेरा परीक्षण (32 बिट लिनक्स पर जावा 1.6.0_07, जावा हॉटस्पॉट 10.0 का उपयोग करके) इंगित करता है कि अपवाद हैंडलिंग नियमित कोड से धीमी नहीं है। मैंने एक लूप में एक विधि चलाने की कोशिश की जो कुछ कोड निष्पादित करता है। विधि के अंत में, मैं एक बूलियन का उपयोग यह इंगित करने के लिए करता हूं कि वापस लौटना या फेंकना है या नहीं। इस तरह वास्तविक प्रसंस्करण एक ही है। मैंने अलग-अलग ऑर्डर में विधियों को चलाने की कोशिश की और मेरे परीक्षण के समय औसत किया, यह सोचकर कि यह जेवीएम वार्मिंग हो सकता है। मेरे सभी परीक्षणों में, फेंक कम से कम जितना तेज़ था, अगर तेज़ नहीं होता (3.1% तक तेज)। मैं इस संभावना के लिए पूरी तरह से खुला हूं कि मेरे परीक्षण गलत थे, लेकिन मैंने कोड नमूना, परीक्षण तुलना, या पिछले वर्ष या दो परिणामों में परिणाम देखा है जो वास्तव में धीमे होने के लिए जावा में अपवाद हैंडलिंग दिखाते हैं ।

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

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


Aleksey Shipilëv ने एक बहुत ही संपूर्ण विश्लेषण किया जिसमें उन्होंने शर्तों के विभिन्न संयोजनों के तहत जावा अपवादों को बेंचमार्क किया:

  • पूर्व निर्मित अपवाद बनाम नए बनाए गए अपवाद
  • स्टैक ट्रेस सक्षम बनाम अक्षम बनाम
  • स्टैक ट्रेस अनुरोध किया गया है कि अनुरोध कभी नहीं किया गया
  • हर स्तर पर बनाम / लपेटे गए प्रत्येक स्तर पर शीर्ष स्तर बनाम पुनर्निर्मित पर पकड़ा गया
  • जावा कॉल स्टैक गहराई के विभिन्न स्तर
  • डिफ़ॉल्ट सेटिंग्स बनाम चरम इनलाइनिंग बनाम कोई इनलाइनिंग अनुकूलन नहीं
  • उपयोगकर्ता परिभाषित फ़ील्ड पढ़ा नहीं पढ़ा बनाम

वह उन्हें त्रुटि आवृत्ति के विभिन्न स्तरों पर एक त्रुटि कोड की जांच के प्रदर्शन की तुलना में भी तुलना करता है।

निष्कर्ष (उनके पद से उद्धृत वर्बेटिम) थे:

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

  2. अपवादों की प्रदर्शन लागत में दो प्रमुख घटक होते हैं: अपवाद तत्काल होने पर स्टैक ट्रेस निर्माण , और अपवाद फेंकने के दौरान अवांछित ढेर

  3. स्टैक ट्रेस निर्माण लागत अपवाद तत्काल के समय गहराई से ढेर करने के लिए आनुपातिक हैं । यह पहले से ही बुरा है, क्योंकि पृथ्वी पर कौन सा ढेर गहराई जानता है जिस पर यह फेंकने की विधि कहा जाएगा? भले ही आप स्टैक ट्रेस पीढ़ी को बंद कर दें और / या अपवादों को कैश करें, आप केवल प्रदर्शन लागत के इस हिस्से से छुटकारा पा सकते हैं।

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

  5. क्या हमें दोनों प्रभावों को खत्म करना चाहिए, अपवादों की प्रदर्शन लागत स्थानीय शाखा का है। इससे कोई फर्क नहीं पड़ता कि यह कितना सुंदर लगता है, इसका मतलब यह नहीं है कि आपको अपवादों को सामान्य नियंत्रण प्रवाह के रूप में उपयोग करना चाहिए, क्योंकि उस स्थिति में आप संकलक को अनुकूलित करने की करुणा पर हैं! आपको केवल उन्हें असाधारण मामलों में उपयोग करना चाहिए, जहां अपवाद आवृत्ति वास्तविक अपवाद को बढ़ाने की संभावित दुर्भाग्यपूर्ण लागत को कम करती है।

  6. आशावादी नियम-का-अंगूठे अपवादों के लिए 10 ^ -4 आवृत्ति असाधारण है। यह निश्चित रूप से अपवादों की भारी भार, अपवाद हैंडलर आदि में किए गए सटीक कार्यों पर निर्भर करता है।

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


एफवाईआई, मैंने उस प्रयोग को बढ़ाया जो मेकी ने किया था:

method1 took 1733 ms, result was 2
method2 took 1248 ms, result was 2
method3 took 83997 ms, result was 2
method4 took 1692 ms, result was 2
method5 took 60946 ms, result was 2
method6 took 25746 ms, result was 2

पहला 3 मेकी के समान है (मेरा लैपटॉप स्पष्ट रूप से धीमा है)।

method4 विधि 3 के समान है सिवाय इसके कि यह throw new Exception() बजाए एक new Integer(1) बनाता है।

method5 विधि 3 की तरह है सिवाय इसके कि यह इसे बिना फेंकने के new Exception() बनाता है।

method6 विधि 3 की तरह है सिवाय इसके कि यह एक नया निर्माण करने के बजाय पूर्व-निर्मित अपवाद (एक उदाहरण चर) फेंकता है।

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


जावा और सी # में अपवाद प्रदर्शन वांछित होने के लिए बहुत अधिक छोड़ देता है।

प्रोग्रामर के रूप में यह हमें व्यावहारिक प्रदर्शन कारणों के लिए नियमों के अनुसार "अपवादों को निरंतर" होना चाहिए।

हालांकि, कंप्यूटर वैज्ञानिकों के रूप में, हमें इस समस्याग्रस्त स्थिति के खिलाफ विद्रोह करना चाहिए। एक समारोह को संलेखित करने वाले व्यक्ति को अक्सर यह नहीं पता कि इसे कितनी बार बुलाया जाएगा, या सफलता या विफलता की संभावना अधिक है या नहीं। केवल कॉलर में यह जानकारी है। Trying to avoid exceptions leads to unclear API idoms where in some cases we have only clean-but-slow exception versions, and in other cases we have fast-but-clunky return-value errors, and in still other cases we end up with both. The library implementor may have to write and maintain two versions of APIs, and the caller has to decide which of two versions to use in each situation.

This is kind of a mess. If exceptions had better performance, we could avoid these clunky idioms and use exceptions as they were meant to be used... as a structured error return facility.

I'd really like to see exception mechanisms implemented using techniques closer to return-values, so we could have performance closer to return values.. since this is what we revert to in performance sensitive code.

Here is a code-sample that compares exception performance to error-return-value performance.

public class TestIt {

int value;


public int getValue() {
    return value;
}

public void reset() {
    value = 0;
}

public boolean baseline_null(boolean shouldfail, int recurse_depth) {
    if (recurse_depth <= 0) {
        return shouldfail;
    } else {
        return baseline_null(shouldfail,recurse_depth-1);
    }
}

public boolean retval_error(boolean shouldfail, int recurse_depth) {
    if (recurse_depth <= 0) {
        if (shouldfail) {
            return false;
        } else {
            return true;
        }
    } else {
        boolean nested_error = retval_error(shouldfail,recurse_depth-1);
        if (nested_error) {
            return true;
        } else {
            return false;
        }
    }
}

public void exception_error(boolean shouldfail, int recurse_depth) throws Exception {
    if (recurse_depth <= 0) {
        if (shouldfail) {
            throw new Exception();
        }
    } else {
        exception_error(shouldfail,recurse_depth-1);
    }

}

public static void main(String[] args) {
    int i;
    long l;
    TestIt t = new TestIt();
    int failures;

    int ITERATION_COUNT = 100000000;


    // (0) baseline null workload
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                t.baseline_null(shoulderror,recurse_depth);
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("baseline: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);
        }
    }


    // (1) retval_error
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                if (!t.retval_error(shoulderror,recurse_depth)) {
                    failures++;
                }
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("retval_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);
        }
    }

    // (2) exception_error
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                try {
                    t.exception_error(shoulderror,recurse_depth);
                } catch (Exception e) {
                    failures++;
                }
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("exception_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);              
        }
    }
}

}

And here are the results:

baseline: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 683 ms
baseline: recurse_depth 2, exception_freqeuncy 0.25 (0), time elapsed 790 ms
baseline: recurse_depth 2, exception_freqeuncy 0.5 (0), time elapsed 768 ms
baseline: recurse_depth 2, exception_freqeuncy 0.75 (0), time elapsed 749 ms
baseline: recurse_depth 2, exception_freqeuncy 1.0 (0), time elapsed 731 ms
baseline: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 923 ms
baseline: recurse_depth 5, exception_freqeuncy 0.25 (0), time elapsed 971 ms
baseline: recurse_depth 5, exception_freqeuncy 0.5 (0), time elapsed 982 ms
baseline: recurse_depth 5, exception_freqeuncy 0.75 (0), time elapsed 947 ms
baseline: recurse_depth 5, exception_freqeuncy 1.0 (0), time elapsed 937 ms
baseline: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1154 ms
baseline: recurse_depth 8, exception_freqeuncy 0.25 (0), time elapsed 1149 ms
baseline: recurse_depth 8, exception_freqeuncy 0.5 (0), time elapsed 1133 ms
baseline: recurse_depth 8, exception_freqeuncy 0.75 (0), time elapsed 1117 ms
baseline: recurse_depth 8, exception_freqeuncy 1.0 (0), time elapsed 1116 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 742 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 743 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 734 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 723 ms
retval_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 728 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 920 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 1121   ms
retval_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 1037 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 1141   ms
retval_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 1130 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1218 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 1334  ms
retval_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 1478 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 1637 ms
retval_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 1655 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 726 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 17487   ms
exception_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 33763   ms
exception_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 67367   ms
exception_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 66990 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 924 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 23775  ms
exception_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 46326 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 91707 ms
exception_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 91580 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1144 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 30440 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 59116   ms
exception_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 116678 ms
exception_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 116477 ms

Checking and propagating return-values does add some cost vs the baseline-null call, and that cost is proportional to call-depth. At a call-chain depth of 8, the error-return-value checking version was about 27% slower than the basline version which did not check return values.

Exception performance, in comparison, is not a function of call-depth, but of exception frequency. However, the degredation as exception frequency increases is much more dramatic. At only a 25% error frequency, the code ran 24-TIMES slower. At an error frequency of 100%, the exception version is almost 100-TIMES slower.

This suggests to me that perhaps are making the wrong tradeoffs in our exception implementations. Exceptions could be faster, either by avoiding costly stalk-walks, or by outright turning them into compiler supported return-value checking. Until they do, we're stuck avoiding them when we want our code to run fast.


दुर्भाग्य से मेरा जवाब यहां पोस्ट करने के लिए बहुत लंबा है। तो मुझे यहां सारांशित करने दें और आपको किरदार विवरण के लिए http://www.fuwjax.com/how-slow-are-java-exceptions/ देखें।

यहां वास्तविक सवाल यह नहीं है कि 'कोड कभी विफल नहीं होता' की तुलना में अपवादों के रूप में "कितनी धीमी गति से रिपोर्ट की गई है?" क्योंकि स्वीकार्य प्रतिक्रिया आपको विश्वास हो सकती है। इसके बजाय प्रश्न विफलताओं की तुलना में अपवादों के रूप में रिपोर्ट की गई धीमी गति से कितनी धीमी गति से होनी चाहिए? " आम तौर पर विफलताओं की रिपोर्टिंग के दो अन्य तरीके या तो सेंटीनल मान या परिणाम रैपर के साथ होते हैं।

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

यह पता चला है कि प्रकार की सुरक्षा के जोखिम पर, सेंटीनेल मूल्य अपवादों से तेज़ होते हैं, लेकिन केवल 2x के कारक से। अब, यह बहुत कुछ प्रतीत हो सकता है, लेकिन 2x केवल कार्यान्वयन अंतर की लागत को कवर करता है। व्यावहारिक रूप से कारक बहुत कम है क्योंकि असफल हो सकता है कि इस पृष्ठ में कहीं और नमूना कोड में कुछ अंकगणितीय ऑपरेटरों की तुलना में असफल हो सकता है।

दूसरी ओर परिणाम लपेटें टाइप सुरक्षा को बलिदान नहीं देते हैं। वे एक वर्ग में सफलता और विफलता की जानकारी लपेटते हैं। तो "उदाहरण" के बजाय वे "isSuccess ()" और सफलता और विफलता दोनों वस्तुओं के लिए गेटर्स प्रदान करते हैं। हालांकि, परिणाम ऑब्जेक्ट अपवादों का उपयोग करने से लगभग 2x धीमी हैं। यह पता चला है कि हर बार एक नया रैपर ऑब्जेक्ट बनाना कभी-कभी अपवाद फेंकने से कहीं अधिक महंगा होता है।

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

अपवादों की तुलना में अपवाद, सुरक्षित वस्तुओं की तुलना में तेज़, और इससे भी कम आश्चर्यजनक हैं। मैं सुझाव नहीं दे रहा हूं कि अगर / अन्य जगहों को प्रतिस्थापित करें / पकड़ें, लेकिन व्यापार तर्क में भी विफलता की रिपोर्ट करने का अपवाद सही तरीका है।

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


भले ही अपवाद फेंकना धीमा न हो, फिर भी सामान्य कार्यक्रम प्रवाह के लिए अपवाद फेंकना अभी भी एक बुरा विचार है। इस तरह से इस्तेमाल किया गया यह एक गोटो के समान है ...

मुझे लगता है कि वास्तव में सवाल का जवाब नहीं देता है। मैं कल्पना करता हूं कि अपवादों को धीमा करने का 'परंपरागत' ज्ञान पहले जावा संस्करणों (<1.4) में सच था। अपवाद बनाना आवश्यक है कि वीएम पूरे स्टैक ट्रेस बनाएं। तब से वीएम में चीजों को गति देने के लिए बहुत कुछ बदल गया है और यह संभवतः एक ऐसा क्षेत्र है जो सुधार हुआ है।


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

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

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

शायद आप अपना टेस्ट कोड पोस्ट कर सकते हैं?


मैंने जावा के लिए स्टैकट्रैक के बिना @Mecki और @incarnate द्वारा दिए गए उत्तरों को विस्तारित किया है।

जावा 7+ के साथ, हम Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace) उपयोग कर सकते हैं। लेकिन जावा 6 के लिए, इस प्रश्न के लिए मेरा जवाब देखें

// This one will regularly throw one
public void method4(int i) throws NoStackTraceThrowable {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceThrowable();
    }
}

// This one will regularly throw one
public void method5(int i) throws NoStackTraceRuntimeException {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceRuntimeException();
    }
}

public static void main(String[] args) {
    int i;
    long l;
    Test t = new Test();

    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method4(i);
        } catch (NoStackTraceThrowable e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method4 took " + l + " ms, result was " + t.getValue() );


    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method5(i);
        } catch (RuntimeException e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method5 took " + l + " ms, result was " + t.getValue() );
}

कोर i7, 8 जीबी राम पर जावा 1.6.0_45 के साथ आउटपुट:

method1 took 883 ms, result was 2
method2 took 882 ms, result was 2
method3 took 32270 ms, result was 2 // throws Exception
method4 took 8114 ms, result was 2 // throws NoStackTraceThrowable
method5 took 8086 ms, result was 2 // throws NoStackTraceRuntimeException

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

संपादित करें: NoStackTraceThrowable.java धन्यवाद @ ग्रेग

public class NoStackTraceThrowable extends Throwable { 
    public NoStackTraceThrowable() { 
        super("my special throwable", null, false, false);
    }
}

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

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

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

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

निम्नलिखित टेस्ट कोड देखें:

public class Test {
    int value;


    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            System.out.println("You'll never see this!");
        }
    }

    // Could in theory throw one, but never will
    public void method2(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            throw new Exception();
        }
    }

    // This one will regularly throw one
    public void method3(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
        // an AND operation between two integers. The size of the number plays
        // no role. AND on 32 BIT always ANDs all 32 bits
        if ((i & 0x1) == 1) {
            throw new Exception();
        }
    }

    public static void main(String[] args) {
        int i;
        long l;
        Test t = new Test();

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            t.method1(i);
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method1 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method2(i);
            } catch (Exception e) {
                System.out.println("You'll never see this!");
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method2 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method3(i);
            } catch (Exception e) {
                // Do nothing here, as we will get here
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method3 took " + l + " ms, result was " + t.getValue()
        );
    }
}

परिणाम:

method1 took 972 ms, result was 2
method2 took 1003 ms, result was 2
method3 took 66716 ms, result was 2

कोशिश प्रक्रिया से मंदी पृष्ठभूमि प्रक्रियाओं जैसे उलझन कारकों को रद्द करने के लिए बहुत छोटी है। लेकिन कैच ब्लॉक ने सब कुछ मारा और इसे 66 गुना धीमा कर दिया!

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


Great post about exception performance is:

https://shipilev.net/blog/2014/exceptional-performance/

Instantiating vs reusing existing, with stack trace and without, etc:

Benchmark                            Mode   Samples         Mean   Mean error  Units

dynamicException                     avgt        25     1901.196       14.572  ns/op
dynamicException_NoStack             avgt        25       67.029        0.212  ns/op
dynamicException_NoStack_UsedData    avgt        25       68.952        0.441  ns/op
dynamicException_NoStack_UsedStack   avgt        25      137.329        1.039  ns/op
dynamicException_UsedData            avgt        25     1900.770        9.359  ns/op
dynamicException_UsedStack           avgt        25    20033.658      118.600  ns/op

plain                                avgt        25        1.259        0.002  ns/op
staticException                      avgt        25        1.510        0.001  ns/op
staticException_NoStack              avgt        25        1.514        0.003  ns/op
staticException_NoStack_UsedData     avgt        25        4.185        0.015  ns/op
staticException_NoStack_UsedStack    avgt        25       19.110        0.051  ns/op
staticException_UsedData             avgt        25        4.159        0.007  ns/op
staticException_UsedStack            avgt        25       25.144        0.186  ns/op

Depending on depth of stack trace:

Benchmark        Mode   Samples         Mean   Mean error  Units

exception_0000   avgt        25     1959.068       30.783  ns/op
exception_0001   avgt        25     1945.958       12.104  ns/op
exception_0002   avgt        25     2063.575       47.708  ns/op
exception_0004   avgt        25     2211.882       29.417  ns/op
exception_0008   avgt        25     2472.729       57.336  ns/op
exception_0016   avgt        25     2950.847       29.863  ns/op
exception_0032   avgt        25     4416.548       50.340  ns/op
exception_0064   avgt        25     6845.140       40.114  ns/op
exception_0128   avgt        25    11774.758       54.299  ns/op
exception_0256   avgt        25    21617.526      101.379  ns/op
exception_0512   avgt        25    42780.434      144.594  ns/op
exception_1024   avgt        25    82839.358      291.434  ns/op

For other details (including x64 assembler from JIT) read original blog post.

That mean Hibernate/Spring/etc-EE-shit are slow because of exceptions (xD) and rewriting app control flow away from exceptions (replace it with continure / break and returning boolean flags like in C from method call) improve performance of your application 10x-100x, depending on how often you throws them ))


I changed @Mecki 's answer above to have method1 return a boolean and a check in the calling method, as you cannot just replace an Exception with nothing. After two runs, method1 was still either the fastest or as fast as method2.

Here is snapshot of the code:

// Calculates without exception
public boolean method1(int i) {
    value = ((value + i) / i) << 1;
    // Will never be true
    return ((i & 0xFFFFFFF) == 1000000000);

}
....
   for (i = 1; i < 100000000; i++) {
            if (t.method1(i)) {
                System.out.println("Will never be true!");
            }
    }

and results:

Run 1

method1 took 841 ms, result was 2
method2 took 841 ms, result was 2
method3 took 85058 ms, result was 2

Run 2

method1 took 821 ms, result was 2
method2 took 838 ms, result was 2
method3 took 85929 ms, result was 2

My opinion about Exception speed versus checking data programmatically.

Many classes had String to value converter (scanner / parser), respected and well-known libraries too ;)

usually has form

class Example {
public static Example Parse(String input) throws AnyRuntimeParsigException
...
}

exception name is only example, usually is unchecked (runtime), so throws declaration is only my picture

sometimes exist second form:

public static Example Parse(String input, Example defaultValue)

never throwing

When the second ins't available (or programmer read too less docs and use only first), write such code with regular expression. Regular expression are cool, politically correct etc:

Xxxxx.regex(".....pattern", src);
if(ImTotallySure)
{
  Example v = Example.Parse(src);
}

with this code programmers hasn't cost of exceptions. BUT HAS comparable very HIGH cost of regular expressions ALWAYS versus small cost of exception sometimes.

I use almost always in such context

try { parse } catch(ParsingException ) // concrete exception from javadoc
{
}

without analysing stacktrace etc, I believe after lectures of Yours quite speed.

Do not be afraid Exceptions


Why should exceptions be any slower than normal returns?

As long as you don't print the stacktrace to the terminal, save it into a file or something similar, the catch-block doesn't do any more work than other code-blocks. So, I can't imagine why "throw new my_cool_error()" should be that slow.

Good question and I'm looking forward to further information on this topic!





exception-handling