क्यों स्विच अगर की तुलना में तेज है


116

जावा पुस्तकों के बहुत सारे switchविवरण कथन की तुलना में अधिक तेज़ हैं if else। लेकिन मुझे कहीं भी यह पता नहीं चला कि स्विच क्यों से तेज है

उदाहरण

मेरे पास एक स्थिति है मुझे दो में से किसी एक आइटम को चुनना होगा। मैं या तो उपयोग कर सकते हैं

switch (item) {
    case BREAD:
        //eat Bread
        break;
    default:
        //leave the restaurant
}

या

if (item == BREAD) {
    //eat Bread
} else {
    //leave the restaurant
}

आइटम पर विचार करना और BREAD एक निरंतर अंतर मान है।

उपरोक्त उदाहरण में कौन सी क्रिया में तेज है और क्यों?


हो सकता है कि यह जावा के लिए भी एक जवाब है: stackoverflow.com/questions/767821/…
टॉबीस

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

इस प्रश्न का शीर्ष उत्तर इसे बहुत अच्छी तरह से समझाता है। यह लेख सब कुछ बहुत अच्छी तरह से समझाता है।
बेज़मैक्स

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

2
आपको उन पुस्तकों से सावधान रहना चाहिए जो विवरण / प्रमाण / तर्क के बिना इस तरह के बयान देते हैं।
मैट बी

जवाबों:


110

क्योंकि विशेष बायोटेक हैं जो बहुत सारे मामलों में कुशल स्विच स्टेटमेंट मूल्यांकन की अनुमति देते हैं।

यदि IF- कथनों के साथ कार्यान्वित किया जाता है तो आपके पास एक चेक, अगले क्लॉज के लिए एक जंप, एक चेक, अगले क्लॉज पर एक जंप वगैरह होगा। स्विच के साथ जेवीएम एक मैच खोजने के लिए मूल्य तालिका के माध्यम से तुलना और पुनरावृत्ति करने के लिए मूल्य को लोड करता है, जो ज्यादातर मामलों में तेज है।


6
"चेक, जंप" करने के लिए अनुवाद नहीं करता है?
पांचवाइटिसिक्स जूल

17
@fivetwentysix: नहीं, इस जानकारी के लिए देखें: artima.com/underthehood/flowP.html । लेख से उद्धरण: जब जेवीएम एक टेबलवॉच निर्देश का सामना करता है, तो यह केवल यह देखने के लिए जांच कर सकता है कि कुंजी निम्न और उच्च द्वारा परिभाषित सीमा के भीतर है या नहीं। यदि नहीं, तो यह डिफ़ॉल्ट शाखा ऑफसेट लेता है। यदि ऐसा है, तो यह शाखा ऑफ़सेट्स की सूची में एक ऑफसेट प्राप्त करने के लिए कुंजी से कम घटाता है। इस तरीके से, यह प्रत्येक केस मान की जांच किए बिना उपयुक्त शाखा ऑफसेट निर्धारित कर सकता है।
बेज़मैक्स

1
(i) एक बायोटेक निर्देश switchमें अनुवाद नहीं किया जा सकता है tableswitch- यह एक lookupswitchनिर्देश बन सकता है जो समान / if (ii) के समान कार्य करता है, भले ही tableswitchबायटेकोड निर्देशों को JIT द्वारा / की एक श्रृंखला में संकलित किया जा सकता है, कारकों के आधार पर जैसे कि एस की संख्या case
अलेक्जिलिया


34

एक switchबयान हमेशा एक ifबयान से तेज नहीं होता है । यह if-elseबयानों की एक लंबी सूची से बेहतर है, क्योंकि switchसभी मूल्यों के आधार पर एक लुकअप प्रदर्शन किया जा सकता है। हालाँकि, एक छोटी स्थिति के लिए यह कोई तेज़ नहीं होगा और धीमा हो सकता है।


5
कृपया "लंबी" कसना। 5 से अधिक है? 10 से अधिक? या 20 - 30 की तरह?
vanderwyst

11
मुझे संदेह है कि यह निर्भर करता है। मेरे लिए इसके 3 या उससे अधिक सुझाव switchस्पष्ट होंगे यदि तेज नहीं है।
पीटर लॉरी

किन परिस्थितियों में यह धीमा हो सकता है?
एरिक

1
@ यह थोड़ी सी मानों के लिए धीमी है स्ट्रिंग या int जो विरल हैं।
पीटर लॉरी

8

