जावा में प्रदर्शन पर अपवाद के प्रभाव क्या हैं?


496

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

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

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

तथा

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

यह प्रश्न # 1 के बारे में है।

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

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

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

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


8
मुझे पता है कि आप 2 के बारे में नहीं पूछ रहे थे), लेकिन आपको वास्तव में पहचानना चाहिए कि प्रोग्राम फ्लो के लिए अपवाद का उपयोग करना GOTO का उपयोग करने से बेहतर नहीं है। कुछ लोग गोटो का बचाव करते हैं, कुछ लोग आपका बचाव करते हैं जो आप के बारे में बात कर रहे हैं, लेकिन यदि आप किसी ऐसे व्यक्ति से पूछते हैं जिसने लागू किया है और समय की अवधि के लिए बनाए रखा है, तो वे आपको बताएंगे कि दोनों डिजाइन प्रथाओं को बनाए रखने के लिए बहुत कठिन हैं (और शायद अभिशाप होगा) उस व्यक्ति का नाम जिसने सोचा था कि वे स्मार्ट थे ताकि उन्हें इस्तेमाल करने का फैसला किया जा सके)।
बिल के

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

14
@ सामान्य नियंत्रण प्रवाह के लिए अपवाद का उपयोग करना जावा में एक बुरा विचार है क्योंकि इस तरह से प्रतिमान पसंद किया गया था । बलोच EJ2 पढ़ें - वह स्पष्ट रूप से बताता है कि, उद्धरण, (आइटम 57) exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow- क्यों के रूप में पूर्ण और व्यापक विवरण दे रहा है। और वह वह लड़का था जिसने Java lib लिखा था । इसलिए, वह वर्गों के एपीआई अनुबंध को परिभाषित करने वाला है। / इस पर बिल K सहमत।

8
@ Ondra (ižka यदि कुछ ढाँचा ऐसा करता है (गैर-असाधारण स्थिति में अपवाद का उपयोग करें), तो यह त्रुटिपूर्ण है और डिज़ाइन द्वारा तोड़ दिया जाता है, भाषा के अपवाद वर्ग अनुबंध को तोड़ देता है। सिर्फ इसलिए कि कुछ लोग घटिया कोड लिखते हैं, वह इसे घटिया नहीं बनाता है।

8
Stackoverflow.com के निर्माता के अलावा कोई भी अपवाद के बारे में गलत नहीं है। सॉफ्टवेयर विकास का सुनहरा नियम कभी भी सरल और जटिल नहीं है। वह लिखते हैं: "यह सच है कि एक साधारण 3 लाइन प्रोग्राम क्या होना चाहिए जब आप अच्छी त्रुटि जाँच में डालते हैं तो अक्सर 48 रेखाएँ खिलती हैं, लेकिन यह जीवन है, ..." यह पवित्रता की खोज है, सरलता नहीं।
sf_jeff

जवाबों:


345

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

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

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

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

निम्नलिखित परीक्षण कोड देखें:

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) के भीतर ट्राय / कैच और थ्रो करते हैं तो परिणाम उतना बुरा नहीं होगा, लेकिन यह एक विशेष जेआईटी ऑप्टिमाइज़ेशन है जिस पर मैं भरोसा नहीं करता। और इस अनुकूलन का उपयोग करते समय, थ्रो अभी भी बहुत धीमा है। इसलिए मुझे नहीं पता कि आप यहां क्या करने की कोशिश कर रहे हैं, लेकिन निश्चित रूप से कोशिश / पकड़ने / फेंकने का उपयोग करने से बेहतर तरीका है।


7
शानदार उत्तर लेकिन मैं सिर्फ इतना कहना चाहूंगा कि जहां तक ​​मुझे पता है, System.nanoTime () को प्रदर्शन मापने के लिए इस्तेमाल किया जाना चाहिए, न कि System.currentTimeMillis ()।
साइमन फोर्सबर्ग

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

