ITPub博客

首页 > 移动开发 > ANDROID > 从if-else和switch,聊聊“八股”的作用

从if-else和switch,聊聊“八股”的作用

原创 ANDROID 作者:山有木xi 时间:2025-09-15 17:27:51 0 删除 编辑

最近在看一些八股文,有涉及到 Java JVM的,也有安卓的底层原理,例如app启动流程,很多在实际业务中没有接触过,也或许是自己没有意识到,所以更多是死记硬背,所以我其实并不推崇八股文。

在实际工作中,完成各种需求其实都有很多种写法,哪怕是最简单的判断也有 if-else和switch(when)可以选择,从来也没想过也可能会带来深层次的影响。

单独的 if处理

最近在写的项目中,突然发现自己的一种写法,起因是因为不希望改变原有代码结构,以及将最常见的情况单独列出、单独处理,例如

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        mFeedbackViewModel.uiStateFlow.collect { state ->
            if (state == FeedBackState.SubmitSuccess) {
                return@collect
            }
            when (state) {
                is FeedBackState.Init -> {
                }
                is FeedBackState.SubmitLoading -> {
                }
                is FeedBackState.SubmitError -> {
                }
                else -> {}
            }
        }
    }
}

只是一种个人习惯,更多时候其实会把片段逻辑写入 when(switch)中,这样也代表了代码可读性

同时,在我学习的时候就知道 switch在编译成字节码时会进行优化,通过一次计算直接定位到目标分支,时间复杂度是O(1),而if-else需要逐个判断,时间复杂度是O(n)

但是今天看到一篇文章讲到一个很有意思的点, if-else在一部分情况下其实性能比switch要好很多。


CPU 分支预测与指令流水线的关键作用  

现代 CPU的两大性能支柱

  • 分支预测: CPU 将指令执行拆分为 “取指→译码→执行→写回” 等步骤并行处理,大幅提升吞吐量;但分支指令(如 if/switch 的跳转)会打断流水线 ——CPU 需等待分支判断结果,才能确定下一条指令地址,导致流水线 “空转”。

  • 指令流水线: CPU 通过 “历史执行记录” 猜测分支走向,提前预取指令填充流水线。若预测成功,流水线无缝执行;若失败,需清空已预取的错误指令,重新加载正确指令,造成 10-20 个 CPU 时钟周期的延迟(高频场景下性能损耗会被放大)。

 

对于像简单的 if跳转,CPU是可以比较好地做分支预测的。但是对于switch跳转,CPU则没有太多的办法。switch本质上是据索引,从地址数组里取地址再跳转。

 

例如:

// 情况 A:if-else
if (x == 100) {
  handle100();
} else if (x == 101) {
  handle101();
} else {
  handleOther();
}
// 情况 B:switch
switch(x) {
  case 100: handle100(); break;
  case 101: handle101(); break;
  default: handleOther();
}


如果是 if,汇编大概是这样的逻辑:

cmp x, 100
beq label_100   // 条件分支(预测器能学习到:几乎总是走)
cmp x, 101
beq label_101
jmp default

如果编译器把 switch变成跳转表,汇编会变成大概这样的逻辑:

idx = x - 100
if (idx < 0 || idx > (max-min)) goto default_label
target = table[idx]        // 从内存取地址
jmp target                 // 间接跳转 —— 难预测

如果 x == 100的概率是 99.9%,那么:

  • if (x==100) 这一个条件分支对预测器来说是强偏向(always true)→ 很少错,流水线几乎不清空;

  • switch 若是跳转表实现:每次都通过间接跳转到对应目标,预测器对“跳到哪个目标”不一定猜得准,导致大量间接跳转失败,流水线损耗放大,吞吐下降。

所以在 “极端不均匀分布”下,通过把高频情况先写成 if,把低频情况交给 switch,能兼顾可读性和性能。

 

为什么处理有序数组要比非有序数组快  

这是一个在 stackoverflow上有一个非常有名的问题,本质上是同一个 CPU 分支预测原理的不同表现,只是场景不一样

假设我们写了这样的循环:

for (int x : arr) {
    if (x > 100) {
        count++;
    }
}

情况 A : arr 有序(递增)

l 前一半元素小于 100 ,后一半大于 100 。

l CPU 的分支预测器能很快学到模式:

n 在前半段几乎总是 false ;

n 到后半段几乎总是 true ;

l 预测命中率接近 100% ,流水线不需要频繁清空 → 执行很快。

情况 B : arr 无序(随机分布)

l 每个元素大于/ 小于 100 的概率差不多, CPU 无法找到规律。

l 预测器几乎是瞎猜,预测成功率 大约 50% 。

l 一半时间会预测失败 → 每次失败要清空流水线,性能下降。

这就是为什么 “有序数组”在某些判断场景下比“无序数组”要快。

  • if (x == HOT) 这种情况就像有序数组中长时间连续出现同一种情况,CPU很容易预测成功。

  • switch(x)(特别是跳转表实现)就像无序数组,每次跳转目标可能不同,CPU 很难预测,导致流水线清空更频繁。

无论是 “有序数组更快”还是“提前 if 优于 switch”,核心原因都是:CPU 的分支预测器喜欢“稳定可预测的模式”,不喜欢“杂乱无序的跳转”。

 

在想

其实在最开始了解这方面的知识只是把这个事情当作一个小 tips了解,看完文章才发现,在很多情况下性能差距可能会达到2倍。

很多的判断,例如加载状态下,其实大部分都是 “加载成功”也就意味着完全可以使用if-else加上switch的方式来提升性能,而代码可读性和扩展性也可以采用其他方案权衡

现在回过头来看,八股文其实并不是研发需要的的最终答案,作用在于提醒我们去思考为什么。如果光去背 “分支少用 if-else,分支多用 switch” 则 毫无意义 , 而是真正理解了 CPU的分支预测、流水线、跳转表优化,才会发现计算机的世界远远比死记硬背来的更精彩。

我不推崇八股文,因为我觉得光会背没有意义。但是我也相信“八股”,因为不论是代码优化、架构设计、性能调优都逃不开这些基本原理。离开了地基,再高深的设计模式、框架思维,都很容易成为空中楼阁。


参考资料: 提前if判断帮助cpu分支预测

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69917874/viewspace-3095775/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
Oracle OCA(Java),IBM高级工程师认证,中国软件行业人才(高级c语言),高级Android工程师,对数据库并发与性能调优也有一定了解

注册时间:2019-04-25

  • 博文量
    147
  • 访问量
    390757