当前位置:  开发笔记 > 编程语言 > 正文

最后的性能优化策略

如何解决《最后的性能优化策略》经验,为你挑选了27个好方法。

这个网站上已经存在很多性能问题,但是我发现几乎所有这些都是特定于问题且相当狭窄的问题.几乎所有人都重复这些建议,以避免过早优化.

我们假设:

代码已经正常工作

所选择的算法对于问题的情况已经是最佳的

已经测量了代码,并且已经隔离了违规的例程

所有优化尝试也将被测量,以确保它们不会使事情变得更糟

我在这里寻找的是在一个关键算法中挤出最后几个百分点的策略和技巧,除此之外别无他法.

理想情况下,尝试使答案语言不可知,并在适用的情况下指出建议策略的任何缺点.

我将使用我自己的初步建议添加回复,并期待Stack Overflow社区可以想到的任何其他内容.



1> Mike Dunlave..:

好的,你将问题定义为似乎没有太大改进的地方.根据我的经验,这是相当罕见的.我试着在十一月'93解释这个布斯博士的文章中,通过从传统设计良好的非平凡的程序,没有明显的浪费,并开始服用它通过一系列的优化,直到它的挂钟时间从48秒减少秒到1.1秒,源代码大小减少了4倍.我的诊断工具就是这个.变化的顺序是这样的:

发现的第一个问题是列表集群(现在称为"迭代器"和"容器类")的使用占了一半以上的时间.用相当简单的代码替换它们,将时间缩短到20秒.

现在最大的时间接受者更多的是建立名单.作为一个百分比,它之前没那么大,但现在是因为更大的问题被删除了.我找到了加速它的方法,时间下降到17秒.

现在很难找到明显的罪魁祸首,但是我可以做一些较小的琐事,时间下降到13秒.

现在我好像已经撞墙了.样品正在告诉我它到底在做什么,但我似乎找不到任何可以改进的东西.然后,我在其事务驱动结构上反思程序的基本设计,并询问它所执行的所有列表搜索是否实际上都是由问题的要求强制执行的.

然后,我偶然发现从较小的集源的重新设计,其中程序代码实际上是(通过预处理宏)生成,其中程序没有不断搞清楚事情的程序员知道有相当的可预见性.换句话说,不要"解释"要做的事情的顺序,"编译"它.

重新设计完成后,将源代码缩小了4倍,时间缩短为10秒.

现在,因为它变得如此之快,所以很难进行抽样,所以我给它做了10倍的工作量,但以下时间是基于原始工作量.

更多的诊断表明,它正在花时间进行队列管理.内嵌这些将时间缩短到7秒.

现在一个重要的时间是我一直在做的诊断印刷.冲洗 - 4秒.

现在最大的时间是调用mallocfree.回收对象 - 2.6秒.

继续抽样,我仍然发现不是绝对必要的操作--1.1秒.

总加速系数:43.6

现在没有两个程序是相似的,但在非玩具软件中,我总是看到这样的进展.首先,你得到了简单的东西,然后就越难,直到你达到收益递减的程度.然后你获得的洞察力很可能导致重新设计,开始新一轮的加速,直到你再次达到收益递减.现在,这是它可能是有意义的怀疑是否点++ii++for(;;)while(1)更快:题型我看到那么经常SO.

PS可能想知道为什么我没有使用分析器.答案是,这些"问题"中几乎每一个都是一个函数调用站点,它会对样本进行精确定位.即使在今天,Profilers也几乎没有意识到语句和调用指令比整个函数更重要,更容易定位,更容易修复.我实际上构建了一个分析器来实现这一点,但是为了与代码正在做的真正的肮脏的亲密关系,没有任何东西可以替代你的手指.样本数量很小不是问题,因为找到的问题都不是很小,很容易错过.

补充:jerryjvl请求了一些例子.这是第一个问题.它由少量独立的代码行组成,占用了一半以上的时间:

 /* IF ALL TASKS DONE, SEND ITC_ACKOP, AND DELETE OP */
