性能优化实践:一行代码性能提升几十倍?

这篇具有很好参考价值的文章主要介绍了性能优化实践:一行代码性能提升几十倍?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

性能优化实践:一行代码性能提升几十倍?,性能优化

Part1

问题背景

手头本来有个很重要的性能优化工作,我也一直在提高它的优先级。毕竟按照四象限时间管理法则,重要的事情要先做。

性能优化实践:一行代码性能提升几十倍?,性能优化

问大家一个问题:给你一个「重要不紧急」的事情和「紧急不重要」的事情,你先做哪一个?

在一般的互联网公司,大家都非常忙碌。活儿是永远干不完的。这时候,我建议先做重要的事情。试想:一个人永远都在做「紧急不重要」的事情,他的产出必然是非常低的。这就是为什么「重要不紧急」在第二象限,仅仅排在「重要且紧急」后面。

道理总结起来容易,做起来难。特别是对于性能优化这种工作内容,需要静下心来不被打扰。所以真正开始分析的时候,已经有些晚了。这个要优化的问题,本来是有一个团队在负责的,人家的优化已经进入了尾声。我这时候才开始,如果最终分析结果是要大改,这就是我的责任,没有更早的介入,让大家做了很多的无用功。

所以对于这个性能优化,对我自身提出的硬性要求是:
1、效果要明显
2、改动要小,最好能用上之前同事做优化时添加代码的一些成果

我得知这个性能优化做完之后要给大家做一个分享。前段时间在做本地调试工具kt-connect。技术问题已经告一段落,目前是推广阶段。附加要求是:能否同时做一个本地调试工具的软推广植入?

目标有了,该怎么做呢?

Part 2

性能优化方法


在《性能之巅》这本书中,我印象最深的性能优化方法,包括三种推荐的方法和三种要避免的方法,我称之为:三正三反。

三正

科学法:采用以下框架 问题->假设->预测->实验->分析

USE方法:USE是utilization、saturation和error的首字母,意思是:对于每一个资源,检查使用率、饱和程度和错误情况。它的目的是尽早地进行性能检查,并发现系统瓶颈。

向下挖掘分析法:开始在高级别检查问题,然后依据之前的发现缩小关注的范围,忽视那些无关的部分,更深入发掘那些相关的部分。这种方法经常和5Why分析法配合使用。

三反

街灯讹:用户选择熟悉的观测工具来分析性能,这些工具可能是从互联网上找到的,或者是用户随意选择的,仅仅想看看会有什么结果出现。这样的方法可能命中问题,也可能忽视很多问题。

随机变动讹:用户随机猜测问题可能存在的位置,然后做改动,直到问题消失。这种方法非常耗时而且可能做出的调整不能保持长期有效。例如,一个应用程序的改动规避了一个数据库或者操作系统的bug,其结果是可以提升性能,但是当这个bug被修复后,程序这样的改动就不再有意义,关键是没有人真正了解这件事情。

责怪他人讹:步骤如下
  1、找到一个不是你负责的系统或环境的组件
  2、假定问题是与那个组件相关的
  3、把问题扔给负责那个组件的团队
  4、如果证明错了,返回步骤1

另外,在本地调试的一个极大的好处是可以集成工具,这次使用Intelij+JProfiler来做功能和性能诊断。

实际问题中,怎样运用这些方法和工具呢?

Part 3

分析过程


问题

有一个核心接口,一次调用涉及多次数据库查询,而且查询返回数据量在几百条的量级。网络正常情况下:第一次请求响应时间在1s~3s之间。

性能优化实践:一行代码性能提升几十倍?,性能优化

这段时间,负责团队进行了性能优化,在数据库操作的地方加了redis缓存,后面连续请求的话,响应时间在630ms~680ms之间。

性能优化实践:一行代码性能提升几十倍?,性能优化

因为这都是本机调试时的测试结果,在服务器上运行耗时要短很多。因为二者的网络架构和经过的网络节点都不同,耗时差异主要在网络延迟。但服务器上运行也要在150ms。对于性能本机与服务器上的结果可以粗略做一个换算:680/150≈4.5。