2
यह वास्तव में ध्यान दिया जाना चाहिए कि आपका परीक्षण एक चरम मामला है। आप एक tryब्लॉक के साथ कोड के लिए बहुत छोटा प्रदर्शन दिखाते हैं , लेकिन नहीं throw। आपका throwपरीक्षण उस समय के 50% अपवादों को फेंक रहा है जो इससे गुजरता है try। यह स्पष्ट रूप से ऐसी स्थिति है जहां विफलता असाधारण नहीं है । केवल 10% तक कटौती करने से प्रदर्शन में गिरावट आती है। इस तरह के परीक्षण के साथ समस्या यह है कि यह लोगों को अपवादों का उपयोग पूरी तरह से रोकने के लिए प्रोत्साहित करता है। असाधारण मामले से निपटने के लिए अपवादों का उपयोग करना, आपके परीक्षण के प्रदर्शन से काफी बेहतर प्रदर्शन करता है।
नैट

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

4
@ ग्लाइड ए थ्रो क्लीन की तरह नहीं है return। यह शरीर के बीच में कहीं एक विधि छोड़ता है, शायद एक ऑपरेशन के बीच में भी (जो कि अभी तक केवल 50% द्वारा पूरा किया गया है) और catchब्लॉक 20 स्टैक फ्रेम ऊपर की ओर हो सकता है (एक विधि में एक tryब्लॉक, कॉलिंग मेथड 1) , जो मेथोड 3 कहता है, जो मेहतोड 3 कहता है, ... और मेथड 20 में ऑपरेशन के बीच में एक अपवाद को फेंक दिया जाता है)। स्टैक को 20 फ्रेम ऊपर की तरफ खोलना चाहिए, सभी अधूरे ऑपरेशनों को पूर्ववत करना चाहिए (संचालन आधा नहीं होना चाहिए) और सीपीयू रजिस्टरों को एक साफ स्थिति में होना चाहिए। यह सब समय लगता है।
मकी

255

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

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 के समान है सिवाय इसके कि यह करने के new Integer(1)बजाय बनाता है throw new Exception()

मेथड 5 मेथड 3 की तरह है सिवाय इसके कि यह new Exception()बिना फेक के बनाता है।

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

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


48
+1 आपका जवाब मुख्य मुद्दे को संबोधित करता है - स्टैक को खोलना और ट्रेस करने के लिए लिया गया समय, और दूसरी बार त्रुटि को फेंकना। मैंने इसे अंतिम उत्तर के रूप में चुना होगा।
इंजीनियर

8
अच्छा। ~ 70% अपवाद बनाते हुए, ~ 30% इसे फेंक देते हैं। अच्छी जानकारी।
प्रातः

1
@ बासील - आपको यह पता लगाने में सक्षम होना चाहिए कि उपरोक्त संख्याओं से।
हॉट लिक्स

1
यह कार्यान्वयन विशिष्ट हो सकता है। इन बेंचमार्क के लिए जावा के किस संस्करण का उपयोग किया गया था?
थोरबजोरन रेव एंडरसन

3
हम यह कह सकते हैं कि मानक कोड में, अपवादों को बनाना और फेंकना दुर्लभ मामलों में होता है (रनटाइम I का मतलब है), यदि यह मामला नहीं है, या तो रनटाइम की स्थिति बहुत खराब है, या डिज़ाइन स्वयं समस्या है; दोनों मामलों में प्रदर्शन एक चिंता का विषय नहीं है ...
जीन-बैप्टिस्ट यूंसे

70

अलेक्सी शिपिलव ने बहुत गहन विश्लेषण किया, जिसमें उन्होंने शर्तों के विभिन्न संयोजनों के तहत जावा अपवादों को बेंचमार्क किया:

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

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

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

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

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

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

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

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

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

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


41

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

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

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

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

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

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

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

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


