10 月 23 - 25 日,QCon 上海站即将召开,现在购票,享9折优惠 了解详情
写点什么

性能优化那些事

  • 2021-01-14
  • 本文字数:4213 字

    阅读完需:约 14 分钟

性能优化那些事

性能在软件工程诞生时就占据着非常重要的位置,如何用更少的硬件资源来支撑更多的功能、来完成更多的任务是软件工程师的职责,也是用来衡量一个软件工程师技艺高低的标准。


性能在软件工程诞生时就占据着非常重要的位置,如何用更少的硬件资源来支撑更多的功能、来完成更多的任务是软件工程师的职责,也是用来衡量一个软件工程师技艺高低的标准。这跟炒菜是一个道理,同样的食材,在饭店的师傅手里跟普通人手里的做法是有很大的差别的;食堂师傅是工程化的做法,讲究是短时间炒制更多的菜,而且味道不能掉;而普通人则比较随意,没有什么章法,而且可能只是爱好或者放松的渠道,所以不用考虑炮制的性能如何,能不能达到预计的目标。简单来说,厨师是靠手艺去赚钱,而其他人只是玩玩。那么程序员的手艺体现在哪呢?可以肯定的说,性能调优是最重要的一环,也是最基本的一环,甚至我觉得只有掌握这个技能后才能够进阶做构架或者做设计,否则如果反其道而行之则会举步维艰。


这时你可能会说,我是觉得性能是很重要,但是怎么去学呢?IT 是个发展迅速的行业,每年涌现的新技术,新框架数不胜数,多如繁星,往往还没学懂 Java,Go 都快被淘汰了……一茬接一茬,无从下手。如果你常常有这个感觉,那么这篇文章可能对你有帮助。


性能优化的套路


先说结论,性能优化或者说如何能够开发出效率更高的程序来(当然硬件资源一样且使用率一样的条件下),只有两条路:


  • 掌握局部性原理

  • 掌握基本的算法设计与分析


在现有计算机构架下(冯诺依曼构架)这是调优的充分必要条件,换句话说就是,如果你写了一个性能比别人好的程序,那么只有两种情况,要么是局部性原理用得更好,要么是就是采用了更好的算法;同时,如果你想要写出比别人好的程序,那么你也只有两条路可以走,运用更底层的局部性优化设计,或者设计更好的算法来解决这个问题。


局部性原理


如果你在软件工程领域“阅历”足够丰富,你就会有恍然大悟的感觉,因为这种例子实在太多:Linux 中的硬盘缓冲、页缓存,Redis 中元素较少的时候用 ziplist 代替 hashmap 可能还会带来性能的提升,内存的减少;Kafka 依赖的磁盘缓存,分区存储机制,Mysql 的 B+树索引;更底层的 CPU 的 Cacheline 技术,分页技术等等其实都是用了局部性原理来作为理论基础的。那么什么是局部性原理呢?


简单来说分为三个要点:


  1. 程序总是按照“批”来处理数据,这样效率最高;比如:CPU 读取内存是按照批来处理如:一个 cacheline=64/128byte,内存与硬盘按照 4KB 来读取。因为计算机结构的特点,各级设备(CPU 到内存到硬盘与外设)之间的访问速度是有数量级的差别;所以就注定不能一个个字节来读取;这个现象决定计算机优化的方向,被称为冯诺依曼瓶颈

  2. 最近访问过的资源(内存位置),可能近期内还会被访问,这个叫做时间局部性,对应的解决方案是缓存

  3. 当前位置内存被访问过,那么大概率旁边的内存也会被后续访问,这个叫做空间局部性,对应的解决方案是预加载


所以推导的顺序是:正是因为现代计算机有冯诺依曼瓶颈所以需要用缓存与预加载来提高程序的性能,否则会因为 IO 过重,资源得不到利用而效率偏低。