वर्तमान जेवीएम में दो प्रकार के स्विच बाइट कोड हैं: लुकअपस्विच और टेबलस्विच।

स्विच स्टेटमेंट में प्रत्येक मामले में एक पूर्णांक ऑफसेट होता है, यदि ये ऑफ़सेट सन्निहित हैं (या अधिकतर बड़े अंतराल के साथ सन्निहित हैं) (केस 0: केस 1: केस 2, आदि), तो टेबलस्विच का उपयोग किया जाता है।

यदि ऑफसेट बड़े अंतराल (केस 0: केस 400: केस 93748 :, आदि) के साथ फैला हुआ है, तो लुकअपस्विच का उपयोग किया जाता है।

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

लुकअप स्विच सही कोड शाखा को खोजने के लिए एक द्विआधारी खोज का उपयोग करता है। यह ओ (लॉग एन) समय में चलता है, जो अभी भी अच्छा है, लेकिन सबसे अच्छा नहीं है।

इस बारे में अधिक जानकारी के लिए, यहां देखें: जेवीएम के लुकअपस्विच और टेबलस्विच के बीच अंतर?

इसलिए जहां तक ​​कोई सबसे तेज है, इस दृष्टिकोण का उपयोग करें: यदि आपके पास 3 या अधिक मामले हैं जिनके मूल्य लगातार या लगभग लगातार हैं, तो हमेशा एक स्विच का उपयोग करें।

यदि आपके पास 2 मामले हैं, तो एक बयान का उपयोग करें।

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

इसके अलावा, ध्यान रखें कि JVM JIT ऑप्टिमाइज़ेशन चलाएगा यदि स्टेटमेंट्स कोड में सबसे हॉट ब्रांच को रखने की कोशिश करेंगे। इसे "शाखा भविष्यवाणी" कहा जाता है। इस बारे में अधिक जानकारी के लिए, यहां देखें: https://dzone.com/articles/branch-prediction-in-java

आपके अनुभव अलग-अलग हो सकते हैं। मुझे नहीं पता कि JVM लुकअपस्विच पर एक समान अनुकूलन नहीं चलाता है, लेकिन मैंने JIT अनुकूलन पर भरोसा करना सीखा है और संकलक को बाहर करने की कोशिश नहीं की है।


1
इसे पोस्ट करने के बाद से, यह मेरे ध्यान में आया है कि "स्विच एक्सप्रेशंस" और "पैटर्न मैचिंग" जावा में आ रहे हैं, संभवतः जैसे ही जावा 12. openjdk.java.net/jeps/325 openjdk.java.net/jeps/305 अभी तक कुछ भी ठोस नहीं है, लेकिन ऐसा प्रतीत होता है कि ये switchभाषा को और भी अधिक शक्तिशाली बना देंगे । पैटर्न मिलान, उदाहरण के लिए, बहुत चिकनी और instanceofपरफ़ॉर्मेंट लुकअप की अनुमति देगा । हालाँकि, मुझे लगता है कि यह मान लेना सुरक्षित है कि बुनियादी स्विच के लिए / यदि परिदृश्य में, मेरे द्वारा उल्लेखित नियम अभी भी लागू होगा।
HesNotTheStig

1

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

नीचे दिए गए परिणाम को प्राप्त करने के लिए आपकी जरूरत है अमूर्तता .. मैं यह नहीं बताने जा रहा हूं कि यह कैसे काम करता है उम्मीद है कि आपको इसकी समझ है।

मैंने इसे पैकेट के आकार को 255 पर सेट करने के लिए अद्यतन किया है यदि आपको अधिक आवश्यकता है तो आपको एक बाउंड चेक करना होगा (आईडी <0) || (आईडी> लंबाई)।

Packets[] packets = new Packets[255];

static {
     packets[0] = new Login(6);
     packets[2] = new Logout(8);
     packets[4] = new GetMessage(1);
     packets[8] = new AddFriend(0);
     packets[11] = new JoinGroupChat(7); // etc... not going to finish.
}

public void handlePacket(IncomingData data)
{
    int id = data.readByte() & 0xFF; //Secure value to 0-255.

    if (packet[id] == null)
        return; //Leave if packet is unhandled.

    packets[id].execute(data);
}

