अगर कभी भी अपवाद न फेंका जाए तो क्या ट्राइ-कैच ब्लॉक का उपयोग करना महंगा है?


189

हम जानते हैं कि अपवादों को पकड़ना महंगा है। लेकिन, क्या जावा में ट्राई-कैच ब्लॉक का इस्तेमाल करना भी महंगा है, भले ही कोई अपवाद न हो?

मुझे स्टैक ओवरफ्लो सवाल / जवाब मिला कि क्यों ब्लॉक महंगे हैं? , लेकिन यह .NET के लिए है


30
वास्तव में इस सवाल का कोई मतलब नहीं है। कोशिश करो..कैच का एक बहुत विशिष्ट उद्देश्य है। यदि आपको इसकी आवश्यकता है, तो आपको इसकी आवश्यकता है। किसी भी मामले में, एक बिंदु को पकड़ने के बिना एक कोशिश क्या है?
जॉनफक्स

49
try { /* do stuff */ } finally { /* make sure to release resources */ }कानूनी और उपयोगी है
A4L

4
उस लागत को लाभों के विरुद्ध तौला जाना चाहिए। यह अकेला खड़ा नहीं है। किसी भी मामले में, महंगा सापेक्ष है, और जब तक आप जानते हैं कि आप ऐसा नहीं कर सकते, तब तक कुछ न करने के बजाय सबसे स्पष्ट पद्धति का उपयोग करना समझ में आता है क्योंकि यह आपको एक घंटे के दौरान एक मिलीसेकंड या दो से बचा सकता है। कार्यक्रम का निष्पादन।
जोएल

4
मुझे आशा है कि यह "
लेट

6
@SAFX: Java7 से आप finallyब्लॉक का उपयोग करके भी छुटकारा पा सकते हैंtry-with-resources
a_horse_with_no_name

जवाबों:


201

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


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

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


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

2
क्या try...finallyबिना ब्लॉक किए catchभी कुछ अनुकूलन को रोका जा सकता है?
दाजूद

5
@Patashu "यह वास्तव में उस अपवाद को फेंक रहा है जो आपकी लागत है" तकनीकी रूप से, अपवाद को फेंकना महंगा नहीं है; Exceptionऑब्जेक्ट को इंस्टेंट करना सबसे अधिक समय लेता है।
ऑस्टिन डी

72

चलो इसे मापने, हम करेंगे?

public abstract class Benchmark {

    final String name;

    public Benchmark(String name) {
        this.name = name;
    }

    abstract int run(int iterations) throws Throwable;

