Java中if / else与switch语句的相对性能差异是什么?


122

担心我的Web应用程序的性能,我想知道“ if / else”或switch语句在性能方面哪个更好?


6
您是否有任何理由认为两个结构不会生成相同的字节码?
Pascal Cuoq 2010年

2
@Pascal:有可能通过优化使用查表窗口,而不是一个列表的完成if等等
jldupont

18
“过早的优化是万恶之源”-唐纳德·克努斯
missingfaktor 2010年

104
虽然这肯定是过早的优化,但“无情地坚持不加考虑地使用报价是我们今天需要一台高端多核计算机来显示合理响应的GUI的原因”-我。
劳伦斯·多尔

2
克努思的头脑很精准。请注意限定词“过早”。优化是一个完全有效的关注点。就是说,服务器受IO限制,网络和磁盘I / O的瓶颈比服务器中发生的任何其他事情都要严重几个数量级。
2011年

Answers:


108

那是微优化和过早的优化,这是邪恶的。相当担心有关代码的可读性和可维护性。如果有两个以上的if/else块粘合在一起或者其大小不可预测,那么您可能会考虑使用一个switch声明。

另外,您也可以使用Polymorphism。首先创建一些接口:

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

并掌握其中的所有实现Map。您可以静态或动态地执行此操作:

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

最后,将if/else或替换为switch类似的内容(将诸如空指针之类的琐碎检查放在一边):

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

可能if/else或慢一些switch,但是代码的可维护性至少要好得多。

在谈论Web应用程序时,您可以利用HttpServletRequest#getPathInfo()as作为操作键(最终编写更多代码以将pathinfo的最后部分循环分离,直到找到操作为止)。您可以在这里找到类似的答案:

如果您总体上担心Java EE Web应用程序的性能,那么您可能会发现本文也很有用。在其他方面,与仅对原始Java代码进行(微)优化相比,可以带来更多的性能提升。


1
或考虑多态性代替
JK。

确实,如果“不可预测”的if / else块数量较多,则建议这样做。
BalusC 2010年

73
我不是很快就将所有早期优化都视为“邪恶”。过于激进是愚蠢的,但是当面对可读性相当的结构时,选择已知性能更好的结构是一个适当的决定。
Brian Knoblauch

8
与tablewitsch指令相比,HashMap查找版本的速度很容易降低10倍。我不会称其为“ microslower”!
x4u 2010年

7
我对实际上了解带有switch语句的一般情况下Java的内部工作感兴趣-我对有人是否认为这与过分优先考虑早期优化无关。话虽这么说,我绝对不知道为什么这个答案这么高,为什么它是被接受的答案……这并不能回答最初的问题。
searchengine27 2015年

125

我完全同意避免过早优化的观点。

但是的确,Java VM具有可以用于switch()的特殊字节码。

请参阅WM规范lookupswitchtableswitch

因此,如果代码是性能CPU图形的一部分,则可能会提高性能。


60
我不知道为什么这个评论没有得到更高的评价:它是所有这些中信息最丰富的。我的意思是:我们都已经知道过早的优化是不好的,所以不需要第1000次进行解释。
Folkert van Heusden

5
+1从stackoverflow.com/a/15621602/89818起,似乎确实可以实现性能提升,如果使用18+种情况,您应该会看到一个优势。
CAW

52

if / else或switch不会成为性能问题的根源。如果您遇到性能问题,则应首先进行性能分析,以确定慢点在哪里。过早的优化是万恶之源!

但是,可以通过Java编译器优化来讨论switch与if / else的相对性能。首先请注意,在Java中,switch语句在非常有限的域(整数)上运行。通常,您可以按以下方式查看switch语句:

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

其中c_0,,c_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语句系列的效率完全相同。α和β的精确值取决于许多因素,并由编译器的代码优化模块确定。

最后,当然,如果of的域<condition>不是整数,则switch语句完全没有用。


+1。在网络I / O上花费的时间很容易使这个特定问题黯然失色。
亚当·佩恩特

3
应该注意的是,开关不仅仅适用于整数。来自Java教程:“开关适用于byte,short,char和int原语数据类型。它也适用于枚举类型(在Enum Types中讨论),String类以及一些包装某些原始类型的特殊类。 :字符,字节,短整数和整数(在数字和字符串中讨论)。” 对String的支持是最近添加的;已在Java 7中添加。docs.oracle.com/javase
tutorial/

1
@jhonFeminella您能否将Swtich中Java7 String的BIG O概念效果与if / else if ..中的String进行比较?
2014年

更精确地讲,javac 8赋予时间复杂度3于空间复杂度的权重:stackoverflow.com/a/31032054/895245
Ciro Santilli郝海东冠状病六四事件法轮功2015年

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

非常感谢您的更新。我的意思是,它们相差一个数量级-当然可以。您确定编译器不仅将switches优化了吗?
DerMike

@DerMike我不记得我是怎么得到旧结果的。我今天变得非常不同。但是请自己尝试,让我知道结果如何。
Bitterblue 2014年

1
当我在笔记本电脑上运行它时; 切换时间需要:3585,如果/其他时间需要:3458因此,如果/其他更好:)或不更糟
halil 2015年

1
测试中的主要成本是随机数的产生。我修改了测试以在循环之前生成随机数,并使用temp值将其反馈给r。切换速度几乎是if-else链的两倍。
骨病

8

我记得读过Java字节码中有2种Switch语句。(我认为这是在“ Java Performance Tuning”中,这是一种非常快速的实现,它使用switch语句的整数值来知道要执行的代码的偏移量。这将要求所有整数必须连续且在定义良好的范围内我猜测使用Enum的所有值也将属于该类别。

不过,我也同意其他许多发帖人的观点...担心此为时过早,除非这是非常非常热的代码。


4
+1为热门代码注释。如果它在您的主循环中,则还不成熟。
KingAndrew 2014年

是的,javac实现switch了几种不同的方法,其中一些方法比其他方法更有效。通常,效率不会比简单的“ if阶梯”差,但是存在足够的变化(尤其是JITC),很难做到比这更精确。
Hot Licks

8

根据Cliff Click在他的2009年Java One讲座“现代硬件崩溃课程”中的说法:

如今,性能主要由内存访问模式决定。缓存未命中占主导地位–内存是新磁盘。[幻灯片65]

你可以在这里找到他的完整幻灯片。

Cliff给出了一个示例(在幻灯片30上完成),该示例显示即使CPU执行寄存器重新命名,分支预测和推测性执行,由于两个高速缓存未命中,这也只能在4个时钟周期内启动7个操作,然后才需要阻塞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
halil,2015年

@halil我不确定该代码在不同的环境下如何工作,但是您已经提到if / elseif比Switch and Map更好,我无法说服if / elseif必须执行更多次等于比较。
卡纳加维卢·苏古玛

2

对于大多数switchif-then-else块,我不能想象有任何明显的或显著的性能相关的问题。

事情是这样的:如果您使用的是switch块,那么它的用法暗示您正在打开一个在编译时已知的一组常量中获取的值。在这种情况下,switch如果可以使用enum特定于常量的方法,则实际上根本不应该使用语句。

switch语句相比,枚举提供了更好的类型安全性和易于维护的代码。可以设计枚举,以便在将常量添加到一组常量时,如果不为新值提供特定于常量的方法,则代码将无法编译。另一方面,有时只有在运行时才有幸忘记添加新caseswitch块,如果您足够幸运地设置了块以引发异常。

特定switchenum常数的方法之间的性能应该没有显着差异,但是后者更具可读性,更安全且更易于维护。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.