5
मैंने एक नियंत्रण कार्यान्वयन की तुलना में तीन कार्यान्वयन के दीर्घकालिक प्रदर्शन का परीक्षण करने का निर्णय लिया जो बिना रिपोर्टिंग के विफलता की जांच करता है। प्रक्रिया में लगभग 4% की विफलता दर है। परीक्षण का एक पुनरावृत्ति एक रणनीति के खिलाफ प्रक्रिया को 10000 बार आमंत्रित करता है। प्रत्येक रणनीति का 1000 बार परीक्षण किया जाता है और अंतिम 900 बार आँकड़े बनाने के लिए उपयोग किया जाता है। यहाँ नैनो में औसत समय है: नियंत्रण 338 अपवाद 429 परिणाम 348 प्रहरी 345
फुवजैक्स

2
बस मज़े के लिए मैं अपवाद परीक्षण में fillInStackTrace को अक्षम कर दिया। यहाँ अब समय है: नियंत्रण 347 अपवाद 351 परिणाम 364 प्रहरी 355
फ्यूजजैक्स

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

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

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

20

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

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

// 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() );
}

कोर आई 7, 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

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

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

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

दिलचस्प है, धन्यवाद। यहाँ लापता वर्ग घोषणा है:public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } }
ग्रेग

beginging पर आपने लिखा था With Java 7+, we can useलेकिन बाद में आपने लिखा Output with Java 1.6.0_45,कि यह Java 6 या 7 परिणाम है?
WBAR

1
जावा 7 से @BAR, हमें बस उस Throwableनिर्माता का उपयोग करने की आवश्यकता है जो boolean writableStackTracearg है। लेकिन यह जावा 6 और नीचे में मौजूद नहीं है। यही कारण है कि मैंने जावा 6 और उससे नीचे के लिए कस्टम कार्यान्वयन दिया है। तो उपरोक्त कोड जावा 6 और नीचे के लिए है। कृपया 2 पैरा की 1 पंक्ति को ध्यान से पढ़ें।
मणिकांत

@manikanta "IMHO, हम केवल सफलता और त्रुटि दोनों के प्रवाह के लिए रिटर्न प्रकारों का उपयोग करके एक स्पष्ट एपीआई डिज़ाइन नहीं कर सकते हैं।"
हज्जाज़मैन

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

8

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

दस हज़ार स्ट्रिंग्स की सूची के लिए, यदि वे सभी वैध संख्याएँ हैं parseInt () दृष्टिकोण रेगेक्स दृष्टिकोण के रूप में चार गुना तेज़ था। लेकिन अगर केवल 80% तार वैध थे, तो रेगेक्स पार्सइंट () के मुकाबले दोगुना था। और अगर 20% वैध थे, तो इसका मतलब यह था कि अपवाद को फेंक दिया गया था और 80% समय पकड़ा गया था, रेगीक्स पार्सइंट () के रूप में लगभग बीस गुना तेज था।

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


आपने किस JVM का उपयोग किया? क्या यह अभी भी सूरज-जेड 6 के साथ धीमा है?
बेनेडिकट वाल्डवोगेल

मैंने इसे खोदा और उस उत्तर को जमा करने से पहले इसे फिर से JDK 1.6u10 के तहत चलाया, और वे परिणाम जो मैंने पोस्ट किए हैं।
एलन मूर

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

8

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

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

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

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


7

पता नहीं है कि ये विषय संबंधित हैं, लेकिन मैं एक बार वर्तमान थ्रेड के स्टैक ट्रेस पर निर्भर एक ट्रिक को लागू करना चाहता था: मैं उस विधि के नाम की खोज करना चाहता था, जिसने तात्कालिक वर्ग के अंदर तात्कालिकता को ट्रिगर किया (हां, विचार पागल है, मैंने पूरी तरह से इसे छोड़ दिया)। तो मुझे पता चला कि फोन करने Thread.currentThread().getStackTrace()है अत्यंत (देशी की वजह से धीमी गति से dumpThreadsविधि है जो वह आंतरिक रूप से उपयोग करता है)।

तो Throwable, जावा , इसी तरह, एक देशी विधि है fillInStackTrace। मुझे लगता है कि हत्यारा- catchब्लॉक पहले वर्णित किसी भी तरह से इस पद्धति के निष्पादन को ट्रिगर करता है।

लेकिन आपको एक और कहानी बताऊं ...

