V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
cpalead
V2EX  ›  Java

Java switch 为什么比 for 循环快?

  •  
  •   cpalead · 2022-08-03 15:18:51 +08:00 · 3993 次点击
    这是一个创建于 873 天前的主题,其中的信息可能已经有所发展或是发生改变。
    测试代码
    for (int j = 0; j < 30; j++) {
    long current = System.currentTimeMillis();
    for (long i = 0; i < 1000000000; i++) {
    getPriority(10);
    }
    long first = System.currentTimeMillis();
    for (long i = 0; i < 1000000000; i++) {
    getPriority1(10);
    }
    long second = System.currentTimeMillis();
    System.out.println("first= " + (first - current) + " second= " + (second - first));
    }

    原方法
    private static int getPriority(int cameraType) {
    switch (cameraType) {
    case 4:
    return CameraType.CAMERA_1.getPriority();
    case 5:
    return CameraType.CAMERA_2.getPriority();
    case 6:
    return CameraType.CAMERA_3.getPriority();
    case 7:
    return CameraType.CAMERA_4.getPriority();
    case 8:
    return CameraType.CAMERA_5.getPriority();
    case 9:
    return CameraType.CAMERA_6.getPriority();
    case 10:
    return CameraType.CAMERA_7.getPriority();
    default:
    return -1;
    }
    }

    private static int getPriority1(int cameraType) {
    for (CameraType cameraType1 : CameraType.values()) {
    if (cameraType1.getCameraType() == cameraType) {
    return cameraType1.getPriority();
    }
    }
    return -1;
    }

    所用枚举类型
    public enum CameraType {
    CAMERA_1("相机 1", 4, 0), CAMERA_2("相机 2", 5, 2), CAMERA_3("相机 3", 6, 3), CAMERA_4("相机 4", 7, 4),
    CAMERA_5("相机 5", 8, 5), CAMERA_6("相机 6", 9, 6), CAMERA_7("相机 7", 10, 1);
    private final String mCameraName;
    private final int mCameraType;
    private final int mPriority;

    CameraType(String name, int cameraType, int priority) {
    mCameraName = name;
    mCameraType = cameraType;
    mPriority = priority;
    }

    public String getCameraName() {
    return mCameraName;
    }

    public int getCameraType() {
    return mCameraType;
    }

    public int getPriority() {
    return mPriority;
    }
    }

    结果
    first= 500 second= 8683
    first= 536 second= 6801
    first= 520 second= 7329
    first= 639 second= 7556
    first= 562 second= 7279
    first= 592 second= 7458

    个人感觉,switch 不还是从最开始 case 匹配到最终符合的 case 然后返回的吗?那不就跟 for 循环的匹配办法一样吗!为什么两者速度差一个数量级
    17 条回复    2022-08-04 10:57:22 +08:00
    mxT52CRuqR6o5
        1
    mxT52CRuqR6o5  
       2022-08-03 15:21:19 +08:00
    你这里的 switch 可以优化成查表
    xujinkai
        2
    xujinkai  
       2022-08-03 15:21:53 +08:00 via Android
    我记得 switch 会优化成数组取下表,这样就不用一个一个匹配了
    Leviathann
        3
    Leviathann  
       2022-08-03 15:27:39 +08:00
    不要用测量前后时间的方式测计算密集的代码
    lmshl
        4
    lmshl  
       2022-08-03 15:38:00 +08:00
    不考虑 JIT 介入的前提下:

    # for of
    1. 循环 Array 会带来检查数组边界的开销,每次访问元素都要 check condition
    2. getCameraType 是函数,函数需要跳转过去,再跳转回来
    3. 发生了额外的三次内存访问,a) 堆获取枚举静态数组, b) 根据数组下标访问枚举对象引用, c) 根据对象引用访问堆内存地址取属性

    # switch
    而 switch 只有简简单单的 compare 和 jump ,高下立判

    不是很懂 jvm ,仅根据操作系统印象胡诌几句
    786375312123
        5
    786375312123  
       2022-08-03 16:07:28 +08:00
    一个是 map 查表,一个是 array 遍历
    xtreme1
        6
    xtreme1  
       2022-08-03 16:14:52 +08:00   ❤️ 1
    cpalead
        7
    cpalead  
    OP
       2022-08-03 17:04:25 +08:00
    @xtreme1 我自己反编译过,跟你这个是类似,能看出啥?原因是啥?
    cpalead
        8
    cpalead  
    OP
       2022-08-03 17:06:51 +08:00
    @Leviathann 为什么?有什么问题,不这样计算时间,怎么算时间
    aguesuka
        9
    aguesuka  
       2022-08-03 17:55:06 +08:00
    @cpalead 用 benchmark, 至于为什么可以谷歌一下
    zmal
        10
    zmal  
       2022-08-03 18:09:22 +08:00
    反编译后的字节码很清晰了:编译优化成查表后把枚举类进行了标量替换,tableswitch 里的数字应该是直接放在方法栈里,避免了去堆里访问对象,所以快了很多。
    L4Linux
        11
    L4Linux  
       2022-08-03 18:10:19 +08:00 via Android
    Java 的性能不是这么比的
    L4Linux
        12
    L4Linux  
       2022-08-03 18:12:17 +08:00 via Android
    还有,挨个比数肯定慢啊。。。
    shyling
        13
    shyling  
       2022-08-03 18:15:26 +08:00
    可以研究下什么是 tableswitch
    superrichman
        14
    superrichman  
       2022-08-03 18:19:34 +08:00 via Android
    知道门牌号直接上门查水表比一家一户敲门快 🐶
    cpalead
        15
    cpalead  
    OP
       2022-08-03 18:44:04 +08:00   ❤️ 2
    已经研究明白了
    如果 case 的值是连续的,那么就会优化成一个数组,效率和原理跟 hashmap 或者数组下标访问的原理一样,那就是 o(1)的时间,如果不是连续的,就会使用二分查找去匹配,那么时间复杂度就是 o(log(n))
    而 for 的时间复杂度就是 n ,所以不如 switch 快!
    zagfai
        16
    zagfai  
       2022-08-04 00:35:01 +08:00
    @cpalead hash 是 o(alpha(n)), 不是 lgn
    ibinary
        17
    ibinary  
       2022-08-04 10:57:22 +08:00
    遇到语法不懂得层面,就去 C++ 看看反汇编. 语法一样.看看人家底层怎么做得. switch 优化方式有很多种. 可以优化为 if 可以优化为表. 可以优化为树. 可以优化为二级查表.
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5523 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 08:21 · PVG 16:21 · LAX 00:21 · JFK 03:21
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.