还是拿做饭这件事举例子。当我们去买菜的时候,会尽可能的减少去菜市场的时间,也肯定不可能发现什么菜没有了再临时去菜市场购买,这样“IO”就太重了,要使用局部性原理来解决。就炒菜的例子来说,我们要炒青椒炒肉这个菜;首先是买菜,我们发现在买青椒的时候可以把葱姜蒜也都一并买了,因为蔬菜区往往是相邻的;而买肉的时候,尽量也把酱油醋都一并买了,它们也是很可能也是挨着的;这种在空间上相邻的提前加载这就是“预加载”了;而回到家,我们开始炒菜的时候,厨房的布置往往是炒菜的锅子跟油盐酱醋是分开放置的,但是炒菜的时候要经常加盐、放酱油,所以为了减少来回取油盐的时间,会在炒菜的时候将配菜与油盐放置在炒锅的附近,加快炒菜的速度,等炒完后又重新放回去,这个就叫做“缓存”的思维——把经常使用的资源放在工作较近的地方,等使用完再释放。


在 Linux 内核中,我们可以看到很多的 buffer 与 cache,这些数据结构都是局部性原理的具体使用实例。比如,我们知道内存跟磁盘之间 IO 速度相差 5 个数量级。那么往往会出现,一个进程刚写入磁盘的数据,又要被其他进程读取,那么一来一回 CPU 都在等待磁盘读取数据,十分浪费资源,那么如果将磁盘的数据放入磁盘写 buffer,读的时候优先从 buffer 中读取,而 buffer 都是在内存中,这样就弥补了这个性能的 gap,这就是“时间局部性”原理的使用,也就是我们常说的“缓存”,或者“空间换时间”;


Mysql 数据库有个特点就是数据是存在磁盘上,读取记录时需要将数据从磁盘加载到内存然后进入 CPU 计算得出结果,所以会横跨三个 IO 边界——磁盘、内存与 CPU,它们之间都有好几个数量级的延迟,所以 IO 性能的平衡就十分的重要。其中比较典型的就是 B+树这种针对外部存储型的数据结构的引入,它十分贴合磁盘 IO 的“口味”。磁盘 IO 有个特点,就是读取写入都很慢,但是可以一次读取大量的数据。根据这个特点,B+树采用多叉树的结构进行设计,这样做相比二叉树来说可以减少树的高度,这样带来的好处是每一层数据都可以是在物理空间相邻的,从而可以通过最少的 IO 次数加载更多的数据到内存;而这些数据往往是业务相关的,所以一次 IO 读取的数据可以供后续很多计算工作使用而不用重复 IO。比如,一个表有 20 列,那么每一行数据就有 20 个字段,这些字段往往在磁盘中是连续存放的,当我们通过 id 查询其中某个字段 a 的值时,Mysql 其实会将整个记录都取出来,加载到内存,而程序访问完 a 字段,再访问记录中其他字段时就不需要磁盘 IO 了,直接读取内存中记录的缓存就行,这就是“空间局部性”的使用实例,也就是我们常说的“预加载”,系统预热。


算法分析与设计


这是计算机科学的范畴,也是最引人注目的领域,就好比武侠小说里面的武功,华丽的招式比不过强大的内功,练招式容易练内功难,一旦内功深厚,招式什么的都是分分钟被秒杀,就好比《一拳超人》中的琦玉老师,专治各种花里胡哨。而我想说的是,算法设计技术就是软件工程领域的内功心法。当然我远远没有达到可以对这个领域品头论足的程度,就想抛砖引玉,介绍点皮毛,希望对有天赋的同学有所启发。


数据结构


数据结构之所以重要是因为不同的数据结构表现出来的数学性质是构成特定算法的基础。就好比各种化学元素可以搭建各种性质不同的化合物,而不同性质的化合物正式化学工程所依赖的基础。计算机科学的数据结构可以大致分为两种,一种是数组,另一种是链表。两种性质截然相反的“物质”。


  1. 数组——线性性能最好,支持随机访问,按照索引取数组中的元素时间复杂度是 O(1),而插入与删除元素时间复杂度是 O(n);

  2. 链表——扩展性能最好,支持动态的增减元素,插入、删除元素的时间复杂度是 O(1),而检索元素的时间复杂度变成了 O(n);