स्काला में कुछ कार्यात्मक विशेषताओं को जेवीएम के उपयोग से संकलित किया गया है ControlThrowable, जो निम्नलिखित तरीके से Throwableइसका विस्तार और अधिरोहण करती fillInStackTraceहै:

override def fillInStackTrace(): Throwable = this

इसलिए मैंने ऊपर परीक्षण को अनुकूलित किया (साइकिल की राशि दस से कम हो गई है, मेरी मशीन थोड़ी धीमी है :)

class ControlException extends ControlThrowable

class T {
  var value = 0

  def reset = {
    value = 0
  }

  def method1(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      println("You'll never see this!")
    }
  }

  def method2(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      throw new Exception()
    }
  }

  def method3(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new Exception()
    }
  }

  def method4(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new ControlException()
    }
  }
}

class Main {
  var l = System.currentTimeMillis
  val t = new T
  for (i <- 1 to 10000000)
    t.method1(i)
  l = System.currentTimeMillis - l
  println("method1 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method2(i)
  } catch {
    case _ => println("You'll never see this")
  }
  l = System.currentTimeMillis - l
  println("method2 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method4(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method4 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method3(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method3 took " + l + " ms, result was " + t.value)

}

तो, परिणाम हैं:

method1 took 146 ms, result was 2
method2 took 159 ms, result was 2
method4 took 1551 ms, result was 2
method3 took 42492 ms, result was 2

तुम देखो, केवल बीच का अंतर method3और method4है कि वे अपवाद के विभिन्न प्रकार फेंक है। हाँ, method4अभी भी की तुलना में धीमी है, method1और method2अंतर कहीं अधिक स्वीकार्य है।


6

मैंने JVM 1.5 के साथ कुछ प्रदर्शन परीक्षण किए हैं और अपवादों का उपयोग करके कम से कम 2x धीमी थी। औसतन: अपवादों के साथ तीन गुना (3x) से अधिक तुच्छ रूप से छोटी विधि पर निष्पादन का समय। तुच्छ रूप से छोटे पाश को अपवाद को पकड़ने के लिए स्व-समय में 2x वृद्धि देखी गई।

मैंने उत्पादन कोड और माइक्रो बेंचमार्क में समान संख्या देखी है।

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

उदाहरण के लिए, बहुत बड़े पाठ फ़ाइल में सभी बुरे मूल्यों को खोजने के लिए "Integer.ParseInt (...)" का उपयोग करना - बहुत बुरा विचार। (मैंने इस उपयोगिता विधि को उत्पादन कोड पर प्रदर्शन को मारते देखा है )

उपयोगकर्ता GUI फॉर्म पर खराब मान रिपोर्ट करने के लिए एक अपवाद का उपयोग करना, शायद प्रदर्शन के दृष्टिकोण से इतना बुरा नहीं है।

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


मुझे कुछ ऐसी चीजें सुझाने दें, जो करने में अच्छी हैं: यदि आपको Integer.valueOf (स्ट्रिंग) का उपयोग करने के बजाय एक फॉर्म में नंबर की आवश्यकता है, तो आपको इसके बजाय एक नियमित अभिव्यक्ति मिलानकर्ता का उपयोग करने पर विचार करना चाहिए। आप precompile कर सकते हैं और पैटर्न का पुन: उपयोग कर सकते हैं जिससे मैचर्स सस्ते होते हैं। हालांकि, GUI फॉर्म में, isValid / validate / checkField या आपके पास जो कुछ भी है, वह संभवतः अधिक स्पष्ट है। इसके अलावा, जावा 8 के साथ हमारे पास वैकल्पिक मोनड हैं, इसलिए उनका उपयोग करने पर विचार करें। (जवाब 9 साल पुराना है, लेकिन अभी भी !: पी)
हैकॉन लॉविटविट

4

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

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

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

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

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

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

पब्लिक क्लास टेस्ट {

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);              
        }
    }
}

}

और यहाँ परिणाम हैं:

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

