Java'da if / else ile switch deyiminin göreli performans farkı nedir?


124

Web uygulamamın performansları hakkında endişelenerek, performansla ilgili olarak "if / else" veya switch ifadelerinden hangisinin daha iyi olduğunu merak ediyorum.


6
İki yapı için aynı bayt kodunun üretilmediğini düşünmek için herhangi bir nedeniniz var mı?
Pascal Cuoq

2
@Pascal: Bir liste ifvb. Yerine tablo aramaları kullanılarak optimizasyon yapılabilir .
jldupont

18
"Erken optimizasyon tüm kötülüklerin köküdür" - Donald Knuth
kayıp kişi

104
Bu kesinlikle erken bir optimizasyon olsa da, "Bağlam dışında kötü bir şekilde alınan bir alıntıya akılsızca bağlı kalmak, bugün makul derecede duyarlı bir GUI görüntülemek için yüksek kaliteli çok çekirdekli bir bilgisayara ihtiyacımız olan nedendir" - Me.
Lawrence Dol

2
Knuth'un kesin bir zihni var. Lütfen "prematüre" niteleyicisine dikkat edin. Optimizasyon tamamen geçerli bir konudur. Bununla birlikte, bir sunucu GÇ'ye bağlıdır ve ağ ve disk G / Ç darboğazları, sunucunuzda olup biten her şeyden çok daha önemlidir.
alphazero

Yanıtlar:


108

Bu, kötü olan mikro optimizasyon ve erken optimizasyondur. Daha ziyade, söz konusu kodun okunabilirliği ve sürdürülebilirliği konusunda endişelenin. Birbirine if/elseyapıştırılmış ikiden fazla blok varsa veya boyutu tahmin edilemezse, o zaman bir switchifade düşünebilirsiniz .

Alternatif olarak, Polimorfizm'i de alabilirsiniz . Önce bir arayüz oluşturun:

public interface Action { 
    void execute(String input);
}

Ve bazılarında tüm uygulamaları yakalayın Map. Bunu statik veya dinamik olarak yapabilirsiniz:

Map<String, Action> actions = new HashMap<String, Action>();

Son olarak if/elseveya yerine switchböyle bir şey koyun (boş işaretçiler gibi önemsiz kontrolleri bir kenara bırakın):

actions.get(name).execute(input);

Bu belki daha microslower olmak if/elseveya switch, fakat kod en azından çok daha iyi muhafaza edilebilir.

Web uygulamalarından bahsederken, HttpServletRequest#getPathInfo()eylem anahtarı olarak kullanabilirsiniz (sonunda, bir eylem bulunana kadar yol bilgisinin son bölümünü bir döngüde ayırmak için biraz daha kod yazın). Burada benzer cevaplar bulabilirsiniz:

Genel olarak Java EE web uygulaması performansı hakkında endişeleniyorsanız, bu makaleyi de yararlı bulabilirsiniz . Ham Java kodunu yalnızca (mikro) optimize etmekten çok daha fazla performans kazancı sağlayan başka alanlar da vardır .


1
veya bunun yerine polimorfizmi düşünün
jk.

Bu gerçekten de "öngörülemeyen" if / else blokları durumunda daha fazla tavsiye edilir.
BalusC

73
Tüm erken optimizasyonu "kötü" olarak görmezden gelmek için o kadar hızlı değilim. Çok agresif olmak aptallıktır, ancak karşılaştırılabilir okunabilirlik yapıları ile karşı karşıya kaldığında, daha iyi performans gösterdiği bilinen birini seçmek uygun bir karardır.
Brian Knoblauch

8
HashMap arama sürümü, bir tablewitsch talimatına kıyasla kolayca 10 kat daha yavaş olabilir. Ben buna "mikro çiçek" demezdim!
x4u

7
Anahtar ifadeleriyle genel durumda Java'nın iç işleyişini bilmekle ilgileniyorum - birisinin bunun erken optimizasyona aşırı öncelik vermekle ilgili olduğunu düşünüp düşünmediğiyle ilgilenmiyorum. Bununla birlikte, bu cevaba neden bu kadar çok oy verildiği ve neden kabul edilen cevap olduğu hakkında hiçbir fikrim yok ... bu, ilk soruyu cevaplamak için hiçbir şey yapmaz.
searchengine27