(注:O 标记是数学语言,用于标记一个函数的增长快慢,是一个渐进界函数,具体细节可以参考屈婉玲教授的详细解释)


这个世界有时候是惊人对称的,对称是美妙的。这样一对性质完全互斥的结构就是构成所有高等数据结构的基础。就像中国传统文化中的太极一样,你中有我我中有你,互相补充,世间万物的运行法则皆可解释。


举几个例子,有些高等数据结构具有耀眼的数学特性,比如“堆”,或者叫做优先级队列、二叉堆,就是以数组作为基础的。它在插入、删除、查询元素时的性能可以稳定在 O(logn)量级;在互联网行业中,只有 logn 级别的算法可以适用,因为当数据规模急剧增加的时候,对数函数能够很好的平稳压力,所以,"logn"的算法对互联网行业贡献巨大,具有整流器的作用。比如,在微服务流量控制,大数据流处理、topN、高性能定时器都有很多应用。而堆只有在数组实现的算法中才能保持这个特性,如果用链表就会退化为 O(nlogn),失去魔力。


另一个例子是二叉树,这就是链表的一个使用场景。二叉树是一种树状结构,其中平衡二叉树在插入与删除的过程中只要移动 logn 次就能找到自己的新位置,而且代码简单易于维护(不容易写错也是工程中一个重要的考虑点,如果写得代码过于复杂就要反思下是否使用错了数据结构)。比如:红黑树就是一个综合性能很好的平衡二叉查找树;它是一个动态的数据结构,可以在动态添加与查找过程中稳定在 O(logn)量级;在 Linux 内核中大量使用;而且在 Java 中的 ConcurrentHashMap 在冲突大的情况下,冲突元素大于 8 也会升级成红黑树存储冲突元素,来平衡工程与算法效率之间的矛盾。


当然,数据结构是可以融合的,比如 Java 中的 LinkedHashMap 就是融合了数组、哈希表与链表的优秀实践。普通的哈希表因为通过哈希函数将元素链接到数组的索引号上面,实现高速的查找性能,但是丢失了元素的插入顺序,而有时候我们需要这个顺序性来实现特殊的需求,比如缓存淘汰策略;而如果使用链表,形成一个插入的队列,先插入的在队列头,后插入在队列尾部;但是这样虽然保存了插入的顺序但是丢失了查找性能;为了平衡,我们可以在哈希表的基础上,每个元素再增加一个指针用来连接前后插入的元素,形成队列,这样没插入一个元素不仅在哈希表上挂载新的索引点,还要将新元素挂接到队列的尾部,而每一步都是 O(1)的开销,是可以接受的,这就是 LinkedHashMap 的实现原理。


算法设计技术


算法设计技术是使用数据结构解决实际问题的技术,就好比化合物特性研究与化学工程的关系,一个是偏重科学特性的研究,一个是解决实际问题。为了能够让科学能够指导生产,能够造福世间,必须将科学落地,那么这个过程中最重要的一步就是对实际问题的建模,建模是对问题的模拟,越准确越能够解决好问题。那么,在算法设计层面有哪些建模方式呢?大体可以分为四个:


  1. 蛮力算法

  2. 贪心算法

  3. 分治算法

  4. 动态规划


