计算机系统应用教程网站

网站首页 > 技术文章 正文

阿里程序员工作小技巧:理解 CPU 分支预测,提高代码效率

btikc 2024-09-04 03:30:14 技术文章 12 ℃ 0 评论


技术传播的价值,不仅仅体现在通过商业化产品和开源项目来缩短我们构建应用的路径,加速业务的上线速率,体现也会在优秀程序员在工作效率提升,产品性能优化和用户体验改善等小技巧方面的分享,以提高我们的工作能力。

本文来自阿里巴巴中间件技术团队的程序员断岭,他是阿里微服务开源项目 Dubbo 的项目组成员,也是 Java 线上诊断开源项目 Arthas 的负责人。dubbo学习也可到Java自学网。

一 、基础概念

a. Dubbo: 是一款高性能、轻量级的开源 Java RPC 框架,提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现;

b. ChannelEventRunnable: Dubbo 里所有网络事件的回调接口;

c. JMH:即 Java Microbenchmark Harness,是专门用于代码微基准测试的工具套件。在性能优化的过程中,可以使用 JMH 对优化的结果进行量化的分析。

二、需求缘起:

在 Stack Overflow 上有一个非常著名的问题:为什么处理有序数组要比非有序数组快?从问题的结论来看,是分支预测对代码运行效率的提升起到了非常重要的作用。

现今的 CPU 是都支持分支预测 (branch prediction) 和指令流水线 (instruction pipeline),这俩的结合可以极大的提高 CPU 的工作效率,从而提高代码执行效率。但这仅适用于简单的 if 跳转,但对于 Switch 跳转,CPU 则没有太好的解决办法,因为 Switch 本质上是据索引,是从地址数组里取地址再跳转。

三、思考和方案假设

要提高代码执行效率,一个重要的实现原则就是尽量避免 CPU 把流水线清空,从 Stack Overflow 上的讨论结果来看,通过提高分支预测的成功率,是可以降低 CPU 对流水线清空的概率。那么,除了在硬件层面,是否可以考虑代码层面帮 CPU 把判断提前,来提高代码执行效率呢?

四、方案验证

在 Dubbo 的 ChannelEventRunnable 里有一个 Switch 来判断 channel state。当一个 channel 建立起来之后,超过 99.9% 的情况,它的 state 都是 ChannelState.RECEIVED,我们可以考虑,把这个判断提前。

以下通过 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 里用一个如果提前判断状态是否ChannelState.RECEIVED。

基准测试结果是:

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] (assumes normal distribution)
 
 
# 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 判断提高了近 3 倍的代码效率,这种技巧可以放在性能要求严格的地方。。

五、总结

  • 开关对于 CPU 来说难以做分支预测。
  • 某些 Switch 条件如果概率比较高,可以在代码层设置提前 if 判断,充分利用 CPU 的分支预测机制。

写在最后:

码字不易看到最后了,那就点个关注呗,只收藏不点关注的都是在耍流氓!

关注并私信我“架构”,免费送一些Java架构资料,先到先得!记得转发哦!

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表