125

Erken optimizasyonun kaçınılması gereken bir şey olduğu fikrine tamamen katılıyorum.

Ancak Java sanal makinesinin switch () 'ler için kullanılabilecek özel bayt kodlarına sahip olduğu doğrudur.

WM Spesifikasyonuna bakın (arama anahtarı ve tablo anahtarı )

Dolayısıyla, kod performans CPU grafiğinin bir parçasıysa, bazı performans kazanımları olabilir.


60
Bu yorumun neden daha yüksek derecelendirilmediğini merak ediyorum: hepsi içinde en bilgilendiricidir. Demek istediğim: Hepimiz erken optimizasyonun kötü olduğunu zaten biliyoruz, bunu 1000. kez açıklamaya gerek yok.
Folkert van Heusden

5
+1 stackoverflow.com/a/15621602/89818 itibarıyla , performans kazanımları gerçekten orada görünüyor ve 18'den fazla kasa kullanırsanız bir avantaj görmelisiniz.
2014 13:32

52

Bir if / else veya bir anahtarın performans sıkıntılarınızın kaynağı olması son derece düşük bir ihtimaldir. Performans sorunları yaşıyorsanız, yavaş noktaların nerede olduğunu belirlemek için önce bir performans profili analizi yapmalısınız. Erken optimizasyon, tüm kötülüklerin köküdür!

Yine de, Java derleyici optimizasyonları ile anahtar ve if / else'in göreceli performansı hakkında konuşmak mümkündür. Öncelikle, Java'da switch ifadelerinin çok sınırlı bir etki alanında (tamsayılar) çalıştığını unutmayın. Genel olarak, bir switch ifadesini aşağıdaki gibi görüntüleyebilirsiniz:

switch (<condition>) {
   case c_0: ...
   case c_1: ...
   ...
   case c_n: ...
   default: ...
}

nerede c_0,, c_1... ve c_Nswitch ifadesinin hedefleri olan ve <condition>bir tamsayı ifadesine çözülmesi gereken integral sayılardır .

  • Bu küme "yoğun" ise - yani (max (c i ) + 1 - min (c i )) / n> α, burada 0 <k <α <1, burada kbazı ampirik değerlerden daha büyük, a Son derece verimli olan atlama tablosu oluşturulabilir.

  • Bu küme çok yoğun değilse, ancak n> = β ise, bir ikili arama ağacı hedefi O (2 * log (n)) içinde bulabilir ve bu da yine de etkilidir.

Diğer tüm durumlar için, bir switch ifadesi tam olarak eşdeğer if / else ifadeleri dizisi kadar etkilidir. Α ve β'nin kesin değerleri bir dizi faktöre bağlıdır ve derleyicinin kod optimizasyon modülü tarafından belirlenir.

Son olarak, elbette, etki alanı <condition>tamsayı değilse, switch deyimi tamamen işe yaramaz.


+1. Ağ G / Ç'sine harcanan zamanın bu sorunu kolayca gölgede bırakma olasılığı yüksektir.
Adam Paynter

3
Anahtarların yalnızca intler ile çalıştığı unutulmamalıdır. Java Öğreticilerinden: "Anahtar bayt, kısa, karakter ve int ilkel veri türleriyle çalışır. Ayrıca numaralandırılmış türlerle (Enum Türleri'nde tartışılmıştır), String sınıfı ve belirli ilkel türleri saran birkaç özel sınıfla çalışır. : Karakter, Bayt, Kısa ve Tam Sayı (Sayılar ve Dizeler'de ele alınmıştır). " String desteği daha yeni bir ektir; Java 7'de eklendi. docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html
atraudes

1
@jhonFeminella Swtich'deki Java7 String için BIG O kavramı etkilerini if ​​/ else if ..'deki String ile karşılaştırır mısınız?
Kanagavelu Sugumar

Daha doğrusu, javac 8, uzay karmaşıklığı üzerinde 3 kat karmaşıklık ağırlığı verir: stackoverflow.com/a/31032054/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

11

Anahtarı kullanın!

