优化技巧:提前if判断帮助CPU分支预测

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,118元/月
性能测试 PTS,5000VUM额度
云原生网关 MSE Higress,422元/月
简介: 在stackoverflow上有一个非常有名的问题:为什么处理有序数组要比非有序数组快?,可见分支预测对代码运行效率有非常大的影响。要提高代码执行效率,一个重要的原则就是尽量避免CPU把流水线清空,那么提高分支预测的成功率就非常重要。

分支预测

在stackoverflow上有一个非常有名的问题:为什么处理有序数组要比非有序数组快?,可见分支预测对代码运行效率有非常大的影响。

现代CPU都支持分支预测(branch prediction)和指令流水线(instruction pipeline),这两个结合可以极大提高CPU效率。对于像简单的if跳转,CPU是可以比较好地做分支预测的。但是对于switch跳转,CPU则没有太多的办法。switch本质上是据索引,从地址数组里取地址再跳转。

要提高代码执行效率,一个重要的原则就是尽量避免CPU把流水线清空,那么提高分支预测的成功率就非常重要。

那么对于代码里,如果某个switch分支概率很高,是否可以考虑代码层面帮CPU把判断提前,来提高代码执行效率呢?

Dubbo里ChannelEventRunnable的switch判断

ChannelEventRunnable里有一个switch来判断channel state,然后做对应的逻辑:查看

一个channel建立起来之后,超过99.9%情况它的state都是ChannelState.RECEIVED,那么可以考虑把这个判断提前。

benchmark验证

下面通过jmh来验证下:

public class TestBenchMarks {
    public enum ChannelState {
        CONNECTED, DISCONNECTED, SENT, RECEIVED, CAUGHT
    }

    @State(Scope.Benchmark)
    public static class ExecutionPlan {
        @Param({ "1000000" })
        public int size;
        public ChannelState[] states = null;

        @Setup
        public void setUp() {
            ChannelState[] values = ChannelState.values();
            states = new ChannelState[size];
            Random random = new Random(new Date().getTime());
            for (int i = 0; i < size; i++) {
                int nextInt = random.nextInt(1000000);
                if (nextInt > 100) {
                    states[i] = ChannelState.RECEIVED;
                } else {
                    states[i] = values[nextInt % values.length];
                }
            }
        }
    }

    @Fork(value = 5)
    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public void benchSiwtch(ExecutionPlan plan, Blackhole bh) {
        int result = 0;
        for (int i = 0; i < plan.size; ++i) {
            switch (plan.states[i]) {
            case CONNECTED:
                result += ChannelState.CONNECTED.ordinal();
                break;
            case DISCONNECTED:
                result += ChannelState.DISCONNECTED.ordinal();
                break;
            case SENT:
                result += ChannelState.SENT.ordinal();
                break;
            case RECEIVED:
                result += ChannelState.RECEIVED.ordinal();
                break;
            case CAUGHT:
                result += ChannelState.CAUGHT.ordinal();
                break;
            }
        }
        bh.consume(result);
    }

    @Fork(value = 5)
    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public void benchIfAndSwitch(ExecutionPlan plan, Blackhole bh) {
        int result = 0;
        for (int i = 0; i < plan.size; ++i) {
            ChannelState state = plan.states[i];
            if (state == ChannelState.RECEIVED) {
                result += ChannelState.RECEIVED.ordinal();
            } else {
                switch (state) {
                case CONNECTED:
                    result += ChannelState.CONNECTED.ordinal();
                    break;
                case SENT:
                    result += ChannelState.SENT.ordinal();
                    break;
                case DISCONNECTED:
                    result += ChannelState.DISCONNECTED.ordinal();
                    break;
                case CAUGHT:
                    result += ChannelState.CAUGHT.ordinal();
                    break;
                }
            }
        }
        bh.consume(result);
    }
}
  • benchSiwtch里是纯switch判断
  • benchIfAndSwitch 里用一个if提前判断state是否ChannelState.RECEIVED

benchmark结果是:

Result "io.github.hengyunabc.jmh.TestBenchMarks.benchSiwtch":
  576.745 ±(99.9%) 6.806 ops/s [Average]
  (min, avg, max) = (490.348, 576.745, 618.360), stdev = 20.066
  CI (99.9%): [569.939, 583.550] 

# Run complete. Total time: 00:06:48

Benchmark                         (size)   Mode  Cnt     Score    Error  Units
TestBenchMarks.benchIfAndSwitch  1000000  thrpt  100  1535.867 ± 61.212  ops/s
TestBenchMarks.benchSiwtch       1000000  thrpt  100   576.745 ±  6.806  ops/s

可以看到提前if判断的确提高了代码效率,这种技巧可以放在性能要求严格的地方。
Benchmark代码:https://github.com/hengyunabc/jmh-demo

总结

  • switch对于CPU来说难以做分支预测
  • 某些switch条件如果概率比较高,可以考虑单独提前if判断,充分利用CPU的分支预测机制
相关文章
|
9月前
|
编译器 Linux C语言
C++新特性“CPU优化对齐”
C++新特性“CPU优化对齐”
149 3
|
10天前
|
存储 设计模式 监控
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
本文介绍了 Java 应用常见的 CPU & JVM 内存热点原因及优化思路。
|
6月前
|
SQL 监控 关系型数据库
MySQL优化: CPU高 处理脚本 pt-kill脚本
MySQL优化: CPU高 处理脚本 pt-kill脚本
|
2月前
|
存储 缓存 监控
Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
本文介绍了Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
192 7
|
4月前
|
存储 缓存 算法
CPU优化
【10月更文挑战第7天】
64 1
|
6月前
|
监控 Java Linux
CPU被打满/CPU 100%:高效诊断与优化策略
【8月更文挑战第28天】在日常的工作与学习中,遇到CPU使用率飙升至100%的情况时,往往意味着系统性能受到严重影响,甚至可能导致程序响应缓慢或系统崩溃。本文将围绕这一主题,分享一系列高效诊断与优化CPU使用的技术干货,帮助大家快速定位问题并恢复系统性能。
385 1
|
7月前
|
Java
手把手教你java CPU飙升300%如何优化
手把手教你java CPU飙升300%如何优化
79 0
|
7月前
|
监控 Java 中间件
FGC频繁导致CPU 飙升定位及JVM配置优化总结
FGC频繁导致CPU 飙升定位及JVM配置优化总结
231 0
|
7月前
|
缓存 弹性计算 监控
云服务器 CPU 使用率高的问题排查与优化
云服务器 CPU 使用率高的问题排查与优化
511 0
|
8月前
|
SQL 数据处理 API
实时计算 Flink版产品使用问题之holo的io以及cpu使用较为稳定,sink端busy一直在20%左右,有时候50%,该如何优化
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。

相关实验场景

更多