




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、程序优化的5个方向80/20法则:程序执行中,80%的时间消耗在20%的代码上。优化前,我们首先得找到这20%的关键路径;各种语言都有专门的工具来找到这20%的关键路径,比如C+经常用到的gprof;参考 C+的性能优化实践在关键路径上对耗时的计算进行优化;主要的优化方向为:减少重复计算、预先计算、延后计算、降低计算代价、不计算;减少重复计算典型的例子如缓存,将之前相同的计算(查数据库,读写文件)存下来,等待下一次继续使用;适用场景:计算结果有有效期,过段时间后需要再次计算;预先计算对于关键路径中比较耗时的计算,预先计算出来,节省每次计算的成本;· 预先计算出对照表关键路径
2、中需要用到的映射关系对照表,将对照表预先计算,在关键路径中直接取用;· 将计算提前到初始化期间比如,内存分配耗时,将其提前到初始化的时间分配,建立内存池;· 将计算提前到编译期间比如:使用常量表达式,在编译期间将最终值计算出来,节省这部分的运行时开销;相关技术:模版元编程;适用场景:计算出来的值一直有效,无需再次计算;延迟计算将计算耗时延迟到后期,这样,对于异常情况或其它分支情况,在中途就转换,不用再计算;· 有较多分支条件将最耗时的计算延后,这样,可能很多场景在中途就转到其它分支上,不用计算;· 判断条件中的技巧:a|b a&&b如果判
3、断条件比较耗时,将更耗时的放在后面计算;这样,对于a|b,当a成立时,b就不用再计算了;延迟计算的好处在于可能可以不用计算;适用场景:分支条件场景;降低计算代价这是通常能想到的最直接的优化手段,如何能够直接降低计算的代价;- 内存申请从堆上改为栈上动态内存分配昂贵,将内存分配从堆上改为栈上;· 降低灵活性,使用自定制版本的函数代替库函数;· 使用更低级的指令或语言改写;在C+中嵌入汇编语言;使用SSE2等指令集;· 使用更优的算法或数据结构;操作STL容器时,STL中的算法一般比自己手写的算法要高效,尽量使用STL的算法来替换我们的手写算法;参考:STL区间成员函
4、数及区间算法总结高效的使用STL适用场景:这类优化一般是以降低代码可读性为代价的(STL的除外),用于优化的最后阶段;不计算优化的终极方案,不计算;· 业务发现用不到的业务逻辑,废弃的业务逻辑,仍然存在关键路径中的还在执行的;痛快的删除它;· 却掉临时对象开销在我们的代码中,可能会有些临时对象是不知不觉的,而消除临时对象,将节省这部分开销;参考: 消除临时对象以上是单线程关键路径的优化,接下来,我们聊聊扩展到多核,在多线程上的优化;当优化扩展到多核时内容目录:· 任务分解的粒度· 数据竞争· 引入锁· 惊群现象·
5、数据复制· 数据分片当优化扩展到多核时"软件开发没有银弹,我们能做的就是选择和平衡;"上一篇文章我们聊了在单线程下程序优化的5个方向(ref:程序优化的5个方向);当单核优化到极值后,就到了多任务的情况; 想起来很清晰,单个任务分解成多个任务,让多个cpu同时来工作,并行执行,效率自然就上去了; 但,未必就这么简单;任务分解的粒度首先,我们需要确定,我们的单个任务是否可以分解;比如解析很多个文件,这样的任务划分成多个很简单;但如果是一个耗时的串行逻辑计算,后期的计算依赖前期的结果,这样就不好拆分;这种形式可能需要在更高层次上来拆分;数据竞争编程
6、就是计算和数据;计算并行了,但数据还是访问同一份,访问共同的资源会产生资源竞争; 如果不进行控制,可能导致同一份数据重复计算(多个读的场景)或是脏数据的产生(有回写的场景);引入锁为了让数据访问有序进行,需要引入锁来防止脏数据; 控制锁的粒度,是个需要精心考虑的话题; 比如对于大量读少量写的场景,相比一视同仁的加锁,使用读写锁能显著提升效率; 我们日常能接触到的产品中,数据库是个用锁高手,在更新数据的时候,是锁住行,还是列、或是表,不同的粒度性能相差明显;惊群现象考虑这样的场景:多个线程都在等在一个锁,如果可以拿到锁,线程就开始工作(线程池)
7、当锁被释放时,如果唤醒多个线程可能会产生 惊群现象; 解决方案: 使用单线程方案/处理accpet连接 处理等待锁的操作,让任何时刻只有一个线程在等待锁; 更多细节参考: 客户-服务器程序设计方法中 预先创建线程池,每个线程各自accept 一节数据复制让每个线程使用自己的数据,让数据不共有,这样能去掉资源竞争,去掉锁; 将数据复制为多份,减少竞争,各自访问各自的数据; 但这又引入了一个新的问题:如果各个线程回写了数据,如何保证这么数据的一致性? 毕竟它们代表的其实是一份数据;涉及到数据的一致性,多份数据之间的同步又是个难题
8、;数据分片那好,换个思路,不使用数据复制;我们使用数据分片;分片这个思想更容易想到,既然“计算”被划分为多个小任务了,那么数据也可以同样处理; 将数据分片,每份数据存的内容不相同,它们之间没有共同点; 这样,数据访问没有数据竞争,同时由于数据不同,也不涉及到数据一致性同步的问题; 但,分片远远没有想的那么美好; 分片导致了每个线程看到数据不再是全集,而是片段;这就注定了这个线程只能处理这部分的特定数据;这样,线程之间的计算失去了可替换性;某种工作只能在特定的线程上处理; 而如果有个任务需要访问所有的数据,这样就变得更加复杂; 原来,分
9、片之后,我们将难题向上推了,推到线程层面,需要考虑到业务逻辑层面的处理; 这样,可能更加复杂;ok,想要速度更快,使用多核来处理,需要面对更多的问题; 将单机扩展多机集群,涉及到架构层面来看,其实我们的面对的问题是类似的; 参考:大型网站技术架构读书笔记2 - 架构的模式软件开发没有银弹,我们能做的就是选择和平衡;消除临时对象内容目录:· 按值返回· 按值传递参数· 类型不匹配的隐式转换· 连续的对象之间的+操作符· 成员对象的初始化C+的性能优化实践内容目录:· 1 Gprof· 2. gpr
10、of使用步骤· 1.初始化大对象耗时· 2.Map使用不当优化准则:1. 二八法则:在任何一组东西中,最重要的只占其中一小部分,约20%,其余80%的尽管是多数,却是次要的;在优化实践中,我们将精力集中在优化那20%最耗时的代码上,整体性能将有显著的提升;这个很好理解。函数A虽然代码量大,但在一次正常执行流程中,只调用了一次。而另一个函数B代码量比A小很多,但被调用了1000次。显然,我们更应关注B的优化。2. 编完代码,再优化;编码的时候总是考虑最佳性能未必总是好的;在强调最佳性能的编码方式的同时,可能就损失了代码的可读性和开发效率;工具:1 Gprof工欲善其事,必先利
11、其器。对于Linux平台下C+的优化,我们使用gprof工具。gprof是GNU profile工具,可以运行于linux、AIX、Sun等操作系统进行C、C+、Pascal、Fortran程序的性能分析,用于程序的性能优化以及程序瓶颈问题的查找和解决。通过分析应用程序运行时产生的“flat profile”,可以得到每个函数的调用次数,消耗的CPU时间(只统计CPU时间,对IO瓶颈无能为力),也可以得到函数的“调用关系图”,包括函数调用的层次关系,每个函数调用花费了多少时间。2. gprof使用步骤1) 用gcc、g+、xlC编译程序时,使用-pg参数,如:g+ -pg -o test.ex
12、e test.cpp编译器会自动在目标代码中插入用于性能测试的代码片断,这些代码在程序运行时采集并记录函数的调用关系和调用次数,并记录函数自身执行时间和被调用函数的执行时间。2) 执行编译后的可执行程序,如:./test.exe。该步骤运行程序的时间会稍慢于正常编译的可执行程序的运行时间。程序运行结束后,会在程序所在路径下生成一个缺省文件名为gmon.out的文件,这个文件就是记录程序运行的性能、调用关系、调用次数等信息的数据文件。3) 使用gprof命令来分析记录程序运行信息的gmon.out文件,如:gprof test.exe gmon.out则可以在显示器上看到函数调用相关的统计、分析
13、信息。上述信息也可以采用gprof test.exe gmon.out> gprofresult.txt重定向到文本文件以便于后续分析。以上只是gpro的使用步骤简介,关于gprof使用实例详见附录1;实践我们的程序遇到了性能瓶颈,在采用架构改造,改用内存数据库之前,我们考虑从代码级入手,先尝试代码级的优化;通过使用gprof分析,我们发现以下2个最为突出的问题:1.初始化大对象耗时分析报告:307 6.5% VOBJ1:VOBJ1240038VOBJ1在整个执行流程中被调用307次,其对象初始化耗时占到6.5%。这个对象很大,包含的属性多,属于基础数据结构;在程序进入构造函数函数体之前
14、,类的父类对象和所有子成员变量对象已经被生成和构造。如果在构造函数体内位其执行赋值操作,显示属于浪费。如果在构造函数时已经知道如何为类的子成员变量初始化,那么应该将这些初始化信息通过构造函数的初始化列表赋予子成员变量,而不是在构造函数函数体中进行这些初始化。因为进入构造函数函数体之前,这些子成员变量已经初始化过一次了。在C+程序中,创建/销毁对象是影响性能的一个非常突出的操作。首先,如果是从全局堆中生成对象,则需要首先进行动态内存分配操作。众所周知,动态分配/回收在C/C+程序中一直都是非常耗时的。因为牵涉到寻找匹配大小的内存块,找到后可能还需要截断处理,然后还需要修改维护全局堆内存使用情况信
15、息的链表等。解决方法:我们将大部分的初始化操作都移到初始化列表中,性能消耗降到1.8%。2.Map使用不当分析报告:89 6.8% Recordset:GetFieldRecordset的getField被调用了89次,性能消耗占到6.8%;Recordset是我们在在数据库层面的包装,对应取出数据的记录集;(用过ADO的朋友很熟悉);由于我们使用的是底层c+数据库接口,通过对数据库原始api进行一层包装,从而屏蔽开发人员对底层api的直接操作。这样的包装,带来的好处就是不用直接与底层数据库交互,在代码编写方面方便不少,代码可读性也很好;带来的问题就是性能的损失;分析:(2点原因)1)在Get
16、Field函数中,使用了map“a”来查询数据,如果找不到“a”,则map会自动插入key ”a”,并设value为0;而m.find(“a”)不会自动插入上述pair,执行效率更高;原有逻辑:1234567891011121314151617 string Recordset:GetField(const string &strName) int nIndex; if (hasIndex=false)
17、60; nIndex = m_nPos; else nIndex = m_vSortm_nPos.m_iorder; if (m_fields
18、strName=0) LOG_ERR("Recordset:GetField:"<<strName<<" Not Find!"); return m_recordsnIndex.GetValue(m_fieldsstrName - 1) ;改造后的逻辑:1234567
19、; string Recordset:GetField(const string &strName) unordered_map:iterator iter = m_fields.find(strName); if (iter = m_fields.end() LOG_ERR("Recordset:Get
20、Field "<< strName <second - 1) ;调整后的Recordset:GetField的执行时间约是之前的1/2;且易读性更高;2)在Recordset中,对于每个字段的存储,使用的是map m_fields; g+中的stl标准库中默认使用的红黑树作为map的底层数据结构;通过附录中的文档2,我们发现其实有更快的结构, 在效率上,unorder map优于hash map, hash map 优于 红黑树;如果不要求map有序,unordered_map 是更好的选择;解决方法:将map结构换成unordered_map,性能消耗降
21、到1.4%;总结我们修改不到30行代码,整体性能提升10%左右,效果明显;打蛇打七寸,性能优化的关键在于找准待优化的点,之后的事,也就水到渠成;附录:附1:prof工具介绍及实践附2: map hash_map unordered_map 性能测试Redis数据导入工具优化过程总结内容目录:· 背景· 优化效果· 用到的工具· 优化过程· 满足生产要求Redis数据导入工具优化过程总结背景使用C+开发了一个Redis数据导入工具 从oracle中将所有表数据导入到redis中; 不是单纯的数据导入,每条oracle
22、中的原有记录,需要经过业务逻辑处理, 并添加索引(redis集合); 工具完成后,性能是个瓶颈;优化效果使用了2个样本数据测试: 样本数据a表8763 条记录; b表940279 条记录;优化前,a表耗时11.417s; 优化后,a表耗时1.883s;用到的工具gprof, pstrace,time使用time工具查看每次执行的耗时,分别包含用户时间和系统时间; 使用pstrace打印实时运行,查询进程主要的系统调用,发现耗时点; 使用gprof统计程序的耗时汇总,集中精力优化最耗时的地方; 使用简介:
23、1.对g+的所有编辑和连接选项都必须要加上-pg(第一天由于没有在连接处加上-pg选项,导致无法出统计报告); 2.执行完程序后,本目录会产生gmon.out文件; 3.gprof redistool gmou.out > report,生成可读文件report,打开report集中优化最耗时的函数;优化过程优化前11.417s:time ./redistool im a a.csvreal 0m11.417suser 0m6.035ssys 0m4.782s (发现系统调用时间过长)文件内存映射系统调用时间过长,主要是文件读写,初步考虑是读取文件时,调用api次数过于频繁; 读取样本采用的是文件fgets一行行的读取,采用文件内存映射mmap后,可直接使用指针操作整个文件内存快;日志开关提前改进了文件读写后,发现优化效果比较有限(提高了2s左右);fgets是C的文件读取库函数,相比系统read(),是带了缓冲区了,应该不会太慢(网上有人测试,文件内存映射相比fgets()能快上一个数量级,感觉场景应该比较特殊);之后通过pstrace工具发现log.dat打开次数过多;原来是调试日志的开关写到了后面,导致 调试日志都是会打开日志文件open("log.dat"); 将日志开关提前;
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年度专业车库租赁与物业管理合同
- 服装批发市场垃圾清运合同
- 2025年度多人共同经营网店借款及利润分配合同
- 二零二五年度玉器珠宝市场拓展与区域代理合同
- 2025年度安全无忧型个人租房合同
- 2025年度企业节能减排改造补贴协议书
- 2025年度员工心理健康关怀上班协议合同全新版
- 2025年度文化场馆设施维护劳务协议书
- 2025年度影视演员场记助理职业素养培训聘用合同
- 2025年佳木斯职业学院单招职业技能测试题库新版
- 2025年施工项目部《春节节后复工复产》工作实施方案 (3份)-75
- 矿山安全生产工作总结
- 小学教师培训课件:做有品位的小学数学教师
- U8UAP开发手册资料
- 监护人考试20241208练习试题附答案
- 证券公司装修施工合同工程
- 人教版PEP三年级到六年级单词以及重点句型
- 2024-2024年上海市高考英语试题及答案
- 中建总承包项目高支模专项施工方案含计算书
- 酒店住宿服务合同三篇
- 学校疫情防控学校传染病疫情及突发公共卫生事件报告制度
评论
0/150
提交评论