If-else-bloklarını korumaktan nefret ediyorum! Bir test yapın:

public class SpeedTestSwitch
{
    private static void do1(int loop)
    {
        int temp = 0;
        for (; loop > 0; --loop)
        {
            int r = (int) (Math.random() * 10);
            switch (r)
            {
                case 0:
                    temp = 9;
                    break;
                case 1:
                    temp = 8;
                    break;
                case 2:
                    temp = 7;
                    break;
                case 3:
                    temp = 6;
                    break;
                case 4:
                    temp = 5;
                    break;
                case 5:
                    temp = 4;
                    break;
                case 6:
                    temp = 3;
                    break;
                case 7:
                    temp = 2;
                    break;
                case 8:
                    temp = 1;
                    break;
                case 9:
                    temp = 0;
                    break;
            }
        }
        System.out.println("ignore: " + temp);
    }

    private static void do2(int loop)
    {
        int temp = 0;
        for (; loop > 0; --loop)
        {
            int r = (int) (Math.random() * 10);
            if (r == 0)
                temp = 9;
            else
                if (r == 1)
                    temp = 8;
                else
                    if (r == 2)
                        temp = 7;
                    else
                        if (r == 3)
                            temp = 6;
                        else
                            if (r == 4)
                                temp = 5;
                            else
                                if (r == 5)
                                    temp = 4;
                                else
                                    if (r == 6)
                                        temp = 3;
                                    else
                                        if (r == 7)
                                            temp = 2;
                                        else
                                            if (r == 8)
                                                temp = 1;
                                            else
                                                if (r == 9)
                                                    temp = 0;
        }
        System.out.println("ignore: " + temp);
    }

    public static void main(String[] args)
    {
        long time;
        int loop = 1 * 100 * 1000 * 1000;
        System.out.println("warming up...");
        do1(loop / 100);
        do2(loop / 100);

        System.out.println("start");

        // run 1
        System.out.println("switch:");
        time = System.currentTimeMillis();
        do1(loop);
        System.out.println(" -> time needed: " + (System.currentTimeMillis() - time));

        // run 2
        System.out.println("if/else:");
        time = System.currentTimeMillis();
        do2(loop);
        System.out.println(" -> time needed: " + (System.currentTimeMillis() - time));
    }
}

Karşılaştırma için C # standart kodum


Lütfen (bazen) bunu nasıl kıyasladığınız konusunda biraz detaylandırır mısınız?
DerMike

Güncellemeniz için çok teşekkür ederim. Demek istediğim, bir büyüklük sırasına göre farklılık gösterirler - ki bu elbette mümkündür. Derleyicinin sadece e-postaları optimize etmediğinden emin misiniz switch?
DerMike

@DerMike Eski sonuçları nasıl aldığımı hatırlamıyorum. Bugün çok farklı oldum. Ama kendiniz deneyin ve nasıl sonuçlandığını bana bildirin.
Bitterblue

1
dizüstü bilgisayarımda çalıştırdığımda; gerekli değiştirme süresi: 3585, eğer / else gerekli zaman: 3458 öyleyse / else daha iyi :) veya daha kötü değil
halil

1
Testteki baskın maliyet, rastgele sayı üretimidir. Döngüden önce rastgele sayı üretmek için testi değiştirdim ve temp değerini r'ye geri beslemek için kullandım. Bu durumda anahtar, eğer-değilse zincirinin neredeyse iki katı hızlıdır.
boneill

8