चूंकि मैं C ++ में जंप टेबल का उपयोग करता हूं, इसलिए अब मैं फ़ंक्शन पॉइंटर जंप टेबल का एक उदाहरण दिखाऊंगा। यह एक बहुत ही सामान्य उदाहरण है, लेकिन मैंने इसे चलाया और यह सही तरीके से काम करता है। ध्यान रखें कि आपको पॉइंटर को NULL पर सेट करना होगा, C ++ जावा में अपने आप ऐसा नहीं करेगा।

#include <iostream>

struct Packet
{
    void(*execute)() = NULL;
};

Packet incoming_packet[255];
uint8_t test_value = 0;

void A() 
{ 
    std::cout << "I'm the 1st test.\n";
}

void B() 
{ 
    std::cout << "I'm the 2nd test.\n";
}

void Empty() 
{ 

}

void Update()
{
    if (incoming_packet[test_value].execute == NULL)
        return;

    incoming_packet[test_value].execute();
}

void InitializePackets()
{
    incoming_packet[0].execute = A;
    incoming_packet[2].execute = B;
    incoming_packet[6].execute = A;
    incoming_packet[9].execute = Empty;
}

int main()
{
    InitializePackets();

    for (int i = 0; i < 512; ++i)
    {
        Update();
        ++test_value;
    }
    system("pause");
    return 0;
}

इसके अलावा एक और बिंदु जो मैं सामने लाना चाहूंगा वह है प्रसिद्ध डिवाइड एंड कॉन्कर। तो मेरा 255 से ऊपर का सरणी विचार कम हो सकता है 8 नहीं तो 8 सबसे खराब स्थिति के रूप में बयान।

Ie लेकिन ध्यान रखें कि यह तेजी से प्रबंधित करने के लिए गन्दा और कठिन हो जाता है और मेरा अन्य दृष्टिकोण आम तौर पर बेहतर होता है, लेकिन यह उन मामलों में उपयोग होता है जहां सरणियाँ इसे काट नहीं पाएंगी। आपको अपने उपयोग के मामले का पता लगाना होगा और जब प्रत्येक स्थिति सबसे अच्छा काम करेगी। जैसे आप इन तरीकों में से किसी का उपयोग नहीं करना चाहते हैं यदि आपके पास केवल कुछ चेक हैं।

If (Value >= 128)
{
   if (Value >= 192)
   {
        if (Value >= 224)
        {
             if (Value >= 240)
             {
                  if (Value >= 248)
                  {
                      if (Value >= 252)
                      {
                          if (Value >= 254)
                          {
                              if (value == 255)
                              {

                              } else {

                              }
                          }
                      }
                  }
             }      
        }
   }
}

2
डबल अप्रत्यक्ष क्यों? चूंकि आईडी को वैसे भी विवश होना चाहिए, क्यों न केवल आने वाली आईडी की जांच करें 0 <= id < packets.lengthऔर सुनिश्चित करें packets[id]!=nullऔर फिर करें packets[id].execute(data)?
लॉरेंस Dol

हाँ देर से प्रतिक्रिया के लिए खेद है कि यह फिर से देखा .. और मुझे नहीं पता कि मैं क्या सोच रहा था मैंने पोस्ट लोल को अपडेट किया और पैकेट को एक अहस्ताक्षरित बाइट के आकार में कैप किया ताकि किसी भी लंबाई की जांच की आवश्यकता न हो।
जेरेमी ट्रिफ़िलो

0

बाइटकोड स्तर पर, विषय चर को केवल एक बार प्रोसेसर रजिस्टर में मेमोरी एड्रेस से संरचित .class फ़ाइल में लोड किया जाता है, जिसे रनटाइम द्वारा लोड किया जाता है, और यह एक स्विच स्टेटमेंट में होता है; हालांकि एक if-statement में, एक अलग jvm निर्देश आपके कोड-संकलन DE द्वारा निर्मित किया जाता है, और इसके लिए यह आवश्यक है कि प्रत्येक चर को रजिस्टरों में लोड किया जाए, हालांकि उसी वेरिएंट का उपयोग if-statement से पहले के रूप में किया जाता है। यदि आप विधानसभा भाषा में कोडिंग के बारे में जानते हैं तो यह सामान्य बात होगी; हालांकि जावा संकलित कॉक्स बायोटेक, या प्रत्यक्ष मशीन कोड नहीं हैं, इसके लिए सशर्त अवधारणा अभी भी सुसंगत है। खैर, मैंने समझाने पर गहरी तकनीकी से बचने की कोशिश की। मुझे उम्मीद है कि मैंने अवधारणा को स्पष्ट और ध्वस्त कर दिया था। धन्यवाद।

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