其中,蛮力算法是对问题建模的初期阶段,是对问题的程序再现,追求的是定性,性能不一定重要,但是问题描述没问题;分治与贪心是在蛮力基础上的一次降阶,往往可以将问题优化到 O(nlogn)的规模以内;而动态规划可以进一步将问题的阶降到 O(n)级别。降阶是设计算法的初衷,前提是问题本身计算的各个阶段是有冗余的,有重复计算的地方,而找到这个重复的点并不容易,就拿动态规划来说吧,虽然有极致的性能但是发现递推方程并不容易,当然这一切都要经过严格的数学证明才行,这就更加难能可贵。我们这里没有考虑空间的优化,往往降阶的过程中最好保证空间的复杂度不会激增,这样才会有效。这方面我们不展开了,有很多资料可以参考。


作者: 肖劲

文章转载自: ThoughtWorks 洞见(ID:TW-Insights)

原文链接:性能优化那些事

2021-01-14 07:001566

评论

发布
暂无评论
发现更多内容

业务复杂度治理方法论--十年系统设计经验总结

京东科技开发者

AV-Comparatives确定2024年夏季的主要网络安全威胁

财见

重磅活动!南开大学赵宏教授倾情分享AI挑战下的教育教学新理念与新方法

ModelWhale

Python 人工智能 通识课程

Redis 主从复制、切片集群

不在线第一只蜗牛

数据库 redis

飞书发布最强业务工具:新一代多维表格、低代码平台、飞书项目

ToB行业头条

淘宝商品详情API返回值中的优惠券与红包信息

技术冰糖葫芦

api 网关 API Explorer API 策略 pinduoduo API

网络管理方法及软件选择指南

Geek_a83400

公开课 | 测试工程师的质量体系构建指南

测吧(北京)科技有限公司

测试

自增主键去哪了?---一次开发过程中的思考

京东科技开发者

LED显示屏厂家如何提升LED产品技术

Dylan

产品 技术 LED显示屏 led显示屏厂家 市场

通过ModelScope开源多模态Embedding模型进行向量生成

DashVector

数据库 向量检索 大模型

从Milvus迁移DashVector

DashVector

数据库 向量检索 大模型 #人工智能

Flink CDC 在货拉拉的落地与实践

Apache Flink

大数据 flink 流计算 Flink CDC

通义灵码最全使用指南,一键收藏

阿里云云效

阿里云 云原生 通义灵码

AI数字实时互动新探索,打造高拟真专属AI智能体

阿里云CloudImagine

云计算 音视频 视频云 实时互动 AI 智能体

万界星空科技MES系统中的排班排产功能

万界星空科技

mes 万界星空科技 生产管理 车间管理 生产排班排产

如何处理 MySQL 主从延迟?

伤感汤姆布利柏

电商创新策略:深度挖掘亚马逊国际商品详情API返回值

代码忍者

天翼云,AI取经路上的逐梦人

脑极体

AI

TikTok海外直播专线:提供稳定、高效直播体验

Ogcloud

海外直播专线 海外直播 tiktok直播专线 海外直播网络 tiktok直播网络

深入理解 Babel - 微内核架构与 ECMAScript 标准化|得物技术

得物技术

web前端 企业号2024年8月PK榜

天猫商品评论API返回值中的虚假评价识别策略

代码忍者

api 网关 API 策略

使用Redis时不可原谅的几个低级错误

江南一点雨

SD-WAN解决企业远程服务难题

Ogcloud

SD-WAN 企业组网 SD-WAN组网 SD-WAN服务商 SDWAN

活动在即,不容错过丨亚信安慧AntDB诚邀您参加“PostgreSQL数据库技术峰会”

亚信AntDB数据库

AntDB 月PK

通义灵码最全使用指南,一键收藏

阿里巴巴云原生

阿里云 云原生 通义灵码

【转载】golang内存分配

京东科技开发者

探索魔乐社区:GLM-4V-9B模型微调之旅

天翼云开发者社区

人工智能 大模型

VMware Workstation 17.6 Pro Unlocker & OEM BIOS 2.7 for Windows & Linux

sysin

macos vmware OEM BIOS Workstation

性能优化那些事_语言 & 开发_张凯峰_InfoQ精选文章