Java bayt kodunda 2 tür Switch ifadesi olduğunu okuduğumu hatırlıyorum. ('Java Performance Tuning'de olduğunu düşünüyorum Biri, çalıştırılacak kodun ofsetini bilmek için switch deyiminin tamsayı değerlerini kullanan çok hızlı bir uygulamadır. Bu, tüm tam sayıların ardışık ve iyi tanımlanmış bir aralıkta olmasını gerektirir. Bir Enum'un tüm değerlerini kullanmanın da o kategoriye gireceğini tahmin ediyorum.

Yine de diğer birçok postere katılıyorum ... Bu çok sıcak bir kod olmadığı sürece, bunun için endişelenmek için erken olabilir.


4
Sıcak kod yorumu için +1. Ana döngünüzde ise erken değil.
KingAndrew

Evet, javacswitch birkaç farklı yol uygular , bazıları diğerlerinden daha verimli. Genel olarak, verimlilik basit bir " ifmerdiven" ten daha kötü olmayacaktır , ancak yeterince varyasyon vardır (özellikle JITC ile) bundan çok daha kesin olmanın zor olduğu.
Hot Licks

8

Cliff Click'in 2009 Java One konuşmasında Modern Donanımda Bir Kilitlenme Kursu'na göre :

Günümüzde performans, bellek erişim kalıplarının hakimiyetindedir. Önbellek hakimiyeti kaçırır - bellek yeni disktir. [Slayt 65]

Tam slaytlarını buradan alabilirsiniz .

Cliff, CPU'nun kayıt yeniden adlandırma, dal tahmini ve spekülatif yürütme yapmasına rağmen, iki önbellek eksikliğinden dolayı bloke etmek zorunda kalmadan önce yalnızca 4 saat döngüsünde 7 işlemi başlatabildiğini gösteren bir örnek (Slayt 30'da bitirme) verir. Döndürülecek 300 saat döngüsü.

Bu yüzden programınızı hızlandırmak için bu tür küçük sorunlara değil, "SOAP → XML → DOM → SQL → ..." gibi gereksiz veri biçimi dönüşümleri yapıp yapmadığınız gibi daha büyük sorunlara bakmanız gerektiğini söylüyor. "bu" tüm verileri önbellekten geçirir ".


4

Benim testimde daha iyi performans Windows7'de ENUM> MAP> SWITCH> IF / ELSE IF'dir.

import java.util.HashMap;
import java.util.Map;

public class StringsInSwitch {
public static void main(String[] args) {
    String doSomething = null;


    //METHOD_1 : SWITCH
    long start = System.currentTimeMillis();
    for (int i = 0; i < 99999999; i++) {
        String input = "Hello World" + (i & 0xF);

        switch (input) {
        case "Hello World0":
            doSomething = "Hello World0";
            break;
        case "Hello World1":
            doSomething = "Hello World0";
            break;
        case "Hello World2":
            doSomething = "Hello World0";
            break;
        case "Hello World3":
            doSomething = "Hello World0";
            break;
        case "Hello World4":
            doSomething = "Hello World0";
            break;
        case "Hello World5":
            doSomething = "Hello World0";
            break;
        case "Hello World6":
            doSomething = "Hello World0";
            break;
        case "Hello World7":
            doSomething = "Hello World0";
            break;
        case "Hello World8":
            doSomething = "Hello World0";
            break;
        case "Hello World9":
            doSomething = "Hello World0";
            break;
        case "Hello World10":
            doSomething = "Hello World0";
            break;
        case "Hello World11":
            doSomething = "Hello World0";
            break;
        case "Hello World12":
            doSomething = "Hello World0";
            break;
        case "Hello World13":
            doSomething = "Hello World0";
            break;
        case "Hello World14":
            doSomething = "Hello World0";
            break;
        case "Hello World15":
            doSomething = "Hello World0";
            break;
        }
    }

    System.out.println("Time taken for String in Switch :"+ (System.currentTimeMillis() - start));




    //METHOD_2 : IF/ELSE IF
    start = System.currentTimeMillis();

    for (int i = 0; i < 99999999; i++) {
        String input = "Hello World" + (i & 0xF);

        if(input.equals("Hello World0")){
            doSomething = "Hello World0";
        } else if(input.equals("Hello World1")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World2")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World3")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World4")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World5")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World6")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World7")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World8")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World9")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World10")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World11")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World12")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World13")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World14")){
            doSomething = "Hello World0";

        } else if(input.equals("Hello World15")){
            doSomething = "Hello World0";

        }
    }
    System.out.println("Time taken for String in if/else if :"+ (System.currentTimeMillis() - start));









    //METHOD_3 : MAP
    //Create and build Map
    Map<String, ExecutableClass> map = new HashMap<String, ExecutableClass>();
    for (int i = 0; i <= 15; i++) {
        String input = "Hello World" + (i & 0xF);
        map.put(input, new ExecutableClass(){
                            public void execute(String doSomething){
                                doSomething = "Hello World0";
                            }
                        });
    }


    //Start test map
    start = System.currentTimeMillis();
    for (int i = 0; i < 99999999; i++) {
        String input = "Hello World" + (i & 0xF);
        map.get(input).execute(doSomething);
    }
    System.out.println("Time taken for String in Map :"+ (System.currentTimeMillis() - start));






    //METHOD_4 : ENUM (This doesn't use muliple string with space.)
    start = System.currentTimeMillis();
    for (int i = 0; i < 99999999; i++) {
        String input = "HW" + (i & 0xF);
        HelloWorld.valueOf(input).execute(doSomething);
    }
    System.out.println("Time taken for String in ENUM :"+ (System.currentTimeMillis() - start));


    }

}

