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

What is the relative performance difference of if/else versus switch statement in Java?

担心我的Web应用程序的性能,我想知道"if/else"或switch语句中哪一个性能更好?


我完全同意这样的观点:过早的优化是应该避免的。

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

参见WM规范(查找开关和表开关)

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


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

或者,您也可以获取多态性。首先创建一些接口:

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

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

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

最后,用这样的方法替换if/elseswitch(将空指针之类的小检查放在一边):

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

它可能比if/elseswitch低很多,但代码的可维护性至少要好得多。

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

  • 使用面向定制servlet的框架,servlet太多,这是一个问题吗?
  • Java前端控制器

如果你担心JavaEE Web应用程序的性能,那么你可能会发现这篇文章也是有用的。还有一些区域比只优化微生成器Java代码更能提高性能。


一个if/else或一个开关极不可能成为性能问题的根源。如果您有性能问题,您应该首先进行性能分析分析,以确定慢点在哪里。过早的优化是万恶之源!

然而,有可能讨论开关与V/R的相对性能,以及Java编译器的优化。首先请注意,在Java中,开关语句在一个非常有限的域——整数上运行。通常,您可以按如下方式查看switch语句:

1
2
3
4
5
6
7
switch (<condition>) {
   case c_0: ...
   case c_1: ...
   ...
   case c_n: ...
   default: ...
}

其中c_0c_1……、c_N是switch语句的目标整数,必须解析为整数表达式。

  • 如果这个集合是"密集的"——也就是说,(max(ci)+1-min(ci)/n>&alpha;,其中0k大于某个经验值,则可以生成一个跳跃表,这是非常有效的。

  • 如果这个集合不是很密集,但是n>=&beta;,一个二进制搜索树可以在o(2*log(n))中找到目标,这也是有效的。

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

最后,当然,如果的域不是整数,那么一个开关这句话完全没用。


使用开关!

我不想再坚持下去了!进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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标准基准代码


我记得读过Java字节码中有2种开关语句。(我认为它是在Java性能调整中的一个),它是一个非常快的实现,它使用Switter语句的整数值来知道要执行的代码的偏移量。这将要求所有整数都是连续的,并且在一个定义良好的范围内。我猜想使用枚举的所有值也将属于该类别。

不过,我同意其他许多海报…除非这是非常热门的代码,否则担心这一点可能还为时过早。


根据他的2009 Java语言中的克里夫点击,一个现代硬件的速成课程:

Today, performance is dominated by patterns of memory access. Cache misses dominate – memory is the new disk. [Slide 65]

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

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

所以他说,为了加快程序的速度,你不应该考虑这类小问题,而应该考虑更大的问题,比如是否要进行不必要的数据格式转换,比如转换"soap→xml→dom→sql→……",它"将所有数据通过缓存传递"。


在我的测试中,更好的性能是Enum>Map>Switch>if/else if in windows7。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
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
 */


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

但问题是:如果您使用的是switch块,它的使用表明您正在打开一个从编译时已知的一组常量中获取的值。在这种情况下,如果可以使用具有常量特定方法的enum,则根本不应该使用switch语句。

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

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