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

时间:2010-01-18 14:09:30

标签: java performance switch-statement if-statement

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

9 个答案:

答案 0 :(得分:112)

我完全同意过早优化是需要避免的。

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

请参阅WM Speclookupswitchtableswitch

如果代码是性能CPU图的一部分,那么可能会有一些性能提升。

答案 1 :(得分:105)

这是微观优化和过早优化,这是邪恶的。而是担心有问题的代码的可读性和可维护性。如果有两个以上的if/else块粘在一起或其大小不可预测,那么您可能会高度考虑switch语句。

或者,您也可以使用 Polymorphism 。首先创建一些界面:

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

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

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

最后用这样的内容替换if/elseswitch(将无效指针放在一边进行琐碎的检查):

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

可能if/elseswitch微不足道,但代码至少可以更好地维护。

当您谈论Web应用程序时,您可以使用HttpServletRequest#getPathInfo()作为操作键(最终编写一些代码以将循环的最后一部分分开循环直到找到操作)。你可以在这里找到类似的答案:

如果您一般担心Java EE Web应用程序的性能,那么您可能会发现this article也很有用。除了(微)优化原始Java代码之外,还有其他领域提供更多性能增益。

答案 2 :(得分:49)

if / else或switch很可能成为你性能问题的根源。如果您遇到性能问题,应首先进行性能分析分析,以确定慢点的位置。过早的优化是万恶之源!

尽管如此,可以通过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&gt; ; α,其中0 < k&lt; α<&lt; 1,k大于某个经验值,可以生成跳转表,效率很高。

  • 如果此集合不是非常密集,但是n> =β,则二叉搜索树可以在O(2 * log(n))中找到目标,这仍然是有效的。

对于所有其他情况,switch语句与等效的if / else语句系列完全一样有效。 α和β的精确值取决于许多因素,并由编译器的代码优化模块确定。

最后,当然,如果<condition>的域不是整数,那么就是一个开关 声明完全没用。

答案 3 :(得分:9)

使用开关!

我讨厌维护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));
    }
}

My C# standard code for benchmarking

答案 4 :(得分:8)

根据Cliff的说法点击他的2009 Java One talk A Crash Course in Modern Hardware

  

今天,性能主要受内存访问模式的影响。缓存未命中占主导地位 - 内存是新磁盘。 [幻灯片65]

你可以获得他的完整幻灯片here

Cliff给出了一个例子(在幻灯片30上完成),表明即使CPU正在执行寄存器重命名,分支预测和推测执行,它也只能在4个时钟周期内启动7个操作,然后由于两个缓存而不得不阻塞错过了 300 时钟周期返回。

所以他说要加速你的程序你不应该考虑这种小问题,而是考虑更大的问题,例如你是否正在进行不必要的数据格式转换,例如转换“SOAP→XML→DOM→ SQL→...“”将所有数据传递通过缓存“。

答案 5 :(得分:8)

我记得在Java字节码中有两种Switch语句。 (我认为它是'Java性能调优'一个是一个非常快速的实现,它使用switch语句的整数值来知道要执行的代码的偏移量。这将要求所有整数是连续的并且在明确定义的范围内我猜测使用Enum的所有值也属于那个类别。

我同意很多其他海报......但担心这个问题可能为时过早,除非这是非常热门的代码。

答案 6 :(得分:4)

在我的测试中,更好的表现是 ENUM&gt; MAP&gt; SWITCH&gt;在Windows7中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
 */ 

答案 7 :(得分:2)

对于大多数switch和大多数if-then-else块,我无法想象有任何明显或重大的与性能相关的问题。

但事情就是这样:如果你使用的是switch块,它的用途就是建议你在编译时切换一组常量中的值。在这种情况下,如果您可以使用具有常量特定方法的switch,那么您根本不应该使用enum语句。

switch语句相比,枚举提供了更好的类型安全性和更易于维护的代码。可以设计枚举,以便如果将常量添加到常量集中,则在不为新值提供常量特定方法的情况下,代码将无法编译。另一方面,忘记将新case添加到switch块有时只能在运行时被捕获,如果您足够幸运,可以将块设置为抛出异常。

switchenum常量特定方法之间的性能不应有显着差异,但后者更易读,更安全,更易于维护。

答案 8 :(得分:0)

以下链接有很好的解释:
https://www.geeksforgeeks.org/switch-vs-else/

测试(c++17)
1 - 如果分组
2 - 如果是连续的
3 - 转到数组
4 - 开关盒 - 跳台
https://onlinegdb.com/Su7HNEBeG