interface ExecutableClass
{
    public void execute(String doSomething);
}



// Enum version
enum HelloWorld {
    HW0("Hello World0"), HW1("Hello World1"), HW2("Hello World2"), HW3(
            "Hello World3"), HW4("Hello World4"), HW5("Hello World5"), HW6(
            "Hello World6"), HW7("Hello World7"), HW8("Hello World8"), HW9(
            "Hello World9"), HW10("Hello World10"), HW11("Hello World11"), HW12(
            "Hello World12"), HW13("Hello World13"), HW14("Hello World4"), HW15(
            "Hello World15");

    private String name = null;

    private HelloWorld(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void execute(String doSomething){
        doSomething = "Hello World0";
    }

    public static HelloWorld fromString(String input) {
        for (HelloWorld hw : HelloWorld.values()) {
            if (input.equals(hw.getName())) {
                return hw;
            }
        }
        return null;
    }

}





//Enum version for betterment on coding format compare to interface ExecutableClass
enum HelloWorld1 {
    HW0("Hello World0") {   
        public void execute(String doSomething){
            doSomething = "Hello World0";
        }
    }, 
    HW1("Hello World1"){    
        public void execute(String doSomething){
            doSomething = "Hello World0";
        }
    };
    private String name = null;

    private HelloWorld1(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void execute(String doSomething){
    //  super call, nothing here
    }
}


/*
 * http://stackoverflow.com/questions/338206/why-cant-i-switch-on-a-string
 * https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.10
 * http://forums.xkcd.com/viewtopic.php?f=11&t=33524
 */ 

Time taken for String in Switch :3235 Time taken for String in if/else if :3143 Time taken for String in Map :4194 Time taken for String in ENUM :2866
Halil

@halil Bu kodun farklı ortamlarda nasıl çalıştığından emin değilim, ancak if / elseif'in Switch and Map'ten daha iyi olduğundan bahsetmiştiniz, if / elseif daha fazla kez karşılaştırma yapmak zorunda olduğu için ikna edemiyorum.
Kanagavelu Sugumar

2

Blokların çoğu switchve çoğu için if-then-else, performansla ilgili kayda değer veya önemli endişeler olduğunu hayal edemiyorum.

Ama switcholay şu: eğer bir blok kullanıyorsanız , onun kullanımı derleme zamanında bilinen bir dizi sabitten alınan bir değeri açtığınızı gösterir. Bu durumda, sabit-özel yöntemlerle switchkullanabiliyorsanız , gerçekten hiç ifadeler kullanmamalısınız enum.

Bir deyimle karşılaştırıldığında switch, enum, bakımı daha kolay olan daha iyi tür güvenliği ve kod sağlar. Numaralandırmalar, sabitler kümesine bir sabit eklenirse kodunuz yeni değer için sabite özgü bir yöntem sağlamadan derlenmeyecek şekilde tasarlanabilir. Öte yandan, casebir switchbloğa yeni bir blok eklemeyi unutmak bazen yalnızca bloğunuzu bir istisna atacak şekilde ayarlayacak kadar şanslıysanız, çalışma zamanında yakalanabilir.

Arasındaki performans switchbir ve enumsabit özgü yöntemiyle önemli ölçüde farklı olmamalıdır, ama ikincisi daha okunabilir, daha güvenli ve bakımı kolaydır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.