Javaのif / elseとswitchステートメントの相対的なパフォーマンスの違いは何ですか?


122

私のWebアプリケーションのパフォーマンスが気になるのですが、パフォーマンスに関して「if / else」またはswitchステートメントのどちらが優れているのでしょうか。


6
2つの構成要素に対して同じバイトコードが生成されないと考える理由はありますか?
Pascal Cuoq、2010年

2
@Pascal:最適化は、リストの代わりに、テーブルルックアップを使用することによって、そこで行われるかもしれないifなど
jldupont

18
「時期尚早な最適化がすべての悪の根源」
-Donald

104
これは間違いなく時期尚早の最適化ですが、「コンテキストから外された引用への不注意な順守が、今日、適度に応答性の高いGUIを表示するためだけにハイエンドマルチコアコンピューターを必要とする理由です」-私。
ローレンスドル2010年

2
クヌースは正確な心を持っています。修飾子「時期尚早」に注意してください。最適化は完全に有効な問題です。つまり、サーバーはIOバウンドであり、ネットワークとディスクI / Oのボトルネックは、サーバーで実行している他の何よりも桁違いに重要です。
alphazero 2011

回答:


108

それは悪であるミクロの最適化と時期尚早の最適化です。むしろ、問題のコードの可読性と保守性について心配します。3つ以上のif/elseブロックが接着されている場合、またはそのサイズが予測できない場合は、switchステートメントを検討することをお勧めします。

または、ポリモーフィズムを取得することもできます。まず、いくつかのインターフェースを作成します。

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

そして、いくつかのすべての実装を把握してくださいMap。これは静的にも動的にも実行できます。

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

最後にif/elseまたはswitchを次のようなものに置き換えます(ヌルポインタのようなささいなチェックは残しておきます)。

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

それは可能性がある microslowerよりもif/elseswitchが、コードは、少なくともはるかに良い保守しています。

Webアプリケーションについて話しているときに、HttpServletRequest#getPathInfo()アクションキーとして使用できます(最終的には、アクションが見つかるまで、ループ内でpathinfoの最後の部分を分割するためのコードをさらに記述します)。あなたはここで同様の答えを見つけることができます:

Java EE Webアプリケーションのパフォーマンス全般について心配している場合は、この記事も役立つかもしれません。生のJavaコードのみを(マイクロ)最適化するよりもはるかに高いパフォーマンス向上をもたらす他の領域があります。


1
または、代わりにポリモーフィズムを検討してください
jk。

「予測できない」量のif / elseブロックの場合は、これが実際に推奨されます。
BalusC 2010年

73
私は初期の最適化をすべて「悪」として却下するのは簡単ではありません。あまりにも攻撃的であるのはばかげていますが、同等の読みやすさのコンストラクトに直面したとき、より優れたパフォーマンスを発揮することがわかっているものを選択することは適切な決定です。
Brian Knoblauch、2010年

8
HashMapルックアップバージョンは、tableswitsch命令に比べて10倍遅くなる可能性があります。私はこれを「マイクロスロー」とは呼びません!
x4u

7
私は、switchステートメントを使用した一般的なケースでのJavaの内部動作を実際に知ることに興味があります。これが初期の最適化の優先順位付けに関連していると誰かが考えるかどうかには興味がありません。とはいえ、なぜこの回答がそれほど多く賛成され、なぜそれが受け入れられた回答であるのかはまったくわかりません...これは最初の質問に答えることはできません。
searchengine27

125

時期尚早な最適化は避けるべきものであるという意見に完全に同意します。

しかし、Java VMにswitch()に使用できる特別なバイトコードがあることは事実です。