对于核心接口,我们TP90的目标是100ms,在我本机执行至少要低于450ms。怎样进一步优化响应耗时呢?

假设

之前和负责团队聊过,负责团队分析认为:之前的性能瓶颈主要在数据库。所以采用Redis访问代替数据库查询的优化方案。从效果上看响应耗时降低到了原来的1/3~1/4。通过DBA的给力配合,在DEV环境,把慢查询日志从之前记录1s以上的慢查询修改为30ms以上的慢查询。通过以下SQL可以查询到慢查询日志情况:

select DATE_ADD(start_time, INTERVAL 8 HOUR)as start_time,db,user_host,query_time,lock_time,rows_sent,rows_examined,CONVERT(sql_text USING utf8mb4) AS slow_log from mysql.slow_log where db='XXX' order by start_time DESC  limit 1000

值得注意的是默认时间记录的是UTC时间,与北京时间相差8小时,所以SQL中包含了对显示时间的转换。

性能优化实践:一行代码性能提升几十倍?,性能优化

从截图可以看出,慢查询的SQL耗时在50ms左右,都是一条SQL,请求中包含的其他SQL耗时都在30ms以下。

从程序端来看:对这条50ms的SQL调用记录时间,发现第一次请求走数据库时耗时在500ms左右,后续走redis缓存时调用耗时在200ms左右。换算成服务器的耗时执行数据库SQL耗时约为110ms,redis缓存耗时约为45ms。我之前见过缓存中间件做的好的,从Redis里取数据加上网络开销也能维持在1ms之内。这个耗时太长了,我怀疑开销花在网络上。

预测

为了降低网络开销,可以将使用Redis集中式缓存改成使用本地内存缓存。通过观察日志计算,抛去从数据库和缓存取数据的开销,其他时间开销在20多毫秒。据此预测,如果用内存缓存响应延迟可维持在35ms以下。

实验

程序中用了Spring的Cacheable做缓存。

@Cacheable(value = "get#300#30",
            key = "",
            cacheManager = "autoRefreshRedisCacheManager")

修改时只需要修改cacheManager的实现即可。

原来的实现方式是使用redis缓存:

