柯桥做网站哪家好济南软件开发公司
web/
2025/10/8 3:45:17/
文章来源:
柯桥做网站哪家好,济南软件开发公司,做进出口外贸网站,线切割加工东莞网站建设技术支持本文聊一个程序员都会关注的问题#xff1a;性能。 当大家谈到“性能”时#xff0c;你首先想到的会是什么#xff1f; 是每次请求需要多长时间才能返回#xff1f; 是每秒钟能够处理多少次请求#xff1f; 还是程序的CPU和内存使用率高不高#xff1f; 这些问题基本上…本文聊一个程序员都会关注的问题性能。 当大家谈到“性能”时你首先想到的会是什么 是每次请求需要多长时间才能返回 是每秒钟能够处理多少次请求 还是程序的CPU和内存使用率高不高 这些问题基本上反应了性能关注的几个主要方面响应时间、吞吐量和资源利用率。在这三个方面中如果能够实现更低的响应时间和更高的吞吐量那么资源利用率也必然得到优化。这是因为我们的工作总是在有限的硬件、软件、时间和预算等的约束下进行的而优化前两个方面将有助于更有效地利用这些资源。 因此本文将主要围绕响应时间和吞吐量的优化展开介绍包括相关领域的定义和软硬件方面的优化方法。 响应时间 想象一下你在餐厅点了一道菜响应时间就是从你下单到菜品送到你面前的这段时间。 在计算机里它指的是单次请求或指令处理的时间。 1.1 软件层面的优化 软件层面的优化主要是通过减少非必要的处理来降低响应时间包括减少IO请求和优化代码逻辑。 1.1.1 减少IO请求 减少IO请求的意义 IO就是输入输出减少IO处理就是减少对输入输出设备的访问。在计算机中除了CPU和内存其它的键盘、鼠标、显示器、音响、硬盘、网卡等等都属于输入输出设备。减少对这些设备的访问为什么有用呢
首先让我们了解下程序的运行过程大概是这样的 操作系统首先将程序的二进制指令从硬盘加载到内存然后再从内存加载到CPU然后CPU就按照二进制指令的逻辑进行处理指令是加减乘除的就做加减乘除指令是跳转的就做跳转指令是访问远程网络的就通过操作系统网卡发起网络请求指令是访问文件的就通过操作系统硬盘进行文件读写。 在CPU的这些处理中逻辑判断、跳转、加减乘除都是很快的因为它们只在CPU内部进行处理CPU中每条指令的运行时间极短但是如果要进行网络请求、文件读写速度就会大幅下降这里边有很多的损耗包括系统调用的时间消耗、总线的传输时间消耗、IO设备的处理时间消耗、远程过程的处理时间消耗等。 我们可以通过一组数字直观感受下假设CPU的主频是1GHZ执行1条指令需要1个时钟周期那么执行1条指令的时间就是1纳秒。而从硬盘读取数据的时间消耗要远大于此如果是机械硬盘大概在5-20毫秒百万倍的差距如果换成固态硬盘情况会好点普遍都在0.1毫秒以下部分能达到微秒级但也是万倍、十万倍以上的差距。 所以减少IO请求能极大的降低响应时间。那么我们可以采取什么方法呢 硬盘IO的优化 包括降低硬盘读写频次和采用顺序读写。 对于频繁使用的数据根据业务情况我们可以把它们放到内存中后续都从内存读取速度会快上不少对于需要写入硬盘的数据根据业务情况我们可以先在内存中攒几条达到一定的数据量之后再写入硬盘其实操作系统本身就会缓存写入很多语言写数据到硬盘之后需要做一个flush的操作就是用来实现最终写入硬盘的。我们使用Memcached、Redis等都是这个方案的延伸只不过它们被封装成了独立的远程服务。 采用顺序读写主要针对的是机械硬盘因为机械硬盘挪动悬臂和磁头比较耗时顺序读写可以尽量减少机械移动情况的发生进而提升读写速度。 网络IO的优化 网络IO的延迟一般都要在毫秒级以上对网络IO的优化除了类似硬盘IO优化中的的降低IO频次另外还可以通过使用更高效的传输协议、降低数据传输量等方式进行优化。 对于需要频繁通过网络获取的数据比如访问远程服务、数据库等获取的数据我们可以在本地内存进行缓存访问内存比访问网络要快很多只需要选择合适的缓存存活时间就好了。 对于短时间内大量需要通过网络获取的数据我们可以采用批量获取的方式比如有一个列表列表中的每一条数据都要调用接口去获取某个相同的字段列表有100条数据就要请求网络100次如果批量获取则只需要一次网络请求就把100条数据全部拿到这可以避免大量的网络IO时间消耗显著降低业务处理的响应时间。 我们一般认为http是无状态的但是它的底层是基于TCP协议的这样每次发起http请求时网络底层还是要先建立一个TCP连接然后本次http请求结束后再释放这个连接。你可能听说过TCP的三次握手、四次挥手、TCP包的顺序保证等这些都需要在客户端和服务器端来回多次通信才能完成而且导致http的网络请求效率不高。很多大佬也看到了这个问题所以搞出来了http2、http3让http使用长连接、跑在UDP协议上。我们在编程时选择基于http2或者http3的通信库就可以降低网络延迟如果有必要我们也可以直接使用TCP或者UDP编写网络程序。 另外如果我们只需要网络接口返回的部分数据就没必要传输完整的数据数据在网络底层通过分包、分帧的方式进行传输数据越大包、帧的数量越多传输消耗的时间也越长。对于减少数据传输量除了业务上的约定我们也可以通过一些序列化方式进行优化比如采用Protobuf替代JSON通常可以生成更短的消息内容。 谈到Protobuf不得不提一下gRPC它使用了Protobuf进行序列化处理还使用了更新的http协议根据我的不严格测试同样的服务相比HTTPJSON的方式gRPC的网络延迟可以降低1个数量级。 内存IO的优化 我们在上边的分析中是把CPU和内存看做一个整体的其实它们内部的通信延迟也不可忽视我们也有一些方法来优化对内存的访问包括下边一些技术 零拷贝这种技术要解决的问题是数据在内核态和用户态之间的重复存放问题。 什么是内核态和用户态操作系统有两个主要的功能一是管理计算机上的所有软件程序主要是CPU和内存资源的分配二是为一些基础计算能力提供统一的使用接口比如网络、硬盘这种为了实现这两大能力操作系统就需要有一些管理程序来处理这些事这些基础管理程序就运行在内核态而操作系统上的其它程序则运行在用户态。同时基于安全考虑内核态的程序以及它们使用的资源必须要保护好不能随便访问所以内核态的数据就不能让用户态直接访问用户程序访问相关数据时得先复制到用户态。 举个例子当程序从硬盘读取数据时程序先要调用内核程序内核程序再去访问硬盘此时数据先读到内核内存空间中然后再拷贝到用户程序定义的内存空间中。如果我们能把用户态和内核态之间的拷贝去掉就是零拷贝技术了。 很多语言和框架中都提供了这种零拷贝的能力。比如Java中的Netty框架在发送文件时可以直接在内核态将文件数据发送到网络端口而不需要先一点点读到用户态再一点点写到内核态的网络处理程序。 其实Netty还使用了一些非传统的零拷贝技术这包括直接内存和复合缓冲。直接内存是Java程序向操作系统直接申请一块内存这块内存的数据可以直接与底层网络传输进行交互不需要在内核态和用户态之间进行拷贝。复合缓冲是Netty定义了一个逻辑上的大缓冲区把网络传输中的多段小数据组合在一起外部读取数据时只需要和它打交道这样比较优雅实际也没有产生内存拷贝。 CPU缓存行这种技术主要是充分利用CPU缓存减少CPU对内存的访问。 什么是CPU缓存在上边谈到IO设备的访问时间时我们说到硬盘的访问时间是CPU执行单条指令消耗时间的万倍以上其实内存的访问时间相较CPU内部也有百倍左右的差距所以CPU中搞了一个缓存将最近需要的数据或指令先加载到缓存中后续执行的时候都通过缓存进行读写。缓存与内存相比速度更快访问一次可能只需要若干纳秒但是成本也更高数量比较少所以没有完全代替内存。 我们要减少内存的访问就需要数据在CPU缓存中维持的时间更久。CPU缓存是以行为基本单位的如果一行中保存了多个变量的数据它们可能就会相互影响比如其中一个变量更新的频率很高这个缓存行可能就会频繁失效导致和内存的频繁同步而这个缓存行中的另一个变量就受到了连带影响。解决这个问题可以让变量独占一个缓存行比如前后使用一些空位进行填充Java中的Disruptor库就是采用了这种方案。 绑定CPU与内存这种技术主要解决CPU或者内核访问不同内存时的速度差异问题。 我们知道计算机中可以安装多块CPU、多条内存在同一块CPU中也可能存在多个核心也就是俗称的多核处理器这些CPU、核心到每条内存的距离可能是不相同的CPU访问距离近的内存速度就会快些访问距离远的内存速度就会慢些。对于计算机的使用者来说我们肯定是希望程序的运行速度越快越好即使做不到最快也不希望程序时快时慢这样容易导致拥堵。 为了解决这个问题计算机发展出了一种称为NUMA的技术NUMA的全称是非一致性内存访问。在这种技术中CPU和内存划分了不同的区域CPU访问本区域的内存时可以直接访问速度十分快CPU访问其它区域的内存时需要通过内部的通道速度会相对慢一些。然后操作系统可以感知到NUMA的区域分布情况并提供了相应的API让应用程序可以将自己使用的内存和CPU尽量保持在同一个区域或者尽量平均分布在不同的区域从而保证了CPU和内存之间访问速度。 1.1.2 优化代码逻辑 在我的编程生涯早期优化程序性能时考虑最多的是这里是不是可以少些几行代码那里是不是可以不使用循环。这些固然可以优化程序的性能但是远没有上边提到的优化IO带来的收益大。因为少执行几条代码只是若干纳秒的节省而少一次IO则是百倍、千倍、万倍的节省。不过当IO没得优化的时候我们也不得不考虑在代码逻辑上下下功夫特别是一些计算密集型的程序。 可以从以下几个方面着手优化 算法优化选择更有效率的算法来减少计算时间。例如使用快速排序代替冒泡排序数据越多快速排序的算法效率越高节省的时间也越多。 数据结构选择选择更合理的数据结构。例如使用哈希表进行快速查找而不是数组。数组需要遍历查找而Hash表则可以根据下标快速定位。 循环展开将循环操作改为同样的逻辑多次执行。这个好处有很多首先可以减少循环条件判断和跳转的处理然后有利于提高CPU内部的指令预测准确度、指令并行度以及提高CPU缓存的命中率。 延迟计算只有在需要结果的时候才进行计算避免不必要的计算。很多函数式编程的方法中都大量使用了这一技术比如C#中针对列表的LINQ查询只有在真的需要处理列表中某条数据的时候才执行相应的查询算法而不需要提前对列表中的所有数据进行处理。在Web前端也有很多的延迟处理方案比如图片的懒加载只在需要显示图片的时候才去加载对于图片比较多的页面可以大幅提升页面的加载速度。 异步计算程序只处理事务中的关键部分然后就给调用方返回一个响应边缘事务或者慢速部分的处理通过发送消息的方式由后台其它程序慢慢处理。比如很多的秒杀、抢购程序都采用这种方案先把用户的请求收下来只是发到一个待处理的队列然后就给用户反馈已经收到你的请求、正在处理中同时后台再有若干程序按照顺序处理队列中的消息处理完毕后再给用户反馈最终的结果。 避免重复计算通过缓存计算结果来避免重复执行需要花费大量时间的计算。 并行计算利用多线程或多进程来同时执行任务特别是有多核CPU的时候。 在进行优化的时候我们也要区分重点可以先通过代码分析工具找到执行最频繁的部分然后再进行优化。 1.1.3 使用更好的编译器 好的编译器就像是一流的厨师能用更少的步骤做出美味的菜。 上边提到优化代码逻辑时可以采用“循环展开”的方法其实这件事完全可以交给编译器去做好的编译器可以代替人工来完成这件事程序员就有更多时间来思考业务逻辑。 除了“循环展开”编译器还可以做“循环合并”针对同一组数据如果代码中编写了多次循环迭代并且迭代的方法都是一样的编译器可以将多次迭代合并成一次。 编译器还可以做很多优化比如移除无用的代码、内联函数减少压栈、计算常量表达式的值、使用常量替换变量、使用更优的算法和数据结构、重排程序指令以利于CPU并行执行等等。 不过大多数情况下比如使用Java、.NET时我们使用官方推荐或者IDE集成的编译器就足够了只有在针对一些特定计算平台或者特定的领域时我们才需要进行选择。 1.2 硬件层面的升级 在硬件层面要缩减程序的运行时间也就是更换运行速度更快的硬件比如使用主频更高的CPU1GHZ的CPU每条指令的执行时间是1纳秒如果更换为3GHZ的CPU每条指令的执行时间可以降低到0.3纳秒。 1.2.1 提升CPU性能 提升CPU就像是给餐厅请一个更快的厨师他做菜的速度更快。 提升主频上边我们已经说过提升主频可以降低每条指令的执行时间。但是主频的提升不是无限的因为主频越高电子元器件的散热、稳定性、成本等都会成为新的问题所以必须在这其中进行平衡。 指令并行技术现代CPU中已经产生了很多并行执行指令的技术包括乱序执行、分支预测技术、超标量技术、多发射技术等这些都像是厨师在同时处理多个菜品而不是一个个来这自然能降低整体的响应时间当然CPU肯定也要兼顾逻辑顺序。 CPU缓存现代CPU中很大一块面积都是用来放置CPU缓存的上文在【内存的IO优化】中我们提到过使用CPU缓存可以大幅降低CPU读取指令的时间消耗同时我们还需要注意到CPU存在多个核心时数据可能会被加载到不同的核心缓存中数据在不同缓存中的同步也是一项很有挑战的工作因此足够大的CPU缓存和足够好的CPU缓存更新机制对于降低响应时间也很关键。 增加核心主频无法大幅提升时可以在CPU中多增加几个核心每个核心就是一个独立的处理器不同的线程、进程可以并行运行在不同的核心上这样也可以降低争抢并行度越高程序的执行时间相对也越低。 我们在选择CPU时应该结合业务特点综合考虑以上这些方面。 1.2.2 跳过CPU的技术 理论上计算机中所有的计算都是CPU来处理的不过我们也可以让它只处理最重要的事一些不太重要的事就授权给其它部件处理就像厨房中洗菜、切菜这些事都交给小工去处理大厨专注于炒菜可以让出菜的速度更快。 在计算中有一种DMA技术就是干这个事的比如使用支持DMA的网卡时网卡可以将数据直接写入到一块内存区域等写满了再通知CPU来读取而不是让CPU一开始就从网卡一点点读取这会大幅提高网络数据处理的效率。因为网卡的速度相对CPU要慢的多没必要在这里耗着等数据接收到内存之后CPU再和内存打交道速度就会快很多了。 对于需要集成IO设备进行处理的程序我们可以尽量选择支持DMA技术的硬件。 1.2.3 使用专用硬件 这个就像做饭时使用不同的厨具虽然我们也可以在汤锅里炒菜但是总不如炒锅用的顺手用的顺手就可以做到更快的速度。 在计算机中CPU是一种通用计算器它可以进行各种运算但是通用也有通用的坏处那就是干一些事的时候效率不高比如图像处理、深度学习算法的运算这些运算的特点就是包含大量的向量计算CPU执行向量运算的效率比较低。 为了加速图像处理科技工作者们搞出了GPU效率有了很大的提升计算速度飞起。GPU一开始是专门用于图形计算的图形计算的主要工作就是向量运算而深度学习也主要是向量计算所以GPU后来也被大量用于深度学习。再到后来科技工作者又搞出来了TPU这种设备更加有利于深度学习的计算。 另外针对一些需要频繁读写硬盘的程序比如数据库程序我们也推荐使用固态硬盘代替机械硬盘因为固态硬盘的访问延迟相比机械硬盘会低1个或多个数量级这会大幅降低数据读写的延迟。 所以针对不同的计算特点我们可以选择更专用的硬件来加速程序的处理这是个不错的方案。
---
当然使用性能更高的硬件需要付出更多的成本需要仔细评估。以前技术开发领域流传过有一句话不要对程序做过多的优化升级下硬件就行了。这是因为当时升级硬件的成本要远低于优化程序的时间成本不过随着互联网的发展人们对性能的追求越来越高升级硬件的难度和成本也越来越高这句话变得不是那么可靠。从Go、Rust等语言的流行Java、.NET等对原生编译的支持我们也可以感受到这个趋势硬件资源开始变得稀缺了。 吞吐量 吞吐量就像餐厅一天能服务多少客人。 在计算机里它指的是单位时间内处理的请求数、数据量或执行的指令数。 2.1 缩短响应时间 缩短响应时间自然能提高吞吐量就像提高厨师做菜速度能让更多客人吃到菜一样。 上文已经介绍了响应时间的很多优化方法这里及不重复了。 但是响应时间对吞吐量的影响不一定总是正面的。降低响应时间有可能会增加资源的使用比如我们把原本放在硬盘中的数据都放到了内存中然后就没有足够的内存用来创建新的线程服务器就无法接收更多的请求吞吐量也就无法提升。 2.2 增加并发能力 2.2.3 增加资源 这是最直接的方法就像买更多的炉子和锅就能同时做更多的菜。 在计算机领域我们可以购买更多的服务器、更强劲的CPU或GPU、更大的内存、读写能力更强的IO设备、更大的网络带宽等等。这些可以让程序在单位时间内接收更多的请求、以及更大的并发处理能力也就增加了系统的吞吐量。当然在具体的增加某种资源之前我们需要先找到系统的瓶颈比如内存使用经常达到90%我们就增加内存空间。 需要注意增加资源虽然可以提升系统的吞吐量但是这也会有一个临界点越过这个临界点之后获得的收益将会低于为此增加的资源成本所以我们应该仔细评估收益和成本再决定增加多少资源。 同时本着节约的精神我们应该追求使用更少的资源来完成更多的工作这样也能产生更多的收益。 2.2.2 利用CPU的先进技术 上文我们在【1.2.1 提升CPU性能】中已经提到过CPU的指令并行技术包括流水线、分支预测、乱序执行、多发射、超标量等它们可以让CPU同时执行多条指令提升CPU的指令吞吐量自然也就提升了程序的处理吞吐量。 这些都是CPU内部的技术优化只要购买性能优良的CPU我们就可以拥有这些能力同时我们在编程时也可以尽量触发指令的并行执行比如上文【1.1.2 优化代码逻辑】中提到的循环展开、内联函数等当然我更推荐选择一个更好的编译器来完成这些优化工作程序员应该多思考下怎么运用技术满足业务需求。
除了CPU微观层面的优化我们还可以利用下CPU的多核并行能力在程序中使用多线程、多进程让程序并行处理从而提升程序的业务吞吐量。 CPU的这些能力就像是多个厨师协作同时准备不同的菜品就能在单位时间内把更多的菜品端上桌。 2.2.1 提高资源利用率 大家可能听说过Go的并发能力特别强简单手撸一个服务就能轻松应对百万并发原因就是因为Go搞出了协程。那么协程为什么这么优秀呢 这是因为程序使用传统的线程模型时线程消耗的时间和空间成本比较高时间成本就是CPU的使用时间空间成本就是内存占用要提升吞吐量只能增加更多的CPU和内存资源资源利用率高不起来具体是怎么回事呢 首先看我们编写的业务程序其实大部分工作都是很多IO操作比如访问数据库、请求网络接口、读写文件等这些IO操作相比CPU指令操作慢了4、5个数量级。使用线程模型时发起IO请求后当前线程使用的CPU要么等着要么切换给其它线程使用等着CPU就是空转切换给其它线程时的成本也比较高总之就是浪费了CPU时间另外在等待IO返回的这段时间内线程不会消失会一直存在而线程占用的内存比较大Windows默认1MLinux默认8M妥妥的站着什么不拉什么。这就是线程的时间成本和空间成本问题。 解决这个问题的第一步是使用异步编程IO操作提交后就把线程释放掉程序也不用在这里等着等IO返回结果时操作系统再分配一个新的线程进行处理。线程少了CPU等待、切换和内存占用也就少了计算资源自然就可以支持更多的请求了吞吐量也就上来了。这就像是厨师在等待一个菜烤制的同时可以去炒另一道菜。 协程则是在异步的基础上更进一步把程序执行的最小单位由线程变成了协程协程分配的内存更小初始时仅为2KB不过它可以随着任务执行按需增长最大可达1GB。同样的8M内存Linux中只能创建1个线程而协程最多则可以创建4096个。我们也就能够理解Go的并发能力为什么这么强了。 总结 性能优化是一个复杂且多面的话题涉及到代码的编写、系统的架构以及硬件的选择与配置。在追求性能的旅途中我们需要掌握的知识有很多既有软件方面的也有硬件方面的很多东西我也没有展开详细讲只是给大家提供了一个引子遇到问题的时候可以顺着它去寻找。 在优化过程中还需要注意性能的提升并非总是线性的我们应当找到系统的瓶颈点有针对性地优化并在资源成本和性能收益之间做出平衡。优化的最终目的是在有限的资源下尽可能地提升程序的响应速度和处理能力。 文章转载自萤火架构 原文链接https://www.cnblogs.com/bossma/p/17946273 体验地址引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/88851.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!