WM仕様を参照(lookupswitchおよびtableswitch

したがって、コードがパフォーマンスCPUグラフの一部である場合、パフォーマンスが向上する可能性があります。


60
このコメントの評価が高くないのはなぜでしょうか。コメントの中で最も有益です。つまり、時期尚早の最適化が悪いなどのことは誰もがすでに知っているので、1000回目に説明する必要はありません。
Folkert van Heusden、2012

5
+1 stackoverflow.com/a/15621602/89818の時点では、パフォーマンスの向上が実際に見られ、18以上のケースを使用すると利点が得られるはずです。
2014年

52

if / elseまたはスイッチがパフォーマンスの問題の原因となることはほとんどありません。パフォーマンスの問題がある場合は、まずパフォーマンスプロファイリング分析を行って、スロースポットの場所を特定する必要があります。時期尚早の最適化はすべての悪の根源です!

それにもかかわらず、Javaコンパイラの最適化を使用して、switchとif / elseの相対的なパフォーマンスについて話すことができます。Javaでは、switchステートメントは非常に限られたドメイン(整数)で動作することに注意してください。一般に、switchステートメントは次のように表示できます。

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

ここc_0c_1、、、 ...、およびc_N整数は、switchステートメントのターゲットであり<condition>、整数式に解決する必要があります。

  • このセットが「密」である場合、つまり(max(c i)+ 1-min(c i))/ n>αであり、0 <k <α<1でありk、ある経験値aよりも大きい場合非常に効率的なジャンプテーブルを生成できます。

  • このセットが非常に密ではないが、n> =βの場合、二分探索木はO(2 * log(n))でターゲットを見つけることができますが、それでもやはり効率的です。

他のすべてのケースでは、switchステートメントは同等の一連のif / elseステートメントとまったく同じくらい効率的です。αとβの正確な値は、いくつかの要因に依存し、コンパイラーのコード最適化モジュールによって決定されます。

最後に、もちろん、のドメインが<condition>整数でない場合、switchステートメントはまったく役に立ちません。


+1。ネットワークI / Oに費やされた時間がこの特定の問題を簡単に覆い隠してしまう可能性は十分にあります。
Adam Paynter、2010年

3
スイッチはint以外のものでも機能することに注意してください。Javaチュートリアルから:「スイッチは、byte、short、char、およびintプリミティブデータ型で動作します。列挙型(列挙型で説明)、Stringクラス、および特定のプリミティブ型をラップするいくつかの特別なクラスでも動作します:文字、バイト、ショート、整数(数値と文字列で説明)。 " 文字列のサポートは、最近追加されました。Java 7で追加。docs.oracle.com
javase

1
@jhonFeminella if / else ifでの文字列と比較したSwtichでのJava7文字列のBIG O表記効果を比較してください。
Kanagavelu Sugumar 2014年

より正確には、javac 8はスペースの複雑さよりも時間の複雑さを3に重み付けします:stackoverflow.com/a/31032054/895245
Ciro Santilli郝海东冠状病六四事件法轮機能

11

スイッチを使用してください!

if-else-blocksを維持するのは嫌いです!テストを受ける:

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

ベンチマーク用の私のC#標準コード


これをどのようにベンチマークしたかについて、(いつか)少し詳しく説明してもらえますか?
DerMike 2014

更新ありがとうございます。つまり、それらは1桁違います-もちろん可能です。コンパイラが単にswitchesを最適化しなかったと確信していますか?
DerMike 2014

@DerMikeどうやって古い結果を出したか覚えていません。今日はとても変わった。しかし、実際に試してみて、どうなるか教えてください。
Bitterblue 2014

1
ラップトップで実行すると、必要な切り替え時間:3585、必要な場合:その他の時間:3458だから、必要な場合は、より良い:)またはより悪くない
ハリル2015

1
テストの主なコストは、乱数の生成です。ループの前に乱数を生成するようにテストを変更し、temp値を使用してrにフィードバックしました。この場合、スイッチはif-elseチェーンのほぼ2倍の速度になります。
boneill

8

Javaバイトコードには2種類のSwitchステートメントがあることを読んだことを覚えています。(「Javaパフォーマンスチューニング」にあったと思います。1つは、実行するコードのオフセットを知るためにswitchステートメントの整数値を使用する非常に高速な実装です。これには、すべての整数が連続していて、明確に定義された範囲内である必要があります。 Enumのすべての値を使用すると、そのカテゴリにも当てはまると思います。

私は他の多くのポスターにも同意します...これが非常にホットなコードでない限り、これを心配するのは時期尚早かもしれません。


4
ホットコードコメントの+1。メインループにある場合は、時期尚早ではありません。
KingAndrew 2014

はい、javacswitchいくつかの異なる方法を実装しています。一般に、効率は単純な「ifはしご」よりも悪くはありませんが、十分なバリエーションがあり(特にJITCの場合)、それよりもはるかに正確にするのは困難です。
ホットリックス2014年

8

2009年のJava OneのCliff Clickによると、モダンハードウェアのクラッシュコース

今日、パフォーマンスはメモリアクセスのパターンによって支配されています。キャッシュミスが優勢–メモリは新しいディスクです。[スライド65]

ここで彼の完全なスライドを入手できます

クリフの例(スライド30で終了)は、CPUがレジスタの名前変更、分岐予測、および投機的実行を行っている場合でも、4つのクロックサイクルで7つの操作しか開始できず、2つのキャッシュミスが原因でブロックする必要があることを示しています。 戻るには300クロックサイクル。

したがって、彼はプログラムを高速化するために、この種の小さな問題ではなく、「SOAP→XML→DOM→SQL→…の変換など、不要なデータ形式の変換を行っているかどうかなど、大きな問題については考えないでください。 「すべてのデータをキャッシュに渡します」。


4

私のテストでは、より良いパフォーマンスは、Windows7のENUM> MAP> SWITCH> IF / ELSE IFです。

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
ハリル2015

@halil私はこのコードが異なる環境でどのように機能するかわかりませんが、if / elseifが比較と等しい回数より多く実行する必要があるため、if / elseifがSwitchとMapよりも優れていると説明しました。
Kanagavelu Sugumar

2

ほとんどの場合switch、ほとんどのif-then-elseブロック、私はいかなる感知または重大なパフォーマンス関連の問題があることを想像することはできません。

しかし、これが問題です。switchブロックを使用している場合、その非常に優れた使用方法は、コンパイル時に既知の定数のセットから取得した値をオンにすることを示唆しています。この場合、switchもし使用できるなら、ステートメントをまったく使用すべきではありませんenum定数固有のメソッドで。

switchステートメントと比較して、列挙型はより優れた型の安全性と維持しやすいコードを提供します。列挙型は、定数が定数のセットに追加された場合、新しい値に対して定数固有のメソッドを提供しないとコードがコンパイルされないように設計できます。一方、新しいを追加し忘れcaseswitchあなたがしている幸運十分に例外をスローするようにあなたのブロックを設定しているしている場合、時には唯一の実行時にキャッチすることができますブロック。

switchenum定数固有のメソッドの間のパフォーマンスは大幅に異なるべきではありませんが、後者の方が読みやすく、安全で、保守が容易です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.