रिटर्न-वैल्यूज़ की जाँच और प्रचार करना, बेसलाइन-नल कॉल के विरुद्ध कुछ लागत जोड़ देता है, और यह लागत कॉल-डेप्थ के समानुपाती होती है। 8 की कॉल-चेन डेप्थ पर, बेसलाइन संस्करण की तुलना में एरर-रिटर्न-वैल्यू चेकिंग संस्करण लगभग 27% धीमा था, जिसने रिटर्न वैल्यूज़ की जाँच नहीं की।

अपवाद प्रदर्शन, तुलना में, कॉल-डेप्थ का कार्य नहीं है, बल्कि अपवाद आवृत्ति का है। हालांकि, अपवाद आवृत्ति बढ़ जाती है के रूप में degredation बहुत अधिक नाटकीय है। केवल 25% त्रुटि आवृत्ति पर, कोड 24-TIMES धीमा चला गया। 100% की त्रुटि आवृत्ति पर, अपवाद संस्करण लगभग 100-TIMES धीमा है।

इससे मुझे पता चलता है कि शायद हमारे अपवाद कार्यान्वयन में गलत तरीके से व्यापार कर रहे हैं। अपवाद अधिक महंगा हो सकता है, या तो महंगा डंठल-चाल से बचने के द्वारा, या सीधे उन्हें संकलक समर्थित रिटर्न-वैल्यू जाँच में बदलकर। जब तक वे ऐसा नहीं करते, हम उनसे बचते हैं जब हम चाहते हैं कि हमारा कोड तेजी से चले।


3

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


2

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

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


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

2
@ स्पेंसर के: एक अपवाद, जैसा कि नाम से ही स्पष्ट है, का अर्थ है कि एक असाधारण स्थिति की खोज की गई थी (एक फ़ाइल चली गई, एक नेटवर्क अचानक बंद हो गया, ...)। इसका मतलब है कि स्थिति UNEXPECTED थी। यदि यह माना जाता है कि स्थिति उत्पन्न होगी, तो मैं इसके लिए एक अपवाद का उपयोग नहीं करूंगा।
मिकी

2
@ मेकी: सही है। मैंने हाल ही में इस बारे में किसी के साथ एक चर्चा की थी ... वे एक मान्यकरण रूपरेखा लिख ​​रहे थे और सत्यापन विफलता के मामले में अपवाद छोड़ रहे थे। मुझे लगता है कि यह एक बुरा विचार है क्योंकि यह काफी सामान्य होगा। मैं बल्कि विधि देखना होगा एक मान्यता।
user38051

2
नियंत्रण प्रवाह के संदर्भ में, एक अपवाद एक के अनुरूप है breakया returnएक, नहीं goto
हॉट लिप्स

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

2

बस तुलना करते हैं कि Integer.parseInt को निम्न विधि से कहें, जो केवल अपवाद को फेंकने के बजाय अप्राप्य डेटा के मामले में एक डिफ़ॉल्ट मान लौटाता है:

  public static int parseUnsignedInt(String s, int defaultValue) {
    final int strLength = s.length();
    if (strLength == 0)
      return defaultValue;
    int value = 0;
    for (int i=strLength-1; i>=0; i--) {
      int c = s.charAt(i);
      if (c > 47 && c < 58) {
        c -= 48;
        for (int j=strLength-i; j!=1; j--)
          c *= 10;
        value += c;
      } else {
        return defaultValue;
      }
    }
    return value < 0 ? /* übergebener wert > Integer.MAX_VALUE? */ defaultValue : value;
  }

जब तक आप दोनों विधियों को "मान्य" डेटा पर लागू करते हैं, तब तक वे दोनों लगभग एक ही दर (भले ही Integer.parseInt अधिक जटिल डेटा को संभालने का प्रबंधन करते हैं) पर काम करेंगे। लेकिन जैसे ही आप अमान्य डेटा (उदाहरण के लिए "abc" 1.000.000 बार पार्स करने की कोशिश करते हैं), प्रदर्शन में अंतर आवश्यक होना चाहिए।


2

अपवाद प्रदर्शन के बारे में महान पोस्ट है:

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

स्टैक ट्रेस और बिना, आदि के साथ, मौजूदा पुन: उपयोग करना

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

