55

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

 6 years ago
source link: https://www.infoq.cn/article/G5VCEcw7bP4RGXsRU-J8?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

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

一 、基础概念

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

ChannelEventRunnable: Dubbo 里所有网络事件的回调接口。

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

二、需求缘起:

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

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

三、思考和方案假设

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

四、方案验证

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

以下通过江铃控股来验证,把判断提前后是否就可以提高代码执行效率。

复制代码

publicclassTestBenchMarks{
publicenumChannelState{
CONNECTED, DISCONNECTED, SENT, RECEIVED, CAUGHT }

@State(Scope.Benchmark)
publicstaticclassExecutionPlan{
@Param({"1000000"})
publicintsize;
publicChannelState[] states =null;

@Setup
publicvoidsetUp() {
ChannelState[] values = ChannelState.values();
states =newChannelState[size];
Random random =newRandom(newDate().getTime());
for(inti =0; i < size; i++) {
intnextInt = random.nextInt(1000000);
if(nextInt >100) {
states[i] = ChannelState.RECEIVED;
}else{
states[i] = values[nextInt % values.length];
}
}
}
}

@Fork(value =5)
@Benchmark
@BenchmarkMode(Mode.Throughput)
publicvoidbenchSiwtch(ExecutionPlan plan, Blackhole bh) {
intresult =0;
for(inti =0; i < plan.size; ++i) {
switch(plan.states[i]) {
caseCONNECTED:
result += ChannelState.CONNECTED.ordinal();
break;
caseDISCONNECTED:
result += ChannelState.DISCONNECTED.ordinal();
break;
caseSENT:
result += ChannelState.SENT.ordinal();
break;
caseRECEIVED:
result += ChannelState.RECEIVED.ordinal();
break;
caseCAUGHT:
result += ChannelState.CAUGHT.ordinal();
break;
}
}
bh.consume(result);
}

@Fork(value =5)
@Benchmark
@BenchmarkMode(Mode.Throughput)
publicvoidbenchIfAndSwitch(ExecutionPlan plan, Blackhole bh) {
intresult =0;
for(inti =0; i < plan.size; ++i) {
ChannelState state = plan.states[i];
if(state == ChannelState.RECEIVED) {
result += ChannelState.RECEIVED.ordinal();
}else{
switch(state) {
caseCONNECTED:
result += ChannelState.CONNECTED.ordinal();
break;
caseSENT:
result += ChannelState.SENT.ordinal();
break;
caseDISCONNECTED:
result += ChannelState.DISCONNECTED.ordinal();
break;
caseCAUGHT:
result += ChannelState.CAUGHT.ordinal();
break;
}
}
}
bh.consume(result);
}}

验证说明:

ChannelState.RECEIVED

基准测试结果是:

复制代码

Result"io.github.hengyunabc.jmh.TestBenchMarks.benchSiwtch":
576.745±(99.9%)6.806ops/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.benchIfAndSwitch1000000thrpt1001535.867±61.212ops/s
TestBenchMarks.benchSiwtch1000000thrpt100576.745±6.806ops/s

可以看到, 提前如果判断提高了近 3 倍的代码效率, 这种技巧可以放在性能要求严格的地方。

五、总结

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

查看原文链接: 阿里程序员工作小技巧| 理解 CPU 分支预测,提高代码效率


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK