同一份逻辑,不同人的实现的代码性能会出现数量级的差异; 同一份代码,你可能微调几个字符或者某行代码的顺序,就会有数倍的性能提升;同一份代码,也可能在不同处理器上运行也会有几倍的性能差异;十倍程序员不是只存在于传说中,可能在我们的周围也比比皆是。十倍体现在程序员的方法面面,而代码性能却是其中最直观的一面。

  本文是《如何写出高性能代码》系列的第四篇,本文将告诉你数据访问会怎么样影响到程序的性能,以及如何通过变更数据访问的方式提升程序的性能。

数据访问速度为什么会影响到程序的性能?

  程序的运行的每一个可以简化为这样一个三步模型:第一步,读数据(当然也有部分数据是别的地方法发过来的);第二步,对数据做处理;第三步,将处理完的结果写入存储器。这里我将这三步骤简称为 读算写。 实际上真实的CPU指令执行过程会稍微复杂有些,但实际上也是这三个步骤。 而一个复杂的程序包含无数个CPU指令,如果读取或者写入数据太慢,必然会影响到程序的性能。

  为了能更直观一点,我这里将程序执行的流程比作是大厨做菜,大厨的工作流程就是取原始食材,然后对食材进行加工(煎烤烹炸煮),最后出锅上菜。影响大厨出菜速度的因素除了加工过程之前,获取食材的耗时也会影响到大厨出菜速度。有些食材就在手边,可以很快获取到,但有些食材可能在冷库、甚至在菜市场,获取就很不方便了。

  CPU犹如大厨,而数据就是CPU的食材,寄存器里的数据就是CPU手边的食材,内存的数据就是在冷库的食材,固态硬盘(SSD)上的数据是还在菜市场的食材,机械硬盘(HDD)上的数据犹如还在地里生长的菜…… 如果CPU在运行程序时,如果拿不到所需要的数据,它也只能等在那儿浪费时间了。

数据访问速度对程序性能有多大影响?

  不同存储器数据读取和写入的时延相差极大,鉴于大多数场景下,我们都是读取数据,我们就只拿数据读取为例,最快的寄存器和最慢的机械磁盘,随机读写的时延相差百万倍。可能你没有直观概念,我们还是拿厨师做个类比。

   假设厨师要做一道西红柿炒鸡蛋,如果食材都有人备好的话,只需要十来秒食材就能下锅炒制。 我们把这个时间比作是CPU从寄存器里取到数据的时间。然而如果是CPU从磁盘获取数据的话,所耗费的时间相当于厨师自己种出西红柿或者养小鸡下蛋了(3-4个月)。由此可见,从错误的存储设备上获取数据,会极大影响程序的运行速度。

  再说一个我们之前在生产环境遇到的实际案例,我们在生产环境也出过故障。原因是这样的,我们有个服务容器化改造的时候,和上游服务没有部署在同一个机房,跨机房虽然只会增加1ms的时延,但他们服务代码写的有问题,有个接口批量串行调另外一个服务,串行累加导致接口时延增加上百ms。 本来没有性能问题的服务,就因为迁移了机房,导致性能出现了问题……

各存储器性能差异

  实际上在编码的时候,遇到的存储设备多种多样,寄存器、内存、磁盘、网络存储……,每种设备都有自己的特点。只有认识到各种存储器之间的差异,我们才能在正确的场景下使用合适的存储器。以下表格就是各类常见存储设备的随机读时延参考数据……

备注:以上数据在不同硬件设备会有出入,这里只是为了展示其差异性,不代表准确值,准确信息请参考硬件手册。

  虽然日常我们觉得内存的读取速度已经很非常快了,日常写代码的时候遇到啥数据获取比较慢,加个内存缓存速度简直就起飞了。但内存的访问速度相对于CPU运行速度来说还是太慢,读取一次内存的时间,都够CPU执行几百条指令了,所以现代CPU都对内存加了缓存。

