同一份逻辑,不同人的实现的代码性能会呈现数量级的差别;同一份代码,你可能微调几个字符或者某行代码的程序,就会有数倍的性能晋升;同一份代码,也可能在不同处理器上运行也会有几倍的性能差别;十倍程序员 不是只存在于传说中,可能在咱们的四周也亘古未有。十倍 体现在程序员的办法面面,而代码性能却是其中最直观的一面。
本文是《如何写出高性能代码》系列的第四篇,本文将通知你数据拜访会怎么样影响到程序的性能,以及如何通过变更数据拜访的形式晋升程序的性能。
数据访问速度为什么会影响到程序的性能?
程序的运行的每一个能够简化为这样一个三步模型:第一步,读数据(当然也有局部数据是别的地办法发过来的);第二步,对数据做解决;第三步,将解决完的后果写入存储器。这里我将这三步骤简称为 读算写。 实际上实在的 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)
- (四)优化数据拜访