public RedisCacheManager autoRefreshRedisCacheManager(
            @Qualifier("cacheRedisTemplate") RedisTemplate<String, Object> redisTemplate) {
    RedisCacheWriter redisCacheWriter =
            RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
            .serializeValuesWith(
                    RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
    return new AutoRefreshRedisCacheManager(redisTemplate, redisCacheWriter, redisCacheConfiguration);
}

这里就涉及到「面向对象设计原则」中的「里氏替换原则」了。这里返回值不建议直接用实现类,而应该返回接口,方便进行替换:

public CacheManager autoRefreshRedisCacheManager() {
    return new ConcurrentMapCacheManager();
}

运行看效果:

性能优化实践:一行代码性能提升几十倍?,性能优化

从效果上看响应时间从几百毫秒降低至几十毫秒,有数量级上的质变,效果还是很显著的。

分析

  • 本地缓存效果这么好,为什么业界偏向于用集中式缓存呢?

集中式缓存可以提供更好的数据一致性,因为所有应用程序实例都访问同一个缓存服务器,减少了数据不一致的可能性;它还通常支持数据的持久化,可以将数据存储在硬盘上,即使缓存服务器重启也能恢复数据;如果数据量大,或者一直不断增长,本地缓存就不合适,甚至可能会服务的运行造成影响,这种情况下,集中式缓存可以通过专用的缓存服务器来处理大量数据,并具有更好的扩展性和性能。

  • 本次的场景是否需要较强的数据一致性和持久化存储呢?

改造之前本身也是定时任务来刷新的,业务场景允许一定的数据不一致。如果数据由于重启等原因失效,只需要从数据库重新拉取,不需要持久化存储。

  • 本次的场景有没有什么副作用?比如缓存数据太多,占用大量内存?

为了尽早发现问题,可以使用USE方法,观察系统的指标。针对本次的场景,因为接口相当于返回的配置数据。如果对数据库的数据只缓存全量,总缓存数据量通过计算吃掉的内存不超过1M,问题不大。但是目前的修改方式,会根据入参做不同的缓存。缓存数据是要成笛卡尔积增长的。加上用户输入非法数值。会不会打满内存?还是默认的内存淘汰策略就可以搞定?

对此我进行了测试,打开JProfiler的录制功能。添加一个测试类,类里将各种参数的笛卡尔积都调用一遍,还加了一些随时数作为入参的来模拟用户输入错误的情况。观察内存和响应时间。下面两张图中,绿色线是CPU占用的情况,蓝色是内存占用的情况。

这张图是修改前使用redis缓存:

性能优化实践:一行代码性能提升几十倍?,性能优化

这张图是修改后使用本地缓存:

性能优化实践:一行代码性能提升几十倍?,性能优化

从结果看,对内存并没有很大影响。先运行这个测试类,因为把所有的情况都已经缓存了,所以运行速度也特别快。

但是如果没有运行测试类,改变一个参数,第一次调用因为缓存不存在,还是会慢的。为了减少缓存占用,同时提高用户不同参数输入时的响应时间,建议将这些配置信息不要按照参数每次去取,而是一次性取出并缓存。其他参数使用内存过滤。

  • 还有没有其他可以提高性能的优化点?

程序中还存在着与入参无关的获取数据操作,耗时也不短,可以进行异步获取,提高响应速度。

  • 有没有发现其他问题?

 其中有一步要分成几百个子任务,经同事实验发现,使用异步时,启用6个线程速度最快。在此基础上增加线程效率反而下降,这个需要调查原因,找到竞争的资源。

Part 4

总结

这次性能优化整体采用科学法的框架,结合USE方法排查隐患和向下挖掘分析法进行问题分析。过程中使用了业界常用的分析工具比如慢日志、JProfiler来做数据支撑。很多工作可以本地编写完了直接扔到服务器上测试。但是使用本地调试和扔到服务器上测试,养成的习惯和解决问题的方式,思考问题的缜密程度都截然不同。这些最终都决定了一个人的编程能力。

本篇文章中缓存使用的是ConcurrentMap。这个从性能上要比咱们耳熟能详的本地缓存大咖:Caffeine、Guava Cache要好。但只适用于测试,不建议生产环境使用。因为生产环境要考虑长期运行时的内存管理等问题。ConcurrentMap将存储所有存入的数据(本次测试场景入参是笛卡尔积之后也只有不到1千种情况,所以可以用),如果不引进额外的管理机制会导致OOM等问题。

以下是生产环境常用的本地缓存类库性能对比:

性能优化实践:一行代码性能提升几十倍?,性能优化

性能最好的Caffeine,业界是这样评价的:

Caffeine是基于Java 1.8的高性能本地缓存库,由Guava改进而来,而且在Spring5开始的默认缓存实现就将Caffeine代替原来的Google Guava,官方说明指出,其缓存命中率已经接近最优值。实际上Caffeine这样的本地缓存和ConcurrentMap很像,即支持并发,并且支持O(1)时间复杂度的数据存取。二者的主要区别在于:

ConcurrentMap将存储所有存入的数据,直到你显式将其移除;
Caffeine将通过给定的配置,自动移除“不常用”的数据,以保持内存的合理占用。
因此,一种更好的理解方式是:Cache是一种带有存储和移除策略的Map。

即:业界生产使用的最好的本地缓存组件的性能标杆是ConcurrentMap。

性能优化实践:一行代码性能提升几十倍?,性能优化文章来源地址https://www.toymoban.com/news/detail-860983.html

到了这里,关于性能优化实践:一行代码性能提升几十倍?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 编译代码性能优化实践:理解循环展开(pragma unroll)

    引言: CUDA的矩阵乘优化经常见到 pragma unroll 的使用,本文通过简单的示例,展示了CPU和CUDA对循环展开前后的性能表现,来通俗理解循环展开的优化策略。         简单理解:将代码中的for循环展开,减少循环次数;循环展开的本质是,利用CPU指令级并行,来降低循环的

    2024年03月27日
    浏览(37)
  • 实践指南-前端性能提升 270%

    当我们疲于开发一个接一个的需求时,很容易忘记去关注网站的性能,到了某一个节点,猛地发现,随着越来越多代码的堆积,网站变得越来越慢。 本文就是从这样的一个背景出发,着手优化网站的前端性能,并总结出一套开发习惯,让我们在日常开发时,也保持高性能,而

    2024年02月13日
    浏览(30)
  • .NET使用EF批量插入数据,一行代码性能飙升!

    背景 小编最近接到一个任务,批量获取内部网站用TXT生成的日志,在闲时把日志插入到MySql数据库做分析。为了快速开发小编选择了Entity Framework Core,很快开发完成了。测试数据不是很多,批量插入数据很快完成,效率很高。但是部署到线上问题来了,最开始也挺快,越到后

    2024年02月13日
    浏览(43)
  • 实践指南-前端性能提升 270% | 京东云技术团队

    当我们疲于开发一个接一个的需求时,很容易忘记去关注网站的性能,到了某一个节点,猛地发现,随着越来越多代码的堆积,网站变得越来越慢。 本文就是从这样的一个背景出发,着手优化网站的前端性能,并总结出一套开发习惯,让我们在日常开发时,也保持高性能,而

    2024年02月13日
    浏览(32)
  • 【Qt 性能优化】 理解与优化Qt信号槽机制 - 提升应用性能的关键策略

    在这个科技日新月异的时代,软件开发不仅仅是编写代码,更是一种艺术。正如著名计算机科学家 Edsger Dijkstra 所说:“计算机科学并不仅仅关于机器,而是更多地关于人的智慧。” Qt框架,作为一个深受广大开发者喜爱的跨平台应用程序和用户界面开发框架,其核心机制之

    2024年02月20日
    浏览(44)
  • 优化索引粒度参数提升ClickHouse查询性能

    当对高基数列进行过滤查询时,总是希望尽可能跳过更多的行。否则需要处理更多数据、需要更多资源。ClickHouse缺省在MergeTree表读取8192行数据块,但我们可以在创建表时调整该 index_granularity 参数。本文通过示例说明如何调整该参数优化查询性能。 下面示例,创建表并插入

    2024年02月11日
    浏览(40)
  • 提升性能:QML Canvas 绘图优化技巧

    减少绘制操作 : 当我们有一个动态更新的图形,例如实时更新的数据可视化图表,可以通过设置一个定时器来控制更新频率,而不是每次数据更新都重新绘制整个图形。 使用硬件加速 : 通过将Canvas的 renderTarget 属性设置为 Canvas.FramebufferObject 来启用硬件加速: renderTarget: 枚

    2024年04月17日
    浏览(32)
  • 提升应用性能的关键步骤——UniApp性能优化策略与技巧详解

    「作者主页」 :雪碧有白泡泡 「个人网站」 :雪碧的个人网站 chatgpt体验地址 描述:代码压缩和混淆是常用的性能优化手段。通过减小JavaScript、CSS和HTML文件的大小,可以降低加载时间和网络传输。 解释: 在构建UniApp应用时,确保开启代码压缩和混淆选项。 使用工具(如

    2024年02月03日
    浏览(36)
  • 前端面试:【网络协议与性能优化】提升Web应用性能的策略

    嗨,亲爱的Web开发者!构建高性能的Web应用是每个开发者的梦想。本文将介绍一些性能优化策略,包括资源加载、懒加载和CDN等,以帮助你提升Web应用的性能。 1. 性能优化策略: 压缩资源: 使用Gzip或Brotli等压缩算法来减小CSS和JavaScript文件的大小,加快加载速度。 合并文件

    2024年02月11日
    浏览(36)
  • 详解视频美颜SDK:算法优化与性能提升

    众所周知,视频美颜SDK的算法优化和性能提升至关重要。下文小编将与大家深度探讨视频美颜SDK的算法原理,以及近期的性能优化措施。 一、常见用法 视频美颜SDK对人脸进行识别,并附加适当的美颜效果。例如: 1.识别、关键点 2.肤色调整 3.磨皮处理 4.瘦脸大眼 二、性能提

    2024年02月03日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包