如何减小数据访问时延对性能的影响?

  减少数据访问时延对性能的影响也很简单,那就是把数据尽可能放到最快的存储介质上。然而,存取速度、容量、价格三者之间有着不可调和的矛盾,简单来说就是 速度越快容量越小但价格越贵,反之容量越大速度越慢而价格越便宜

  世界总是那么巧秒,仿佛一切早被安排好,我们并不需要把所有的数据都放在最快的存储介质上。 还记得我们在第二篇(巧用数据特性)[https://blog.csdn.net/xindoo/article/details/123941141] 提到的数据局部性吗! 局部性分两种,空间局部性和时间局部性。

  • 时间局部性: 如果一份某个时刻数据被访问过,那不久之后这份数据会被再次访问到。
  • 空间局部性: 如果某个存储元被访问过,大概率那不久之后,其附近的存储单元也会被访问。

  总结下这两点就是,程序大部分时间只会集中访问很小的一部分数据。 这意味着我们可以用较小的存储空间覆盖到大部分被访问的数据。 说直接点就是,我们可以加缓存。 实际上,不管是计算机硬件、数据库、还是业务系统,到处都充斥着缓存。甚至你写下的每一行代码,在机器上运行时都用到了缓存,不知道大家有没有关注过CPU,CPU有个参数,就是缓存大小,我们以intel酷睿i7-12650HX 为例,它就有24MB的三级缓存,这个缓存就是CPU到内存之间的缓存。 只不过现代计算机将底层的细节屏蔽掉了而已,我们日常不太可能主要的到。

  **在我们自己写代码的时候,也可以加缓存来提升程序性能。**举个最近的我们在系统中遇到的例子,我们最新在做数据权限相关的功能,不同的员工在我们系统中有不同的权限,所以他们看到的数据也应该是不同的。我们的实现方式是每个用户请求系统的时候,首先获取到该用户所有的权限列表,然后把所有在权限列表中的数据展示出来。

  因为每个人的权限列表比较大,所以权限接口的性能不怎么样,每次请求耗时也比较长。所以,我们直接给这个接口的数据加了缓存,优先从缓存里取,取不到再调接口,极大提升了程序性能。当然因为权限数据也不会经常变动,所以也不用太考虑数据滞后导致的后果。另外,我们缓存数据只加了几分钟,因为一个用户单次使用我们系统时长也就持续几分钟,过几分钟后数据过期缓存空间也会自动释放,达到节省空间的目的。

  我在上大学那会,笔记本电脑还是标配机械硬盘的年代,那时候电脑永久了会很卡,后来了解到换装SSD会提升电脑性能,那个时候SSD还挺贵的,普通笔本都不会标配SSD后来我攒半个月的生活费给自己笔记本替换了一块120g的SSD,电脑的运行速度就有明显的提升,本质上还是因为SSD的随机访问时延比机械硬盘快上百倍的原因。 之前某大厂号称将mysql性能提升了上百倍,其实也是基于SSD做的很多查询优化。

缓存不是银弹

银弹(英文:Silver Bullet),指由纯银质或镀银的子弹。在欧洲民间传说及19世纪以来哥特小说风潮的影响下,银色子弹往往被描绘成具有驱魔功效的武器,是针对狼人、吸血鬼等超自然怪物的特效武器。后来也被比喻为具有极端有效性的解决方法,作为杀手锏、最强杀招、王牌等的代称。


  这里特别提醒下,缓存不是万能的,缓存其实是有副作用的,那就是数据的有效性很难得到保证。缓存其实里面放的是旧数据,当前时刻数据是不是还是这样的?不确定,也许数据早就变了,所以使用缓存时必须要关注缓存数据有效性问题。如果缓存时间过久,数据失效的可能性能,数据不一致导致的风险也就越大。 如果缓存时间过短,因为经常需要获取原始数据,缓存存在意义也就越小。所以在使用缓存必须要做出数据不一致和性能之间的权衡(trade-off),你需要正确评估数据的时效性,对缓存设置合理的过期策略。

  上文说到其实我们写下的每一行代码都用到了缓存,现在大家已经都知道这个缓存其实就是CPU的Cache。CPU的Cache也是有明显的副作用的,我们在写多线程代码的时候也不得不关注到,那就是多核CPU之间数据一致性的问题。因为CPU Cache的存在,我们写多线程代码时不得不考虑数据同步的问题,导致多线程的代码很难编写,出了问题也很难排查。

  有个面试八股文题目其实就很容易说明这个问题——多线程计数器,多线程去操作计数器,累加统计数据,如何保证数据统计的准确性。如果只是简单使用cnt++实现,这里就会遇到多核CPU缓存导致的数据不一致性,具体原理这里不再解释,反正结果就是统计出来的数据会比真是数据少。 正确的做法就是,你必须在累加的过程中加多线程同步的机制,保证同一时刻只可能有一个线程在操作,操作完之后也能保证数据能写回内存,在java中必须使用锁或者原子类实现。而这对于编程新手而言又是一道门槛。


总结

  数据访问是任何程序不可或缺的一部分,甚至对大多数程序而言时间都耗费在了数据访问的过程上,所以只要优化了这部分的耗时,程序的性能必然能得到提升。

  本文全部内容就到这了,下一篇,我们将继续探讨下性能优化到极致该怎么做,敬请期待!!另外,有兴趣也可以查阅下之前的几篇文章。

如何写出高性能代码系列文章

  • (一)善用算法和数据结构
  • (二)巧用数据特性
  • (三)优化内存回收(GC)
  • (四)优化数据访问

如何写出高性能代码(四)优化数据访问相关推荐

  1. 如何写出高性能代码(二)巧用数据特性

    导语 同一份逻辑,不同人的实现的代码性能会出现数量级的差异: 同一份代码,你可能微调几个字符或者某行代码的顺序,就会有数倍的性能提升:同一份代码,也可能在不同处理器上运行也会有几倍的性能差异:十倍程序 ...

  2. 如何写出高性能代码(一)善用算法和数据结构

    同一份逻辑,不同人的实现的代码性能会出现数量级的差异: 同一份代码,你可能微调几个字符或者某行代码的顺序,就会有数倍的性能提升:同一份代码,也可能在不同处理器上运行也会有几倍的性能差异:十倍程序员不是 ...

  3. 怎样用 Android Annotations 写出高性能代码

    上一篇博文中简单介绍了 Android Annotations 的基本用法,顺便扯了一下概念 - 契约编程,阅读量少的可怜,看来并没有多少人对此感兴趣,今天再来一篇,介绍几个稍微高级点的用法,我就不信 ...

  4. code blocks代码性能分析_Julia系列教程13--如果写出高性能的Julia代码

    避免全局变量https://www.zhihu.com/video/1113506985873588224 code generationhttps://www.zhihu.com/video/111 ...

  5. 三周写出高性能的Python代码,这些小技巧你值得一试。

    1一个不上进的 Python 使用者 我是一个有 C 语言背景的开发者.最近转做了 Python,平时用 Python 还算 6,这周在给新员工分享工作之后,有个小孩跑来问我:"哥,你是学 ...

  6. 10个小技巧助您写出高性能的ASP.NET Core代码

    今天这篇文章我们来聊一聊如何提升并优化ASP.NET Core应用程序的性能,本文的大部分内容来自翻译,当然中间穿插着自己的理解,希望对大家有所帮助!话不多说开始今天的主题吧! 我们都知道性能是公共网 ...

  7. 如何写出“简单“代码?

    如何写出"简单"代码? 当你做实际开发中,你一定会发现有人跟你说,或者你在网上看:要写简单代码要写简单代码.    这句话其实就跟大家熟悉的设计模式中的简单(kiss)原则是挂钩的 ...

  8. 优雅的写出 JavaScript 代码

    目录 前言 避免使用 js 糟粕和鸡肋 编写简洁的 JavaScript 代码 使用 ES6/ES7 新特性 Babel ESLint Prettier 采用函数式编程 优雅的敲 JS 代码的几个原则 ...

  9. 写出漂亮代码的45个小技巧

    不知道大家有没有经历过维护一个已经离职的人的代码的痛苦,一个方法写老长,还有很多的if else ,根本无法阅读,更不知道代码背后的含义,最重要的是没有人可以问,此时只能心里默默地问候这个留坑的兄弟. ...

最新文章

  1. 机器学习中的数学基础:(1.1)矩阵特征值和特征向量的几何意义
  2. darknet: ./src/cuda.c:36: check_error: Assertion `0' failed.
  3. python起步输入-[转载]python核心编程学习笔记-python起步
  4. 秩、标量、矢量、矩阵
  5. 告诉你,初学网络安全应该怎样去学呢?安排的明明白白的
  6. 我命由我不由天,5个月逆袭百度,头条AI岗位,来挑战吗?
  7. 1.Kong入门与实战 基于Nginx和OpenResty的云原生微服务网关 --- 基础知识点概述
  8. c语言中变量后减号大于号,大于等于运算符.ppt
  9. java中mvc框架有哪些,MVC框架详解,框架到底是什么? | 学步园
  10. php计算ip掩码,php进行ip地址掩码运算处理的方法
  11. Kubernetes crictl管理命令详解
  12. 云电脑有显卡吗?云电脑怎么做画面处理?
  13. 【概率论】6-4:分布连续性修正(The Correction for Continuity)
  14. Spring系列04德鲁伊连接池配置
  15. 台式机尺寸相关信息整理
  16. 大流量下订单号生成方法
  17. Python3 三元条件判断表达式(if else/and or)
  18. paypal php 返回_接入 paypal PHP-sdk 支付 / 回调 / 退款全流程
  19. 华东理工大学matlab怎么下载,华东理工大学MATLAB.ppt
  20. Java打怪升级道路

热门文章

  1. unity 人物走动声音_Unity3D实现人物走动
  2. 微信小程序遇到的坑。
  3. 莫队入门例题之持久化莫队:2120: 数颜色
  4. js-三阶贝塞尔曲线计算公式
  5. 洛咕 P2465 [SDOI2008]山贼集团
  6. 【Luogu】P2465山贼集团(树形状压DP)
  7. 浅识k8s中的准入控制器
  8. docker for mysql
  9. Things I wish I’d known when I was younger 年轻时就该知道的那些事儿
  10. stf环境安装(iosandroid)