当前位置: 首页 > news >正文

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

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

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

一 、基础概念

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 {\tpublic enum ChannelState {\t\tCONNECTED, DISCONNECTED, SENT, RECEIVED, CAUGHT\t}\t@State(Scope.Benchmark)\tpublic static class ExecutionPlan {\t\t@Param({ \u0026quot;1000000\u0026quot; })\t\tpublic int size;\t\tpublic ChannelState[] states = null;\t\t@Setup\t\tpublic void setUp() {\t\t\tChannelState[] values = ChannelState.values();\t\t\tstates = new ChannelState[size];\t\t\tRandom random = new Random(new Date().getTime());\t\t\tfor (int i = 0; i \u0026lt; size; i++) {\t\t\t\tint nextInt = random.nextInt(1000000);\t\t\t\tif (nextInt \u0026gt; 100) {\t\t\t\t\tstates[i] = ChannelState.RECEIVED;\t\t\t\t} else {\t\t\t\t\tstates[i] = values[nextInt % values.length];\t\t\t\t}\t\t\t}\t\t}\t}\t@Fork(value = 5)\t@Benchmark\t@BenchmarkMode(Mode.Throughput)\tpublic void benchSiwtch(ExecutionPlan plan, Blackhole bh) {\t\tint result = 0;\t\tfor (int i = 0; i \u0026lt; plan.size; ++i) {\t\t\tswitch (plan.states[i]) {\t\t\tcase CONNECTED:\t\t\t\tresult += ChannelState.CONNECTED.ordinal();\t\t\t\tbreak;\t\t\tcase DISCONNECTED:\t\t\t\tresult += ChannelState.DISCONNECTED.ordinal();\t\t\t\tbreak;\t\t\tcase SENT:\t\t\t\tresult += ChannelState.SENT.ordinal();\t\t\t\tbreak;\t\t\tcase RECEIVED:\t\t\t\tresult += ChannelState.RECEIVED.ordinal();\t\t\t\tbreak;\t\t\tcase CAUGHT:\t\t\t\tresult += ChannelState.CAUGHT.ordinal();\t\t\t\tbreak;\t\t\t}\t\t}\t\tbh.consume(result);\t}\t@Fork(value = 5)\t@Benchmark\t@BenchmarkMode(Mode.Throughput)\tpublic void benchIfAndSwitch(ExecutionPlan plan, Blackhole bh) {\t\tint result = 0;\t\tfor (int i = 0; i \u0026lt; plan.size; ++i) {\t\t\tChannelState state = plan.states[i];\t\t\tif (state == ChannelState.RECEIVED) {\t\t\t\tresult += ChannelState.RECEIVED.ordinal();\t\t\t} else {\t\t\t\tswitch (state) {\t\t\t\tcase CONNECTED:\t\t\t\t\tresult += ChannelState.CONNECTED.ordinal();\t\t\t\t\tbreak;\t\t\t\tcase SENT:\t\t\t\t\tresult += ChannelState.SENT.ordinal();\t\t\t\t\tbreak;\t\t\t\tcase DISCONNECTED:\t\t\t\t\tresult += ChannelState.DISCONNECTED.ordinal();\t\t\t\t\tbreak;\t\t\t\tcase CAUGHT:\t\t\t\t\tresult += ChannelState.CAUGHT.ordinal();\t\t\t\t\tbreak;\t\t\t\t}\t\t\t}\t\t}\t\tbh.consume(result);\t}}

验证说明:

  • benchSiwtch里是纯Switch判断;
  • benchIfAndSwitch里用一个如果提前判断状态是否ChannelState.RECEIVED

基准测试结果是:

Result \u0026quot;io.github.hengyunabc.jmh.TestBenchMarks.benchSiwtch\u0026quot;:  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:48Benchmark                         (size)   Mode  Cnt     Score    Error  UnitsTestBenchMarks.benchIfAndSwitch  1000000  thrpt  100  1535.867 ± 61.212  ops/sTestBenchMarks.benchSiwtch       1000000  thrpt  100   576.745 ±  6.806  ops/s

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

五、总结

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

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

相关文章:

  • Ubuntu18.04配置双网卡、双路由
  • 刚刚,阿里发布AI谣言粉碎机,识别准确率达81%
  • 如何通过StackStorm自动支持2万多台服务器
  • 流动的SVG线条
  • Spring框架IOC和AOP的实现原理(概念)
  • 使用GlobalSSH加速Ansible海外部署效率
  • gulp的使用方法
  • npm更新升级
  • 微信自动跳转外部浏览器打开网页或下载APP的技术源码,可以避免分享链接被微信拦截...
  • 微信小程序中使用emoji表情相关说明
  • idou老师教你学istio30:Mixer Redis Quota Adapter 实现和机制
  • Anaconda 和 JetBrains 联手推出 'Anaconda的PyCharm'
  • 苹果、三星、华为们进场,睡眠产业的百家争鸣
  • Redis主从数据库同步
  • 前嗅ForeSpider脚本教程-链接抽取:链接在源码的js变量里写脚本
  • 深入了解以太坊
  • electron原来这么简单----打包你的react、VUE桌面应用程序
  • JavaScript 一些 DOM 的知识点
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • socket.io+express实现聊天室的思考(三)
  • vue-cli3搭建项目
  • 阿里研究院入选中国企业智库系统影响力榜
  • 机器学习中为什么要做归一化normalization
  • 基于Javascript, Springboot的管理系统报表查询页面代码设计
  • 前端攻城师
  • 使用agvtool更改app version/build
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 消息队列系列二(IOT中消息队列的应用)
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • No resource identifier found for attribute,RxJava之zip操作符
  • Android开发者必备:推荐一款助力开发的开源APP
  • const的用法,特别是用在函数前面与后面的区别
  • # 数据结构
  • #《AI中文版》V3 第 1 章 概述
  • #pragma预处理命令
  • #在线报价接单​再坚持一下 明天是真的周六.出现货 实单来谈
  • (2)关于RabbitMq 的 Topic Exchange 主题交换机
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (二)Linux——Linux常用指令
  • (顺序)容器的好伴侣 --- 容器适配器
  • (详细版)Vary: Scaling up the Vision Vocabulary for Large Vision-Language Models
  • (一)Spring Cloud 直击微服务作用、架构应用、hystrix降级
  • .NET Core 控制台程序读 appsettings.json 、注依赖、配日志、设 IOptions
  • .Net CoreRabbitMQ消息存储可靠机制
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .net快速开发框架源码分享
  • 。Net下Windows服务程序开发疑惑
  • @AutoConfigurationPackage的使用
  • @FeignClient注解,fallback和fallbackFactory
  • @param注解什么意思_9000字,通俗易懂的讲解下Java注解
  • @开发者,一文搞懂什么是 C# 计时器!
  • [.NET]桃源网络硬盘 v7.4
  • [AIGC 大数据基础]hive浅谈
  • [Android Pro] AndroidX重构和映射
  • [Android Pro] Notification的使用