    private BigDecimal time() {
        try {
            int nextI = 1;
            int i;
            long duration;
            do {
                i = nextI;
                long start = System.nanoTime();
                run(i);
                duration = System.nanoTime() - start;
                nextI = (i << 1) | 1;
            } while (duration < 100000000 && nextI > 0);
            return new BigDecimal((duration) * 1000 / i).movePointLeft(3);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String toString() {
        return name + "\t" + time() + " ns";
    }

    public static void main(String[] args) throws Exception {
        Benchmark[] benchmarks = {
            new Benchmark("try") {
                @Override int run(int iterations) throws Throwable {
                    int x = 0;
                    for (int i = 0; i < iterations; i++) {
                        try {
                            x += i;
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    return x;
                }
            }, new Benchmark("no try") {
                @Override int run(int iterations) throws Throwable {
                    int x = 0;
                    for (int i = 0; i < iterations; i++) {
                        x += i;
                    }
                    return x;
                }
            }
        };
        for (Benchmark bm : benchmarks) {
            System.out.println(bm);
        }
    }
}

मेरे कंप्यूटर पर, यह कुछ इस तरह प्रिंट करता है:

try     0.598 ns
no try  0.601 ns

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

आम तौर पर, मैं अनुशंसा करता हूं कि भाषा निर्माण की प्रदर्शन लागत के बारे में चिंता न करें जब तक आपके पास अपने कोड में वास्तविक प्रदर्शन समस्या का सबूत न हो। या जैसा कि डोनाल्ड नथ ने कहा था: "समय से पहले अनुकूलन सभी बुराई की जड़ है"।


4
अधिकांश जेवीएम पर एक जैसा होने की कोशिश / कोई कोशिश नहीं की जाती है, माइक्रोबेंचमार्क बहुत त्रुटिपूर्ण है।
bestsss

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

3
सूचना बार यात्रा प्रति पाश की। माप के रूप में केवल तभी उपयोग किया जाएगा जब इसका कुल बीता हुआ समय> 0.1 सेकंड (या 2 बिलियन पुनरावृत्तियों, जो यहां मामला नहीं था) मुझे लगता है कि आपके दावे को पूरी तरह से कठिन मानकर हटा दिया गया है - क्योंकि लूप को हटा दिया गया, जिसे निष्पादित करने में 0.1 सेकंड का समय लगा?
मेरिटॉन

... और वास्तव में, -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssemblyदोनों के अनुसार , लूप और उसके अतिरिक्त जेनरेट किए गए मूल कोड में मौजूद हैं। और नहीं, अमूर्त विधियों को इनबिल्ड नहीं किया जाता है, क्योंकि उनका कॉलर बस संकलित समय में नहीं होता है (संभवतः, क्योंकि यह पर्याप्त समय नहीं लगाया गया है)।
मेरिटोन

मैं जावा में एक सही माइक्रो-बेंचमार्क कैसे लिखूं: stackoverflow.com/questions/504103/…
Vadzim

46

try/ catchप्रदर्शन पर कुछ प्रभाव पड़ सकता है। ऐसा इसलिए है क्योंकि यह JVM को कुछ अनुकूलन करने से रोकता है। जोशुआ बलोच ने "प्रभावी जावा में," निम्नलिखित कहा:

• कोड-पकड़ने वाले ब्लॉक के अंदर कोड रखने से कुछ अनुकूलन हो सकते हैं जो आधुनिक JVM कार्यान्वयन अन्यथा प्रदर्शन कर सकते हैं।


24
"यह JVM को कुछ अनुकूलन करने से रोकता है" ...? क्या आप विस्तृत रूप से बता सकते हैं?
द क्रैकन

5
@ द क्रैक कोड ऑफ़ ट्राई ब्लॉक (आमतौर पर? हमेशा?) को एक उदाहरण के रूप में, कोशिश ब्लॉक के बाहर कोड के साथ फिर से ऑर्डर नहीं किया जा सकता है।
पाराशू

3
ध्यान दें कि सवाल यह था कि "यह महंगा है", न कि "इसका प्रदर्शन पर कोई प्रभाव पड़ता है"।
मिकोलेक

3
प्रभावी जावा से एक अंश जोड़ा गया है , और यह जावा की बाइबिल है, ज़ाहिर है; जब तक कोई संदर्भ नहीं है अंश कुछ भी नहीं बताता है। व्यावहारिक रूप से जावा में कोई भी कोड किसी स्तर पर / अंत में कोशिश के भीतर है।
bestsss

29

हां, जैसा कि दूसरों ने कहा है, एक tryब्लॉक {}इसके आसपास के पात्रों में कुछ अनुकूलन को रोकता है। विशेष रूप से, ऑप्टिमाइज़र को यह मान लेना चाहिए कि ब्लॉक के भीतर किसी भी बिंदु पर एक अपवाद हो सकता है, इसलिए ऐसा कोई आश्वासन नहीं है कि बयान निष्पादित किए जाते हैं।

उदाहरण के लिए:

    try {
        int x = a + b * c * d;
        other stuff;
    }
    catch (something) {
        ....
    }
    int y = a + b * c * d;
    use y somehow;

tryमूल्य के बिना , असाइन करने के लिए गणना की गई मूल्य xको एक "सामान्य सबप्रेप्रेशन" के रूप में सहेजा जा सकता है और असाइन करने के लिए पुन: उपयोग किया जा सकता है y। लेकिन की वजह सेtry कोई आश्वासन नहीं है कि पहले अभिव्यक्ति का कभी मूल्यांकन किया गया था, इसलिए अभिव्यक्ति को पुन: प्रतिष्ठित किया जाना चाहिए। यह आमतौर पर "स्ट्रेट-लाइन" कोड में एक बड़ी बात नहीं है, लेकिन लूप में महत्वपूर्ण हो सकता है।

हालाँकि, यह ध्यान दिया जाना चाहिए कि यह केवल JITCed कोड पर लागू होता है। javac केवल अनुकूलन की एक मात्रा है, और एक tryब्लॉक में प्रवेश करने / छोड़ने के लिए bytecode दुभाषिया की शून्य लागत है । (ब्लॉक सीमाओं को चिह्नित करने के लिए कोई बाइटकोड उत्पन्न नहीं किया गया है।)

और बेस्टसी के लिए:

public class TryFinally {
    public static void main(String[] argv) throws Throwable {
        try {
            throw new Throwable();
        }
        finally {
            System.out.println("Finally!");
        }
    }
}

आउटपुट:

C:\JavaTools>java TryFinally
Finally!
Exception in thread "main" java.lang.Throwable
        at TryFinally.main(TryFinally.java:4)

जावप आउटपुट:

C:\JavaTools>javap -c TryFinally.class
Compiled from "TryFinally.java"
public class TryFinally {
  public TryFinally();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Throwable;
    Code:
       0: new           #2                  // class java/lang/Throwable
       3: dup
       4: invokespecial #3                  // Method java/lang/Throwable."<init>":()V
       7: athrow
       8: astore_1
       9: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      12: ldc           #5                  // String Finally!
      14: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      17: aload_1
      18: athrow
    Exception table:
       from    to  target type
           0     9     8   any
}

कोई "गोटो" नहीं।


ब्लॉक बाउंड्रीज़ को चिह्नित करने के लिए कोई बाइटकोड उत्पन्न नहीं होता है यह जरूरी नहीं है - इसे ब्लॉक छोड़ने के लिए गोटो की आवश्यकता होती है, अन्यथा यह catch/finallyफ्रेम में गिर जाएगा ।
bestsss

@bestsss - भले ही एक GOTO उत्पन्न होता है (जो दिया नहीं जाता है) की लागत न्यूनतम है, और यह ब्लॉक सीमा के लिए "मार्कर" होने से बहुत दूर है - GOTO कई निर्माणों के लिए उत्पन्न किया जा सकता है।
हॉट लाइक्स

मैंने कभी भी लागत का उल्लेख नहीं किया है, हालांकि कोई बायटेकोड उत्पन्न नहीं होता है यह एक गलत कथन है। बस इतना ही। दरअसल, बायटेकोड में कोई ब्लॉक नहीं होते हैं, फ्रेम बराबर ब्लॉक नहीं होते हैं।
बेस्टसेव्स

यदि कोई प्रयास सीधे अंत में गिर जाता है, तो कोई गोटो नहीं होगा, और ऐसे अन्य परिदृश्य हैं जहां कोई गोटो नहीं होगा। मुद्दा यह है कि "एंट्री ट्राई" / "एक्जिट ट्राई" बाईटकोड के आदेश पर कुछ भी नहीं है।
हॉट लाइक्स

अगर कोशिश अंत में सीधे गिरती है तो कोई GOTO नहीं होगा - गलत! बायटेकोड finallyमें कोई नहीं है, यह है try/catch(Throwable any){...; throw any;}और इसमें स्टेटमेंट डब्ल्यू / एक फ्रेम और थ्रोबेबल है, जिसे मस्ट बीई डिफाइन किया गया है (नॉन नल) और इसी तरह। आप विषय के बारे में बहस करने की कोशिश क्यों करते हैं, आप कम से कम कुछ बायोटेक की जांच कर सकते हैं? निहितार्थ के लिए वर्तमान दिशानिर्देश। अंत में ब्लॉक की नकल कर रहा है और गोटो सेक्शन (पिछले निहित) से परहेज कर रहा है, लेकिन कितने एक्जिट पॉइंट हैं, इसके आधार पर बायटेकॉड को कॉपी करना होगा।
बेस्ट

8

यह समझने के लिए कि अनुकूलन का प्रदर्शन क्यों नहीं किया जा सकता है, अंतर्निहित तंत्र को समझना उपयोगी है। सबसे उपयुक्त उदाहरण मैं पा सकता हूँ कि सी मैक्रोज़ में लागू किया गया था: http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html

#include <stdio.h>
#include <setjmp.h>
#define TRY do{ jmp_buf ex_buf__; switch( setjmp(ex_buf__) ){ case 0: while(1){
#define CATCH(x) break; case x:
#define FINALLY break; } default:
#define ETRY } }while(0)
#define THROW(x) longjmp(ex_buf__, x)

कंपाइलरों को अक्सर यह निर्धारित करने में कठिनाई होती है कि क्या एक छलांग एक्स, वाई और जेड के लिए स्थानीय हो सकती है ताकि वे अनुकूलन को छोड़ दें कि वे सुरक्षित होने की गारंटी नहीं दे सकते हैं, लेकिन कार्यान्वयन खुद ही हल्का है।


4
ये सी मैक्रोज़ जिन्हें आपने ट्राइ / कैच के लिए पाया है, वे जावा या C # कार्यान्वयन के बराबर नहीं हैं, जो 0 रन टाइम निर्देशों का उत्सर्जन करते हैं।
पताशु

जावा कार्यान्वयन पूर्ण रूप से शामिल करने के लिए बहुत व्यापक है, यह इस बात के मूल विचार को समझने के उद्देश्य से एक सरलीकृत कार्यान्वयन है कि अपवाद कैसे लागू किया जा सकता है। यह कहते हुए कि यह 0 रन टाइम निर्देशों का भ्रामक है। उदाहरण के लिए एक साधारण क्लासकैस्टैसेप्शन रनटाइमएक्सैप्शन को बढ़ाता है जो अपवाद को बढ़ाता है जो फेंकने योग्य का विस्तार करता है जिसमें शामिल हैं: grepcode.com/file/repository.grepcode.com/java/root/jdk/openjkk/ ... सी में स्विच-केस कहने जैसा है। यदि केवल 1 केस का उपयोग किया जाता है, तो अभी भी एक छोटा स्टार्टअप ओवरहेड है।
टेक्नोसॉरस

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

2
मैं C के बारे में नहीं बोल सकता। C # और Java में, कोशिश मेटाडाटा जोड़कर लागू की जाती है, कोड नहीं। जब एक कोशिश ब्लॉक में प्रवेश किया जाता है, तो यह इंगित करने के लिए कुछ भी निष्पादित नहीं किया जाता है - जब एक अपवाद फेंक दिया जाता है, तो स्टैक को खोल दिया जाता है और मेटाडाटा उस अपवाद प्रकार (महंगे) के हैंडलर के लिए जाँच की जाती है।
पलाशू

1
हां, मैंने वास्तव में एक जावा दुभाषिया और स्थिर बायोटेक कंपाइलर को लागू किया है और बाद में JITC (IBM iSeries के लिए) पर काम किया है और मैं आपको बता सकता हूं कि कुछ भी नहीं है जो बाईटकोड में कोशिश रेंज के प्रवेश / निकास के लिए है, बल्कि श्रेणियों की पहचान एक अलग तालिका में की जाती है। दुभाषिया एक कोशिश सीमा के लिए कुछ खास नहीं करता है (जब तक कि कोई अपवाद नहीं उठाया जाता है)। एक JITC (या स्थिर बायोटेक कंपाइलर) को पहले से बताए गए अनुकूलन को दबाने के लिए सीमाओं के बारे में पता होना चाहिए।
हॉट लक्स

8

फिर भी एक और माइक्रोबेनमार्क ( स्रोत )।

मैंने एक परीक्षण बनाया जिसमें मैं एक अपवाद प्रतिशत के आधार पर ट्राइ-कैच और नो-ट्राई-कैच कोड संस्करण को मापता हूं। 10% प्रतिशत का मतलब है कि परीक्षण के 10% मामलों में शून्य मामलों का विभाजन था। एक स्थिति में इसे एक ट्रायल-कैच ब्लॉक द्वारा नियंत्रित किया जाता है, दूसरे में एक सशर्त ऑपरेटर द्वारा। यहाँ मेरे परिणाम तालिका है:

OS: Windows 8 6.2 x64
JVM: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 23.25-b01
प्रतिशत | परिणाम (प्रयास करें / यदि, ns)   
    0% | 88/90   
    1% | 89/87    
    10% | 86/97    
    90% | 85/83   

जो कहता है कि इनमें से किसी भी मामले में कोई महत्वपूर्ण अंतर नहीं है।


3

मैंने NullPointException को बहुत महंगा पाया है। 1.2k संचालन के लिए समय 200ms और 12ms था जब मैंने इसे उसी तरह से हैंडल किया if(object==null)जिसके साथ मेरे लिए बहुत सुधार हुआ।

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