ChatGPT解决这个技术问题 Extra ChatGPT

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

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

您是否有任何理由认为不会为这两个构造生成相同的字节码?
@Pascal:可能会通过使用表查找而不是 if 列表等来进行优化。
“过早的优化是万恶之源”——Donald Knuth
虽然这绝对是过早的优化,但“盲目地坚持一个严重断章取义的报价是我们今天需要一台高端多核计算机来显示合理响应的 GUI 的原因”——我。
Knuth 有一个精确的头脑。请注意限定词“过早的”。优化是一个完全有效的问题。也就是说,服务器受 IO 限制,网络和磁盘 I/O 的瓶颈比您在服务器中进行的任何其他事情都要重要几个数量级。

S
Steffen

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

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

请参阅 WM Speclookupswitchtableswitch

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


我想知道为什么这条评论没有得到更高的评价:它是所有评论中信息最丰富的。我的意思是:我们都已经知道过早优化是不好的等等,不需要第 1000 次解释。
+1 从 stackoverflow.com/a/15621602/89818 开始,性能提升似乎确实存在,如果您使用 18 个以上的案例,您应该会看到优势。
C
Community

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

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

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() 作为操作键(最终编写更多代码以将 pathinfo 的最后一部分拆分为循环,直到找到操作)。你可以在这里找到类似的答案:

使用自定义的面向 Servlet 的框架,太多的 servlet,这是一个问题吗

Java 前端控制器

如果您总体上担心 Java EE Web 应用程序的性能,那么您可能会发现 this article 也很有用。与仅(微)优化原始 Java 代码相比,还有其他领域可以提供更多性能提升。


或者考虑多态性
在 if/else 块的“不可预测”数量的情况下,确实更推荐这样做。
我不会很快将所有早期优化视为“邪恶”。过于激进是愚蠢的,但是当面对可读性相当的结构时,选择一个已知性能更好的结构是一个合适的决定。
与 tableswitsch 指令相比,HashMap 查找版本很容易慢 10 倍。我不会称之为“微慢”!
我有兴趣在 switch 语句的一般情况下实际了解 Java 的内部工作原理——我不关心是否有人认为这与过度优先考虑早期优化有关。话虽这么说,我完全不知道为什么这个答案被如此赞成以及为什么它是公认的答案……这对回答最初的问题没有任何帮助。
J
John Feminella

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(ci) + 1 - min(ci)) / n > α,其中 0 < k < α < 1,其中 k 大于某个经验值,跳转可以生成表,效率很高。

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

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

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


+1。很有可能花在网络 I/O 上的时间很容易掩盖这个特定问题。
应该注意的是,开关不仅仅是整数。来自 Java 教程:“开关适用于 byte、short、char 和 int 原始数据类型。它还适用于枚举类型(在 Enum Types 中讨论)、String 类和一些包装某些原始类型的特殊类:字符、字节、短整数和整数(在数字和字符串中讨论)。”对 String 的支持是最近添加的;在 Java 7 中添加。docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html
@jhonFeminella您能否将Swtich中Java7 String的BIG O概念效果与if / else if ..中的String进行比较?
更准确地说,javac 8 对时间复杂度的权重超过了空间复杂度:stackoverflow.com/a/31032054/895245
C
Community

使用开关!

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


您能否(有时)详细说明一下您是如何进行基准测试的?
非常感谢您的更新。我的意思是,它们相差一个数量级——这当然是可能的。您确定编译器不只是优化switches吗?
@DerMike我不记得我是如何得到旧结果的。我今天变得非常不同。但是你自己试试,让我知道结果如何。
当我在笔记本电脑上运行它时;需要切换时间:3585,if/else 需要时间:3458 所以 if/else 更好:) 或不差
测试中的主要成本是随机数生成。我修改了测试以在循环之前生成随机数,并使用 temp 值反馈给 r。切换的速度几乎是 if-else 链的两倍。
m
malaverdiere

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

不过,我同意许多其他发帖者的观点……担心这一点可能为时过早,除非这是非常热门的代码。


+1 用于热代码注释。如果它在你的主循环中,它并不为时过早。
是的,javac 实现了 switch 几种不同的方式,其中一些方式比其他方式更有效。一般来说,效率不会比直接的“if阶梯”差,但是有足够的变化(尤其是JITC),很难比这更精确。
J
Jim Ferrans

根据 Cliff Click 在他的 2009 Java One 演讲 A Crash Course in Modern Hardware 中:

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

您可以获取他的完整幻灯片here

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

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


K
Kanagavelu Sugumar

在我的测试中,更好的性能是 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 我不确定这段代码如何在不同的环境中工作,但你提到 if/elseif 比 Switch 和 Map 更好,我无法说服,因为 if/elseif 必须执行更多次数等于比较。
s
scottb

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

但事情是这样的:如果您使用的是 switch 块,那么它的用途表明您正在打开一个取自编译时已知的一组常量的值。在这种情况下,如果您可以将 enum 与特定于常量的方法一起使用,那么您真的根本不应该使用 switch 语句。

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

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


T
Talles

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

测试(c++17)
1 - 如果分组
2 - 如果顺序
3 - 转到数组
4 - 切换案例 - 跳转表
https://onlinegdb.com/Su7HNEBeG