स्टैक ट्रेस की गहराई पर निर्भर करता है:

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

अन्य विवरणों के लिए (जेआईटी से x64 कोडर सहित) मूल ब्लॉग पोस्ट पढ़ें।

इसका मतलब है कि हाइबरनेट / स्प्रिंग / आदि-ईई-शिट अपवादों (xD) के कारण धीमी हैं और एप्‍लिकेशन एप कंट्रोल को अपवादों से दूर रखता है (इसकी जगह इसे ले लें continure/ विधि कॉल से C में जैसे झंडे breakवापस booleanलौटाएं) आपके एप्लिकेशन का प्रदर्शन बेहतर करता है Ix-100x पर निर्भर करता है कि आप उन्हें कितनी बार फेंकते हैं))


0

मैंने @Mecki के उत्तर को बदल दिया है पद्धति 1 में एक बूलियन और कॉलिंग विधि में एक चेक लौटाया है, क्योंकि आप केवल अपवाद को कुछ भी नहीं बदल सकते हैं। दो रन के बाद, मेथड 1 या तो सबसे तेज था या मेथड 2 जितना तेज था।

यहाँ कोड का स्नैपशॉट है:

// 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!");
            }
    }

और परिणाम:

1 चलाएं

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

2 चलाएं

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

0

केवल समय पर अप्रत्याशित परिस्थितियों से निपटने के लिए एक अपवाद है ।

सरल सत्यापन के स्थान पर एक अपवाद का उपयोग करना जो संकलन समय में किया जा सकता है, निष्पादन समय तक सत्यापन में देरी करेगा। यह बदले में कार्यक्रम की दक्षता को कम करेगा।

एक सरल का उपयोग करने के बजाय एक अपवाद को फेंकने पर .. यदि सत्यापन भी कोड को लिखने और बनाए रखने के लिए जटिल बना देगा।


-3

प्रोग्राम गति बनाम डेटा की जाँच के बारे में मेरी राय।

कई वर्गों में स्ट्रिंग टू वैल्यू कन्वर्टर (स्कैनर / पार्सर), सम्मानित और प्रसिद्ध पुस्तकालय भी थे;)

आमतौर पर रूप है

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

अपवाद नाम केवल उदाहरण है, आमतौर पर अनियंत्रित (रनटाइम), इसलिए फेंकता घोषणा केवल मेरी तस्वीर है

कभी-कभी दूसरा रूप मौजूद होता है:

public static Example Parse(String input, Example defaultValue)

कभी नहीं फेंकने वाला

जब दूसरा उपलब्ध नहीं है (या प्रोग्रामर बहुत कम डॉक्स पढ़ता है और केवल पहले का उपयोग करता है), ऐसे कोड को नियमित अभिव्यक्ति के साथ लिखें। नियमित अभिव्यक्ति शांत, राजनीतिक रूप से सही आदि:

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

इस कोड के साथ प्रोग्रामर अपवादों की लागत नहीं लेते हैं। लेकिन कभी-कभी अपवादों की छोटी लागत की तुलना में नियमित रूप से अभिव्यक्ति की बहुत उच्च लागत होती है।

मैं लगभग हमेशा ऐसे संदर्भ में उपयोग करता हूं

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

स्टैकट्रेस आदि का विश्लेषण किए बिना, मुझे विश्वास है कि आपका व्याख्यान काफी गति के बाद होगा।

अपवादों से डरो मत


-5

सामान्य रिटर्न की तुलना में अपवाद क्यों धीमा होना चाहिए?

जब तक आप स्टैकट्रेस को टर्मिनल पर प्रिंट नहीं करते हैं, तब तक इसे एक फ़ाइल या कुछ समान में सहेजें, कैच-ब्लॉक अन्य कोड-ब्लॉक की तुलना में अधिक काम नहीं करता है। इसलिए, मैं कल्पना नहीं कर सकता कि "नए my_cool_error ()" को क्यों फेंकना चाहिए।

अच्छा सवाल है और मैं इस विषय पर अधिक जानकारी के लिए तत्पर हूं!


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