if (ptop->current_task >= ILST_LENGTH(ptop->tasklist){
. . .
/* FOR EACH OPERATION REQUEST */
for ( ptop = ILST_FIRST(oplist); ptop != NULL; ptop = ILST_NEXT(oplist, ptop)){
. . .
/* GET CURRENT TASK */
ptask = ILST_NTH(ptop->tasklist, ptop->current_task)

这些是使用列表集群ILST(类似于列表类).它们以通常的方式实现,"信息隐藏"意味着类的用户不应该关心它们是如何实现的.当写出这些行(大约800行代码中)时,没有想到这些行可能是"瓶颈"(我讨厌这个词).它们只是推荐的做事方式.事后很容易说这些应该是可以避免的,但根据我的经验,所有性能问题都是这样的.通常,尽量避免产生性能问题是很好的.找到并修复创建的内容会更好,即使它们"应该已经避免"(事后看来).我希望这会带来一些味道.

这是第二个问题,分为两个部分:

 /* ADD TASK TO TASK LIST */
ILST_APPEND(ptop->tasklist, ptask)
. . .
/* ADD TRANSACTION TO TRANSACTION QUEUE */
ILST_APPEND(trnque, ptrn)

这些是通过将项目附加到其末尾来构建列表.(修复是收集数组中的项目,并一次构建列表.)有趣的是,这些语句只花费(即在调用堆栈上)原始时间的3/48,因此它们不在事实上一开始就是一个大问题.然而,在消除了第一个问题之后,他们花费了3/20的时间,所以现在是"更大的鱼".一般来说,就是这样.

我可以补充一点,这个项目是从我帮助过的真实项目中提炼出来的.在该项目中,性能问题更加引人注目(与加速一样),例如在内部循环中调用数据库访问例程以查看任务是否已完成.

参考添加:原始和重新设计的源代码可以在www.ddj.com中找到,1993年,在文件9311.zip中,文件slug.asc和slug.zip.

编辑2011/11/26:现在有一个sourceforge项目,包含Visual C++中的源代码,以及如何调整它的详细描述.它只经历了上述场景的前半部分,并且它不遵循完全相同的序列,但仍然获得2-3个数量级的加速.


@Thorbjørn:为了跟进,我确实联系了GoogleBooks,填写了所有表格,然后给他们发了一份硬拷贝.我收到一封电子邮件,询问我是否真的拥有版权.出版商Van Nostrand Reinhold被国际汤普森收购,后者被路透社收购,当我试图打电话或发电子邮件时,它就像一个黑洞.所以它处于不确定状态 - 我还没有精力去追逐它.
...我还写了一本现已绝版的书,所以它在亚马逊上的价格是一个荒谬的价格 - "建立更好的应用程序"ISBN 0442017405.第一章基本上是相同的材料.
Google图书链接:http://books.google.dk/books?id = 8A43E1UFs_YC
我想阅读您在上面概述的步骤的一些细节.是否可以包含优化的一些片段?(没有让帖子太长?)
@Mike Dunlavey,我建议告诉谷歌你已经扫描了它.他们可能已经与购买您的出版商的人签订了协议.
@Thorbjørn:感谢您的注意,我希望您做得很好.它仍然只显示了本书的第一部分.我有点放弃试图找出谁拥有版权并发布整个事情.有时我会通过电子邮件发送给有问题的人
@Thorbjørn:我想我不知道该怎么做,我也不知道是否允许,特别是因为出版商已被买了几次.我自己扫描并将其发送给一些感兴趣的人,但是以jpg形式发送电子邮件有点大.

2> jerryjvl..:

建议:

预先计算而不是重新计算:包含具有相对有限的输入范围的计算的任何循环或重复调用,考虑进行查找(数组或字典),其中包含有效范围内的所有值的计算结果投入.然后在算法中使用简单的查找.
下方:如果实际使用的预先计算值很少,这可能会使事情变得更糟,查找也可能占用大量内存.

不要使用库方法:大多数库需要编写才能在各种场景下正确运行,并对参数执行空值检查等.通过重新实现一种方法,您可以去掉许多逻辑不适用于您使用它的确切情况.
下方:编写额外的代码意味着更多的表面区域的bug.

使用库方法:与自己相矛盾,语言库是由比你或我更聪明的人写的; 可能性是他们做得更好更快.不要自己实现它,除非你能真正加快它(即:总是测量!)

作弊:在某些情况下,虽然您的问题可能存在精确计算,但您可能不需要"精确",有时近似可能"足够好"且交易速度更快.问问自己,如果答案是1%,这真的很重要吗?5%?甚至10%?
向下:嗯......答案不准确.


作弊往往是胜利.我有一个色彩校正过程,核心是一个3矢量点缀着3x3矩阵.CPU在硬件中有一个矩阵乘法,省去了一些交叉项并且与其他所有方法相比实际上变得非常快,但只支持4x4矩阵和4向量浮点数.更改代码以携带额外的空插槽并将计算从固定点转换为浮点允许稍微不准确但*更快*更快的结果.
预计算并不总是有帮助,有时甚至会受到影响 - 如果查找表太大,它可能会破坏缓存性能.
作弊是使用矩阵乘法,遗漏了一些内部产品,使得可以在微码中实现单个CPU指令,其完成速度甚至比单个指令的等效序列更快.这是一个骗子,因为它没有得到"正确"的答案,只是一个"足够正确"的答案.
@RBerteig:只要"足够正确"就是大多数人在我的经历中错过的优化机会.
你不能总是假设每个人都比你更聪明.最后,我们都是专业人士.但是,您可以假设您使用的特定库因其质量而存在并已到达您的环境,因此该库的编写必须非常彻底,您不能仅仅因为您不专门领域,你不要投入同样的时间.不是因为你不那么聪明 来吧.

3> kenj0418..:

当你无法再提高性能时 - 看看你是否可以提高感知性能.

您可能无法更快地使用fooCalc算法,但通常有一些方法可以使您的应用程序对用户更具响应性.

几个例子:

预测用户将要求的内容并在此之前开始处理

在结果显示时显示结果,而不是在结束时立即显示结果

准确的进度表

这些不会使您的程序更快,但它可能会让您的用户更快乐.


最后加速的进度条可能被认为比绝对准确的更快.在"重新思考进度条"(2007)中,Harrison,Amento,Kuznetsov和Bell测试了一组用户的多种类型的条形图,并讨论了重新安排操作的一些方法,以便可以将进度视为更快.
naxa,大多数进度条都是假的,因为预测流量分成一个百分比的多个不同步骤很难或有时是不可能的.只要看看那些被卡在99%的酒吧:-(

4> Crashworks..:

我大部分时间都在这个地方度过.广泛的描述是运行你的探查器并让它记录:

缓存未命中.数据缓存是大多数程序中排名第一的停顿源.通过重组有问题的数据结构来提高缓存命中率以获得更好的位置; 打包结构和数字类型以消除浪费的字节(因此浪费了缓存提取); 尽可能预取数据以减少停顿.

加载热门商店.编译器关于指针别名的假设以及数据通过内存在断开的寄存器集之间移动的情况可能会导致某种病态行为导致整个CPU管道在加载操作中清除.找到浮动,向量和整体相互投射的位置并消除它们.使用__restrict自由地向编译器承诺别名.

微编码操作.大多数处理器都有一些不能流水线操作的操作,而是运行一个存储在ROM中的微小子程序.PowerPC上的示例是整数乘法,除法和按变量移位量.问题是整个管道在执行此操作时停止运行.尝试消除这些操作的使用,或至少将它们分解为组成的流水线操作,这样您就可以在程序的其余部分上获得超标量分派的好处.

分支错误预测.这些太空了管道.查找CPU花费大量时间在分支后重新填充管道的情况,并使用分支提示(如果可用)使其更频繁地正确预测.或者更好的是,尽可能用条件移动替换分支,特别是在浮点运算之后,因为它们的管道通常更深,并且在fcmp之后读取条件标志会导致停顿.

顺序浮点运算.制作这些SIMD.

还有一件事我喜欢做:

将编译器设置为输出汇编列表,并查看它为代码中的热点函数发出的内容.所有这些聪明的优化"一个好的编译器应该能够自动为你做"?有可能你的实际编译器不会这样做.我见过GCC发出真正的WTF代码.


我主要使用Intel VTune和PIX.不知道他们是否能够适应C#,但实际上,一旦你有了JIT抽象层,大多数这些优化都无法实现,除了改进缓存局部性和可能避免一些分支.
即便如此,检查后JIT输出可能有助于弄清楚是否有任何结构在JIT阶段没有很好地优化......调查永远不会受到伤害,即使结果是死路一条.
我想很多人,包括我自己,都会对这个由gcc制作的"wtf assembly"感兴趣.你的听起来像一个非常有趣的工作:)

5> sisve..:

投入更多硬件!


对制作消费者软件的人来说,这不是一个非常有用的答案:客户不会想听到你说"购买更快的电脑".特别是如果你正在编写软件来定位像视频游戏机这样的东西.
我曾经不得不调试一个有大量内存泄漏的程序 - 它的VM大小每小时增长大约1Mb.一位同事开玩笑说我需要做的就是以恒定的速率*添加内存*.:)
当您拥有预期在现场硬件上运行的软件时,并不总是可以选择更多硬件.
@Crashworks,或者说就是嵌入式系统.当最后一个功能最终进入并且第一批电路板已经旋转时,不是发现您应该首先使用更快的CPU的时刻......
我的想法完全正确 当你没有什么东西可以开始谈论"最后几个百分点"时,让它运行得更快的最简单的方法是更快的硬件,而不是花费大量的程序员时间来挤压它的最后几个百分点.
更多硬件:啊是平庸的开发者的生命线.我不知道有多少次我听说"添加另一台机器并将容量增加一倍!"
所有这些都取决于用户有多少问题,以及您是否可以通过花费相同的时间来创建新功能来为用户创造更多价值.一些程序员愿意花费6个月的时间来使用一些小功能,但拒绝花5分钟来实现一个快捷方式,每天为用户节省几分钟的工作量.一切都以表现的名义.不,这不是一个修辞的例子
如果你认为摩尔定律在x2/18个月运行,那么复合率约为0.13%/天(并且周末工作并且不需要假期:^).只需等待,就可以权衡最后几个百分点优化的内容.如果你采取足够长的视图,这在控制台/消费者/嵌入式空间中同样适用(性能图表只是更加量化),你仍然可以为继续下一件事而不是投入时间回报微不足道的东西.
从技术上讲,这是第一手段的性能策略.
我刚刚优化了一些代码,运行了大约11个小时而无法完全完成,因为64GB RAM还不够.较新的硬件(128GB RAM和更快的CPU /磁盘)使用大约6.5小时的相同数据,这显然更好.经过我的优化后,OLD机器耗时1:40分钟,可以用64GB RAM完成整个数据,而新机器的总时间缩短到1:15分钟.所以,是的,添加硬件可以提供帮助,但适当的选择,包括整个生产过程,可以大大提高用水度.而且这样的服务器也不是很便宜.

6> jerryjvl..:

更多建议:

避免I/O:任何I/O(磁盘,网络,端口等)总是比执行计算的任何代码慢得多,因此请删除任何您不需要的I/O.

预先移动I/O:预先加载计算所需的所有数据,以便在关键算法的核心内没有重复的I/O等待(可能因此重复磁盘搜索,当一次命中加载所有数据时可能会避免寻找).

延迟I/O:在计算结束之前不要写出结果,将它们存储在数据结构中,然后在完成工作时将其一次性转储.

螺纹I/O:对于那些大胆的人,将'I/O预先'或'延迟I/O'与实际计算相结合,将加载移动到并行线程中,这样当您加载更多数据时,您可以工作计算您已有的数据,或者在计算下一批数据时,您可以同时写出最后一批数据.


请注意,"将IO移动到并行线程"应该在许多平台(例如Windows NT)上作为异步IO完成.
I/O确实是一个关键点,因为它很慢并且具有巨大的延迟,你可以通过这个建议加快速度,但它仍然存在根本缺陷:点是延迟(必须隐藏)和系统调用开销(必须通过减少_number_的I/O调用来减少它.最好的建议是:使用`mmap()`作为输入,做适当的`madvise()`调用并使用`aio_write()`来编写大块输出(=几个MiB).

7> HLGEM..:

由于许多性能问题都涉及数据库问题,因此在调优查询和存储过程时,我将为您提供一些具体的内容.

在大多数数据库中避免使用游标.避免循环.大多数情况下,数据访问应该基于集合,而不是记录处理.这包括当您要一次插入1,000,000条记录时不重用单个记录存储过程.

切勿使用select*,只返回您实际需要的字段.如果存在任何连接,则尤其如此,因为连接字段将被重复,从而导致服务器和网络上的不必要的负载.

避免使用相关子查询.使用连接(包括可能的连接到派生表)(我知道这适用于Microsoft SQL Server,但在使用不同的后端时测试建议).

索引,索引,索引.如果适用于您的数据库,请更新这些统计信息.

使查询可以进行搜索.意义避免使得无法使用索引的事情,例如在like子句的第一个字符或连接中的函数中使用通配符或作为where语句的左侧部分.

使用正确的数据类型.在日期字段上进行日期数学比在必须尝试将字符串数据类型转换为日期数据类型更快,然后进行计算.

切勿将任何类型的循环放入触发器中!

大多数数据库都有办法检查查询执行的执行方式.在Microsoft SQL Server中,这称为执行计划.首先检查那些问题区域.

在确定需要优化的内容时,请考虑查询运行的频率以及运行所需的时间.有时候,从轻微的调整到每天运行数百万次的查询可以获得更多的性能,而不是每月只运行一次的long_running查询擦除时间.

使用某种分析器工具来查找实际发送到数据库和从数据库发送的内容.我记得有一次我们无法弄清楚为什么页面在存储过程很快时加载速度太慢,并通过剖析网页要求查询多次而不是一次查询.

探查器还可以帮助您找到阻止谁的人.由于来自其他查询的锁定,一些在单独运行时快速执行的查询可能会变得非常慢.



8> 小智..:

今天唯一最重要的限制因素是有限的内存bandwitdh.多核只会使这种情况变得更糟,因为带宽在核心之间共享.此外,用于实现高速缓存的有限芯片面积也在核心和线程之间划分,甚至更加恶化了这个问题.最后,保持不同高速缓存一致所需的芯片间信令也随着核心数量的增加而增加.这也增加了罚款.

这些是您需要管理的效果.有时通过微观管理代码,但有时通过仔细考虑和重构.

很多评论已经提到缓存友好代码.至少有两种不同的风格:

避免内存提取延迟.

降低内存总线压力(带宽).

第一个问题特别涉及使数据访问模式更加规则,允许硬件预取器高效工作.避免动态内存分配,将内存中的数据对象分散开来.使用线性容器而不是链接列表,哈希和树.

第二个问题与改进数据重用有关.更改算法以处理适合可用缓存的数据子集,并在数据仍处于缓存中时尽可能多地重用这些数据.

更紧密地打包数据并确保使用热循环中缓存行中的所有数据,这将有助于避免这些其他影响,并允许在缓存中安装更多有用的数据.



9> Johan Kotlin..:

你在运行什么硬件?您可以使用特定于平台的优化(如矢量化)吗?

你能得到更好的编译器吗?例如从GCC切换到英特尔?

你能让你的算法并行运行吗?

您可以通过重组数据来减少缓存未命中吗?

你能禁用断言吗?

针对您的编译器和平台进行微优化.以"if/else,将最常见的陈述放在第一位"的风格


应该是"从GCC切换到LLVM":)
*你能让你的算法并行运行吗?* - 逆也适用
没错,减少线程数量可以是一个同样好的优化

10> none..:

你应该考虑"谷歌的观点",即确定你的应用程序如何在很大程度上并行化和并发化,这在某种程度上也意味着要考虑在不同的机器和网络上分发你的应用程序,这样它可以理想地几乎线性地扩展用你投入的硬件.

另一方面,谷歌人也因为投入大量人力和资源来解决他们正在使用的项目,工具和基础设施中的一些问题而闻名,例如通过拥有专门的工程师团队对gcc进行整体计划优化为了准备谷歌典型的用例场景,黑客攻击gcc内部.

同样,对应用程序进行概要分析不再仅仅意味着简单地分析程序代码,而是分析其周围的所有系统和基础设施(想想网络,交换机,服务器,RAID阵列),以便从系统的角度来识别冗余和优化潜力.



11> asoundmove..:

虽然我喜欢Mike Dunlavey的回答,但实际上它是一个很好的答案,确实有支持的例子,我认为它可以非常简单地表达出来:

找出最先花费最多时间的东西,并了解原因.

它是时间点的识别过程,可帮助您了解必须优化算法的位置.这是唯一一个无所不包的语言不可知的答案,我可以找到一个已经应该完全优化的问题.还假设您希望在追求速度时独立于架构.

因此,尽管可以优化算法,但是它的实现可能不是.标识允许您知道哪个部分是哪个:算法或实现.所以,无论哪个时间最重要的是你的主要候选人.但是既然你说你想要挤出最后几个百分比,你可能还想检查较小的部分,那些你最初没有仔细检查过的部分.

最后,通过不同方式实现相同解决方案或可能不同的算法的性能数据的一些试验和错误可以带来有助于识别时间浪费和节省时间的见解.

HPH,asoudmove.



12> plinth..:

内联例程(消除调用/返回和参数推送)

尝试使用表格查找消除测试/开关(如果它们更快)

将循环(Duff的设备)展开到它们恰好适合CPU缓存的位置

本地化内存访问,以免破坏缓存

如果优化器尚未执行此操作,则本地化相关计算

如果优化器尚未执行此操作,则消除循环不变量


IIRC Duff的设备很少快.只有当op很短时(就像一个小的数学表达式)

13> Dror Helper..:

当你到达使用高效算法时,你需要更多速度或内存的问题.使用缓存在内存中"支付"以获得更高的速度,或使用计算来减少内存占用.

如果可能(并且更具成本效益)抛出硬件问题 - 更快的CPU,更多内存或HD可以更快地解决问题然后尝试编码它.

如果可能,请使用并行化 - 在多个线程上运行部分代码.

使用正确的工具完成工作.一些编程语言创建更高效​​的代码,使用托管代码(即Java/.NET)加速开发,但本机编程语言创建更快的运行代码.

微优化.仅适用于您可以使用优化的程序集来加速小块代码,在正确的位置使用SSE /向量优化可以大大提高性能.



14> MPelletier..:

分而治之

如果正在处理的数据集太大,则循环遍历它的块.如果您已经完成了正确的代码,那么实现应该很容易.如果你有一个单片程序,现在你知道的更好.


我在阅读最后一句时听到的苍蝇拍"啪"的声音为+1.

15> gnat..:

首先,正如前几个答案中所提到的,了解一下你的表现是什么 - 它是内存或处理器,网络或数据库还是别的东西.取决于......

......如果它的记忆 - 找到很久以前由Knuth写的一本书,"计算机编程艺术"系列之一.很可能是关于排序和搜索的问题 - 如果我的记忆错了,那么你必须找出他谈论如何处理慢速磁带数据存储的问题.精神上将他的内存/磁带对分别转换为你的一对缓存/主内存(或一对L1/L2缓存).研究他描述的所有技巧 - 如果你找不到解决问题的方法,那就聘请专业的计算机科学家进行专业研究.如果您的内存问题偶然发生在FFT(执行基数为2的蝴蝶时缓存未达到位反转索引),那么就不要雇用科学家 - 而是手动优化传递,直到您获胜或获得走到尽头.你提到挤出最后几个百分点吧?如果确实很少,你很可能会获胜.

...如果是处理器 - 切换到汇编语言.研究处理器规范 - 什么需要滴答,VLIW,SIMD.函数调用很可能是可替换的滴答滴答者.学习循环转换 - 管道,展开.乘法和除法可以用位移替换/插值(乘以小整数可以用加法替换).尝试使用更短的数据 - 如果你幸运的话,一个64位的指令可能会被替换为32位上的两位或16位上的4位或8位上的8位数.尝试更长的数据 - 例如,您的浮点计算可能比特定处理器的双重计算慢.如果您有三角函数,请使用预先计算的表格进行对抗; 还要记住,如果精度损失在允许的限度内,那么小值的正弦值可能会被该值替换.

...如果它是网络 - 想想压缩你传递的数据.用二进制替换XML传输.研究方案.如果可以以某种方式处理数据丢失,请尝试使用UDP而不是TCP.

...如果是数据库,那么,去任何数据库论坛并寻求建议.内存数据网格,优化查询计划等等.

HTH :)



16> Killroy..:

缓存!一种廉价的方法(在程序员的努力下)几乎可以做任何事情就是在程序的任何数据移动区域添加一个缓存抽象层.无论是I/O还是传递/创建对象或结构.通常,很容易将缓存添加到工厂类和读写器中.

有时候缓存不会给你带来太多帮助,但是这是一种简单的方法,只需添加缓存,然后在没有帮助的地方禁用缓存.我经常发现这可以获得巨大的性能而无需对代码进行微观分析.



17> Steve Wortha..:

我认为这已经以不同的方式说过了.但是当你处理一个处理器密集型算法时,你应该以最重要的内容为代价来简化内部循环中的所有内容.

对某些人来说这似乎是显而易见的,但无论我使用哪种语言,我都会尝试关注这一点.例如,如果您正在处理嵌套循环,并且您发现有机会将某些代码放在某个级别,那么在某些情况下您可以大大加快代码速度.作为另一个例子,有些事情需要考虑,比如使用整数而不是浮点变量,并且尽可能使用乘法而不是除法.同样,这些是你最内循环应该考虑的事情.

有时您可能会发现在内循环内对整数执行数学运算的好处,然后将其缩小为可以在之后使用的浮点变量.这是在一个部分牺牲速度以提高另一部分的速度的一个例子,但在某些情况下,回报可能是值得的.



18> 小智..:

我花了一些时间来优化在低带宽和长延迟网络(例如卫星,远程,离岸)上运行的客户端/服务器业务系统,并且能够通过相当可重复的过程实现一些显着的性能改进.

措施:首先了解网络的基础容量和拓扑.与业务中的相关网络人员交谈,并利用ping和traceroute等基本工具在典型的运营期间(至少)建立每个客户端位置的网络延迟.接下来,对显示有问题症状的特定最终用户功能进行准确的时间测量.记录所有这些测量值,以及它们的位置,日期和时间.考虑在客户端应用程序中构建最终用户"网络性能测试"功能,允许高级用户参与改进过程; 当你处理因表现不佳的系统而感到沮丧的用户时,像这样赋予他们权力可能会产生巨大的心理影响.

分析:使用可用的任何和所有日志记录方法准确确定在执行受影响的操作期间正在传输和接收的数据.理想情况下,您的应用程序可以捕获客户端和服务器发送和接收的数据.如果这些也包括时间戳,甚至更好.如果没有足够的日志记录(例如,封闭系统或无法将修改部署到生产环境中),请使用网络嗅探器并确保您真正了解网络级别正在进行的操作.

缓存:查找重复传输静态或不经常更改的数据并考虑适当的缓存策略的情况.典型示例包括"选择列表"值或其他"参考实体",其在一些商业应用中可能令人惊讶地大.在许多情况下,用户可以接受他们必须重新启动或刷新应用程序以更新不经常更新的数据,特别是如果它可以显着地显示常用的用户界面元素.确保您了解已部署的缓存元素的实际行为 - 许多常见的缓存方法(例如HTTP ETag)仍然需要网络往返以确保一致性,并且在网络延迟昂贵的情况下,您可以完全避免它一种不同的缓存方法.

并行化:查找逻辑上不需要严格按顺序发出的顺序事务,并重新编写系统以并行发布它们.我处理了一个案例,其中端到端请求具有~2s的固有网络延迟,这对于单个事务不是问题,但是在用户重新获得对客户端应用程序的控制之前需要6次连续2次往返,它成了沮丧的巨大根源.发现这些事务实际上是独立的,允许它们并行执行,从而将最终用户延迟减少到非常接近单次往返的成本.

组合:必须按顺序执行顺序请求,寻找将它们组合成单个更全面的请求的机会.典型示例包括创建新实体,然后是将这些实体与其他现有实体相关联的请求.

压缩:寻找机会利用有效负载的压缩,或者通过用二进制替换文本形式,或者使用实际的压缩技术.许多现代(即十年内)技术堆栈几乎透明地支持这一点,因此请确保它已配置好.我经常对压缩的重大影响感到惊讶,因为似乎很明显问题基本上是延迟而不是带宽,发现它允许事务适合单个数据包或以其他方式避免数据包丢失,因此具有特大优势对绩效的影响.

重复:回到开头并重新测量您的操作(在相同的位置和时间)并进行改进,记录并报告您的结果.与所有优化一样,一些问题可能已经解决,暴露出现在占主导地位的其他问题.

在上面的步骤中,我专注于与应用程序相关的优化过程,但当然您必须确保以最有效的方式配置底层网络本身以支持您的应用程序.让网络专家参与业务,并确定他们是否能够应用容量改进,QoS,网络压缩或其他技术来解决问题.通常情况下,他们无法理解您的应用程序的需求,因此您必须配备(在分析步骤之后)与他们讨论,并为您将要求他们承担的任何成本制定业务案例. .我遇到过错误的网络配置导致应用程序数据通过慢速卫星链路而不是陆上链路传输的情况,原因很简单,因为它使用的网络专家并没有"众所周知".显然纠正这样的问题会对性能产生巨大影响,根本不需要软件代码或配置更改.



19> 小智..:

很难给出这个问题的通用答案.这实际上取决于您的问题域和技术实现.一种相当语言中立的通用技术:识别无法消除的代码热点,并手动优化汇编代码.



20> 小智..:

最后几个%是一个非常依赖CPU和应用程序的东西....

缓存架构不同,有些芯片有片上RAM可以直接映射,ARM(有时)有一个向量单元,SH4是一个有用的矩阵操作码.是否有GPU - 也许是着色器是要走的路.TMS320对循环内的分支非常敏感(如果可能的话,单独的循环和移动条件除外).

名单还在继续......但这些事情真的是最后的手段......

构建x86,并针对代码运行Valgrind/Cachegrind以进行适当的性能分析.或德州仪器的 CCStudio有一个甜蜜的剖析器.然后你真的知道在哪里集中......



21> Sam..:

Did you know that a CAT6 cable is capable of 10x better shielding off extrenal inteferences than a default Cat5e UTP cable?

对于任何非离线项目,虽然拥有最好的软件和最好的硬件,但如果你的吞吐量很弱,那么这条细线就会挤压数据并给你延迟,虽然只有几毫秒......但如果你在谈论最后一滴,对于任何发送或接收的包装,这是一些24/7的下降.



22> Aaron..:

不像以前的答案那样深度或复杂,但这里有:(这些是更初级/中级)

明显:干燥

向后运行循环,所以你总是比较0而不是变量

尽可能使用按位运算符

将重复代码分解为模块/函数

缓存对象

局部变量具有轻微的性能优势

尽可能限制字符串操作


关于向后循环:是的,循环结束的比较会更快.通常,您使用该变量来索引到内存中,并且由于频繁的缓存未命中(无预取),反向访问它可能会产生相反的效果.

23> jalf..:

不可能说.这取决于代码的样子.如果我们可以假设代码已经存在,那么我们可以简单地看一下并从中找出,如何优化它.

更好的缓存局部性,循环展开,尝试消除长依赖链,以获得更好的指令级并行性.在可能的情况下,首选条件移动分支.尽可能利用SIMD指令.

了解您的代码正在做什么,并了解它正在运行的硬件.然后,确定您需要做什么来提高代码性能变得相当简单.这真的是我能想到的唯一真正的一般建议.

嗯,那,和"在SO上显示代码并询问针对该特定代码的优化建议".



24> 小智..:

如果更好的硬件是一个选项,那么一定要去做.除此以外

检查您使用的是最佳编译器和链接器选项.

如果不同库中的热点例程对于频繁调用者,请考虑将其移动或克隆到调用者模块.消除一些调用开销,并可能改善缓存命中率(参见AIX如何将strcpy()静态链接到单独链接的共享对象).这当然也可以降低缓存命中率,这就是一个衡量标准的原因.

查看是否有可能使用专门版本的热点例程.下行不止一个版本需要维护.

看看汇编程序.如果您认为它可能会更好,请考虑编译器为什么没有弄清楚这一点,以及如何帮助编译器.

考虑一下:你真的使用最好的算法吗?它是您输入大小的最佳算法吗?



25> asyncwait..:

谷歌的方式是一个选项"缓存它......尽可能不要触摸磁盘"



26> Andrew Neely..:

以下是我使用的一些快速而肮脏的优化技术.我认为这是"第一次通过"优化.

了解花费时间的地方确切了解花时间.它是文件IO吗?是CPU时间吗?是网络吗?是数据库吗?如果不是瓶颈,那么优化IO是没用的.

了解您的环境 了解优化位置通常取决于开发环境.例如,在VB6中,通过引用传递比传递值慢,但在C和C++中,通过引用传递速度要快得多.在C中,如果返回代码指示失败,尝试某些操作并执行不同的操作是合理的,而在Dot Net中,捕获异常比在尝试之前检查有效条件要慢得多.

指数构建上频繁查询数据库字段的索引.你几乎总能以空间换取速度.

避免查找 在要优化的循环内部,我避免必须进行任何查找.找到循环外的偏移量和/或索引,并重用其中的数据.

最小化IO尝试以减少必须通过网络连接读取或写入的次数的方式进行设计

减少抽象代码必须处理的抽象层越多,它就越慢.在关键循环内部,减少抽象(例如,揭示避免额外代码的低级方法)

具有用户界面的项目的Spawn Threads,产生新线程以执行较慢的任务使应用程序感觉更具响应性,尽管不是.

预处理您通常可以交换空间以提高速度.如果有计算或其他强烈操作,请查看您是否可以在进入关键循环之前预先计算某些信息.



27> ideasman42..:

添加这个答案,因为我没有看到它包含在所有其他人.

最小化类型和符号之间的隐式转换:

这至少适用于C/C++,即使您已经认为自己没有转换 - 有时可以测试在需要性能的函数周围添加编译器警告,尤其是在循环内进行转换时要小心.

GCC spesific:您可以通过在代码周围添加一些详细的编译指示来测试它,

#ifdef __GNUC__
#  pragma GCC diagnostic push
#  pragma GCC diagnostic error "-Wsign-conversion"
#  pragma GCC diagnostic error "-Wdouble-promotion"
#  pragma GCC diagnostic error "-Wsign-compare"
#  pragma GCC diagnostic error "-Wconversion"
#endif

/* your code */

#ifdef __GNUC__
#  pragma GCC diagnostic pop
#endif

我已经看到过这样的情况,你可以通过减少这样的警告引起的转换来获得几个百分点的加速.

在某些情况下,我有一个带有严格警告的标题,我会将其包括在内以防止意外转换,但这是一种权衡,因为您最终可能会添加大量演员表来安静地进行故意转换,这可能会使代码更加混乱收益.

推荐阅读
mobiledu2402852357
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有