不得已的性能优化策略[关闭]


609

这个站点上已经有很多性能问题,但是据我所知,几乎所有问题都是针对特定问题的,而且范围很窄。几乎所有人都会重复建议,以免过早优化。

假设:

  • 该代码已经正常工作
  • 选择的算法已经针对问题的情况进行了优化
  • 代码已被测量,并且违规例程已被隔离
  • 所有优化的尝试也将得到衡量,以确保它们不会使情况变得更糟

我在这里寻找的是策略和技巧,这些方法和技巧可以使关键算法中的最后几个部分都挤到最后几个百分点,而除了它需要做的事情之外,别无其他。

理想情况下,请尝试使答案与语言无关,并在适用时指出建议策略的任何不利方面。

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

Answers:


427

好的,您正在将问题定义为似乎没有太多改进空间的地方。根据我的经验,那是相当罕见的。我试图在1993年11月的Dobbs博士的一篇文章中对此进行解释,从一个设计合理,无明显浪费的平凡程序开始,并进行了一系列优化,直到其挂钟时间从48秒减少到到1.1秒,源代码的大小减少了4倍。我的诊断工具是this。更改的顺序是这样的:

  • 发现的第一个问题是使用列表集群(现在称为“迭代器”和“容器类”)占了一半以上的时间。那些被替换为相当简单的代码,使时间减少到20秒。

  • 现在最大的时间接受者是建立更多的清单。以百分比计,它以前没那么大,但是现在是因为消除了更大的问题。我找到了一种加快速度的方法,时间减少到17秒。

  • 现在很难找到明显的罪魁祸首,但是我可以做一些较小的罪魁祸首,而时间缩短到13秒。

现在我似乎撞墙了。这些样本准确地告诉了我它在做什么,但是我似乎找不到任何可以改进的地方。然后,我对程序的基本设计,事务驱动的结构进行了反思,并询问该程序正在执行的所有列表搜索是否实际上都是由问题的要求所强制执行的。

然后我进行了重新设计,在该程序中,实际上是从较小的一组源中(通过预处理器宏)生成了程序代码,并且在该程序中,程序并没有不断地弄清程序员知道是可以预知的。换句话说,不要“解释”要做的事情的顺序,而要“编译”它。

  • 重新设计完成,将源代码缩小4倍,时间减少到10秒。

现在,由于它变得如此之快,因此很难进行采样,因此我给它做的工作量是其的10倍,但接下来的时间是基于原始的工作量。

  • 更多的诊断表明,它花时间在队列管理上。内联这些将时间减少到7秒。

  • 现在,我一直在做的诊断打印非常重要。冲洗-4秒钟。

  • 现在,花费最大的时间是调用mallocfree。回收对象-2.6秒。

  • 继续进行示例,我仍然发现并非绝对必要的操作-1.1秒。

总加速因子:43.6

现在没有两个程序是相同的,但是在非玩具软件中,我总是看到这样的进展。首先,您会获得一些简单的东西,然后获得更多的困难,直到收益递减。然后,您获得的见解很可能会导致重新设计,开始新一轮的提速,直到您再次获得递减的收益。现在,这是它可能是有意义的怀疑是否点++ii++for(;;)while(1)更快:题型我看到经常堆栈溢出。

PS也许想知道为什么我不使用探查器。答案是这些“问题”中的几乎每个都是一个函数调用站点,这些站点会精确地采样。直到今天,事件探查器仍几乎不了解这样的想法,即语句和调用指令比整个功能更重要的是定位和更容易修复。

我实际上构建了一个探查器来执行此操作,但是对于与代码正在做的事情真正亲密无间的亲密关系,没有什么可替代的。样本数量很少不是问题,因为发现的所有问题都没有很小到可以轻易遗漏的程度。

添加: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的时间,因此成为了“大鱼”。总的来说,就是这样。

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

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

编辑2011/11/26:现在有一个SourceForge项目,其中包含Visual C ++中的源代码以及对其进行调整的详细说明。它仅经历上述场景的前半部分,并且不遵循完全相同的顺序,但是仍然会加快2-3个数量级。


3
我很想阅读您上面概述的步骤的一些详细信息。是否可以包括一些风味优化的片段?(没有让帖子过长?)
jerryjvl

8
...我还写了一本书,现在已经绝版,因此在亚马逊上要花掉荒谬的价格-“构建更好的应用程序” ISBN0442017405。第一章基本上是相同的材料。
Mike Dunlavey,2009年

3
@Mike Dunlavey,我建议告诉Google您已经扫描过它。他们可能已经与购买您的发布者的人达成协议。
托尔比约恩Ravn的安徒生

19
@Thorbjørn:为了跟进,我确实与GoogleBooks挂钩,填写了所有表格,并将其发送给他们。我收到一封电子邮件,询问我是否确实拥有该版权。出版商范·诺斯特兰德·雷因霍尔德(Van Nostrand Reinhold),被国际汤普森(International Thompson)收购,被路透社(Reuters)收购,当我尝试给他们打电话或发电子邮件时,这就像一个黑洞。所以它处于困境-我还没有力量真正追逐它。
Mike Dunlavey


188

意见建议:

  • 预先计算而不是重新计算:如果包含输入范围相对有限的计算的任何循环或重复调用,请考虑进行查找(数组或字典),以查找有效范围内所有值的计算结果。输入。然后在算法内部使用简单的查找。
    缺点:如果实际上很少使用预先计算的值,这可能会使情况变得更糟,查找也会占用大量内存。
  • 不要使用库方法:大多数库都需要编写才能在广泛的场景下正确运行,并对参数执行空检查等。通过重新实现方法,您可能能够去除很多逻辑,不适用于您正在使用的确切情况。
    缺点:编写额外的代码意味着更多的漏洞。
  • 一定要使用图书馆的方法:与自己矛盾的是,语言图书馆是由比你或我聪明得多的人编写的;他们有可能做得更好,更快。除非您实际上可以更快地实现它,否则不要自己实现它(即:始终测量!)
  • 作弊:在某些情况下,尽管可能会为您的问题提供精确的计算,但您可能不需要“精确”,有时近似值可能“足够好”,并且交易速度快得多。问问自己,答案是否超出1%真的重要吗?5%?甚至10%?
    缺点:嗯...答案并不确切。

32
预计算并不总是有帮助,有时甚至会受到伤害-如果您的查询表太大,则会破坏缓存性能。
亚当·罗森菲尔德

37
作弊往往是制胜法宝。我有一个色彩校正过程,其核心是一个由3x3矩阵点缀的3矢量。CPU在硬件上具有矩阵乘法,与所有其他方式相比,它省略了一些交叉项,并且运算速度很快,但仅支持4x4矩阵和4个浮点矢量。改变代码携带额外的空时隙和计算从允许的略少精确的固定点转换为浮点但快的结果。
RBerteig

6
作弊是在使用矩阵乘法的情况下,它省略了一些内部乘积,从而有可能在微代码中实现单个CPU指令的完成,该指令甚至比单个指令的等效序列还快。之所以作弊是因为它没有得到“正确”的答案,而只是得到了“足够正确”的答案。
RBerteig 2011年

6
@RBerteig:仅仅“足够正确”是大多数人在我的经验中都错过的优化机会。
马丁·汤普森

5
您不能总是假设每个人都比您聪明。最后,我们都是专业人士。但是,您可以假定您使用的特定库由于其质量而存在并且已经到达您的环境,因此该库的编写必须非常透彻,您不能做到这一点,因为您不擅长该库领域,您不会在其中投入相同的时间。不是因为你不那么聪明。来吧。
v.oddou

164

当您无法再提高性能时,请查看是否可以提高感知性能。

您可能无法使自己的fooCalc算法更快,但是通常有一些方法可以使您的应用程序对用户的响应更快。

一些例子:

  • 预测用户将要请求的内容并在此之前开始进行处理
  • 显示结果,而不是一次全部显示
  • 准确的进度表

这些不会使您的程序更快,但是可能会使您的用户对自己的速度感到满意。


27
最后的进度条可能比绝对准确的进度条快。Harrison,Amento,Kuznetsov和Bell在“重新思考进度栏”(2007年)中测试了一组用户的多种类型的进度条,并讨论了重新安排操作的一些方法,以便可以感觉到进度更快。
EmilVikström,2012年

9
纳克萨语,大多数进度条都是假的,因为很难预测甚至有时无法预测单个百分比中流量的多个不同步骤。只要看看所有卡在99%处的酒吧:-(
EmilVikström2013年

138

我一生的大部分时间都在这个地方。大招是运行您的探查器并进行记录:

  • 缓存未命中。在大多数程序中,数据高速缓存是停顿的第一大来源。通过重组有问题的数据结构以具有更好的局部性来提高缓存命中率;压缩结构和数值类型,以消除浪费的字节(并因此浪费缓存的获取);尽可能预取数据以减少停顿。
  • 加载商店。编译器有关指针别名的假设以及数据通过内存在断开连接的寄存器集之间移动的情况,可能会导致某些病理现象,从而导致整个CPU管道在加载操作时被清除。找到将浮点数,向量和整数相互转换的地方,并消除它们。大量使用__restrict以向编译器承诺有关别名的信息。
  • 微码操作。大多数处理器的某些操作无法流水线化,而是运行存储在ROM中的微小子例程。PowerPC上的示例是整数乘法,除法和按变量移位。问题在于,执行此操作时,整个管道将停止运行。尝试消除对这些操作的使用,或者至少将它们分解为它们组成的流水线操作,以便您可以在程序的其余部分中获得超标量调度的好处。
  • 分支预测错误。这些太空了管道。查找CPU在分支后花费大量时间重新填充管道的情况,并使用分支提示(如果可用)使其更频繁地正确预测。或更好的办法是,尽可能使用条件移动替换分支,尤其是在浮点运算之后,因为分支的管道通常更深,并且在fcmp之后读取条件标志可能会导致停顿。
  • 顺序浮点运算。制作这些SIMD。

我还想做一件事:

  • 设置编译器以输出程序集清单,并查看代码中热点功能所发出的内容。所有那些“一个好的编译器应该能够自动为您完成”的聪明的优化?您实际的编译器可能不会执行这些操作。我已经看到GCC发出了真正的WTF代码。

8
我主要使用Intel VTune和PIX。不知道它们是否可以适应C#,但实际上,一旦获得了JIT抽象层,这些优化中的大多数都是无法实现的,除了提高缓存的局部性并可能避免一些分支。
Crashworks,2009年

6
即便如此,检查JIT后的输出仍可以帮助找出是否有任何结构在JIT阶段不能很好地进行优化……调查永远不会受到伤害,即使最终结果是死路一条。
jerryjvl,2009年

5
我认为很多人,包括我自己,都会对gcc制作的“ wtf程序集”感兴趣。您的工作听起来很有趣:)
BlueRaja-Danny Pflughoeft

1
Examples on the PowerPC ...<-即PowerPC的某些实现。PowerPC是ISA,而不是CPU。
Billy ONeal

1
@BillyONeal即使在现代x86硬件上,imul也会使管道停滞;请参阅“英特尔®64和IA-32体系结构优化参考手册”§13.3.2.3:“整数乘法指​​令需要几个周期来执行。它们被流水线化,以便整数乘法指​​令和另一个长等待时间指令可以使整数乘法指​​令向前执行。执行阶段。但是,由于程序顺序的要求,整数乘法指​​令将阻止其他单周期整数指令的发出。” 这就是为什么通常最好使用字对齐的数组大小和lea
Crashworks 2013年

78

扔更多的硬件!


30
当您拥有可以在现场已经存在的硬件上运行的软件时,并非总是选择更多的硬件。
Doug T.

76
对于生产消费类软件的人来说,这不是一个很有帮助的答案:客户不会希望听到您说“购买更快的计算机”。尤其是当您编写针对诸如视频游戏机之类的软件时。
Crashworks

19
@Crashworks,或就此而言,是嵌入式系统。当最后一个功能终于出现并且第一批板卡已经旋转时,现在还不是时候发现您应该首先使用更快的CPU ...
RBerteig'2009年

71
我曾经不得不调试一个存在大量内存泄漏的程序-它的VM大小每小时增加约1Mb。一位同事开玩笑说,我要做的就是以恒定的速度增加内存。:)
j_random_hacker 2010年

9
更多硬件:是的,是普通开发人员的生命线。我不知道有多少次听到“添加另一台机器并使容量翻倍!”。
Olof Forshell

58

更多建议:

  • 避免使用I / O:任何I / O(磁盘,网络,端口等)总是比执行计算的任何代码都要慢得多,因此请摆脱不必要的任何I / O。

  • 提前移动I / O:预先加载计算所需的所有数据,这样就不会在关键算法的核心内重复I / O等待(并且可能会重复执行)磁盘搜索,一次加载所有数据时可以避免搜索)。

  • 延迟I / O:在计算结束之前,不要写出结果,将结果存储在数据结构中,然后在完成艰苦工作后立即一次性转储。

  • 线程I / O:对于那些足够大胆的人,可以通过将加载移到并行线程中,将“ I / O前期”或“延迟I / O”与实际计算结合起来,以便在加载更多数据时可以工作根据已有数据进行计算,或者在计算下一批数据时,可以同时写出上一批数据的结果。


3
请注意,“将IO移动到并行线程”应在许多平台(例如Windows NT)上作为异步IO进行。
Billy ONeal

2
I / O确实是一个关键点,因为它速度慢且具有巨大的延迟,您可以通过此建议更快地获得建议,但从根本上来说还是有缺陷的:延迟(必须隐藏)和系统调用开销(必须通过减少I / O调用次数来减少)。最佳建议是:mmap()用于输入,进行适当的madvise()调用并用于aio_write()写入大块输出(=几个MiB)。
cmaster-恢复莫妮卡

1
最后一个选项在Java中非常容易实现。对于我编写的应用程序,它极大地提高了性能。另一个重要点(不仅仅是将I / O提前移动)是使其成为SEQUENTIAL和大块I / O。由于磁盘寻道时间的原因,许多小读比一大读要贵得多。
BobMcGee 2013年

一方面,我只是通过在计算之前将所有文件临时移至RAM磁盘,然后再将其移回来避免I / O。这很脏,但是在您不控制进行I / O调用的逻辑的情况下可能很有用。
MD

48

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

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

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

避免使用相关的子查询。使用联接(如果可能,包括联接到派生表)(我知道这对于Microsoft SQL Server是正确的,但是在使用其他后端时请测试建议)。

索引,索引,索引。并更新那些统计信息(如果适用于您的数据库)。

使查询可修改。含义应避免使用索引无法使用的事情,例如在like子句的第一个字符中使用通配符,或者在联接中或在where语句的左侧使用函数。

使用正确的数据类型。与必须先将字符串数据类型转换为日期数据类型然后进行计算相比,对日期字段进行日期数学运算要快得多。

切勿将任何形式的循环带入触发器!

大多数数据库都有一种检查查询执行方式的方法。在Microsoft SQL Server中,这称为执行计划。首先检查那些,看看问题所在。

在确定需要优化的内容时,请考虑查询的运行频率以及运行时间。有时候,通过对每天运行数百万次的查询进行细微调整,可以获得比从每月仅运行一次的long_running查询中消除时间所获得的性能更高的性能。

使用某种事件探查器工具来查找实际发送到数据库或从数据库发送的内容。我记得过去有一次,我们无法弄清楚为什么存储过程很快时页面加载如此缓慢的原因,并通过分析发现网页多次而不是一次查询。

探查器还将帮助您查找谁阻止了谁。由于其他查询的锁定,某些在单独运行时快速执行的查询可能会变得非常慢。


29

今天,最重要的限制因素是有限的存储带宽。多核只会使情况变得更糟,因为带宽是在多核之间共享的。而且,专用于实现高速缓存的有限芯片区域也分配在内核和线程之间,这使该问题更加恶化。最后,随着内核数量的增加,保持不同缓存一致性所需的芯片间信令也随之增加。这也增加了罚款。

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

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

  • 避免内存获取延迟。
  • 较低的内存总线压力(带宽)。

第一个问题特别与使数据访问模式更规则有关,从而允许硬件预取器有效地工作。避免动态分配内存,这会分散您的数据对象在内存中的位置。使用线性容器而不是链表,哈希表和树。

第二个问题与改善数据重用性有关。修改算法以处理适合可用缓存的数据子集,并在数据仍在缓存中时尽可能多地重用该数据。

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


25
  • 您在什么硬件上运行?您可以使用特定于平台的优化(例如矢量化)吗?
  • 您能否获得更好的编译器?例如从GCC切换到Intel?
  • 您可以使算法并行运行吗?
  • 您可以通过重组数据来减少高速缓存未命中吗?
  • 您可以禁用断言吗?
  • 针对您的编译器和平台进行微优化。用“如果是/否则,将最常见的语句放在第一位”的样式

4
应该是“从GCC切换到LLVM” :)
Zifre

4
您可以使算法并行运行吗?-倒数也适用
贾斯汀

4
没错,减少线程数量同样是一个很好的优化
Johan Kotlinski 2011年

回复:微优化:如果您检查编译器的asm输出,则通常可以调整源代码以手持它以产生更好的asm。请参阅为什么此C ++代码比我的用于测试Collat​​z猜想的手写程序集更快?有关在现代x86上帮助或击败编译器的更多信息。
彼得·科德斯

17

尽管我喜欢Mike Dunlavey的答案,但实际上,通过支持示例确实是一个不错的答案,我认为可以这样简单地表达它:

首先找出最耗时的时间,然后了解原因。

时间猪的识别过程可以帮助您了解必须在哪里优化算法。对于我认为已经完全优化的问题,这是唯一能够涵盖所有语言的不可知论答案。还要假设您希望速度不依赖于体系结构。

因此,虽然可以优化算法,但可能无法实现。通过标识,您可以知道哪个部分是哪一部分:算法或实现。因此,最浪费时间的人是您进行审查的主要人选。但是,由于您说要挤出最后的几个百分比,因此您可能还希望检查较小的部分,这些部分起初您没有仔细检查过。

最后,通过性能指标的反复试验,以不同的方式来实现相同的解决方案,或者可能使用不同的算法,可以带来有助于识别时间浪费者和时间节省者的见解。

HPH,行进。


16

您可能应该考虑“ Google观点”,即确定应用程序如何在很大程度上并行化和并发化,这不可避免地也意味着在某个时候考虑将应用程序分布在不同的机器和网络上,以便可以理想地线性扩展与您投入的硬件。

另一方面,Google员工也因解决他们正在使用的项目,工具和基础架构中的某些问题而投入了大量的人力和资源,例如通过拥有一支专门的工程师团队来针对gcc进行整个程序优化入侵gcc内部,以便为Google典型的用例场景做好准备。

类似地,对应用程序进行性能分析不再意味着要简单地描述程序代码,而是要分析其所有周围的系统和基础结构(例如网络,交换机,服务器,RAID阵列),以便从系统角度识别冗余和优化潜力。


15
  • 内联例程(消除调用/返回和参数推送)
  • 尝试通过查表消除测试/开关(如果更快)
  • 将循环(Duff的设备)展开到刚好适合CPU缓存的程度
  • 本地化内存访问,以免破坏缓存
  • 如果优化程序尚未执行本地化相关的计算
  • 如果优化器尚未这样做,则消除循环不变性

2
IIRC Duff的设备很少会更快。仅当op非常短时(例如单个小的数学表达式)
BCS

12
  • 当您到达要使用高效算法的地步时,就需要进一步提高速度或内存的问题。使用缓存来“支付”内存以提高速度,或使用计算来减少内存占用。
  • 如果可能(并且更具成本效益)将硬件扔在问题上 -更快的CPU,更多的内存或HD可以更快地解决问题,然后尝试对其进行编码。
  • 使用并行化如果可能, -在多个线程上运行部分代码。
  • 使用正确的工具完成工作。一些编程语言使用托管代码(即Java / .NET)创建更有效的代码,从而加快了开发速度,但本机编程语言创建了运行速度更快的代码。
  • 微优化。只有在适用的情况下,您才可以使用优化的汇编来加快小段代码的速度,在正确的位置使用SSE /向量优化可以极大地提高性能。

12

分而治之

如果正在处理的数据集太大,则循环遍历它的大块。如果您正确编写了代码,则实现应该很容易。如果您有一个整体程序,那么现在您会更加了解。


9
我在阅读最后一句话时听到的flyswatter“打m”声+1。
布莱恩·博伊彻

11

首先,如先前的一些答案中所述,了解影响性能的是内存还是处理器,网络,数据库还是其他东西。取决于...

  • ...如果有记忆,可以找Knuth很久以前写的书之一,它是“计算机编程的艺术”系列之一。很可能是关于排序和搜索的问题-如果我的记忆不对,那么您将不得不找出他在其中谈论如何处理慢速磁带数据存储的问题。轻轻地将他的记忆/录音带对分别转换为缓存/主内存对(或L1 / L2缓存对)。研究他描述的所有技巧-如果您找不到解决问题的方法,请雇用专业的计算机科学家进行专业研究。如果您的存储问题是偶然的FFT(做基数为2的蝴蝶时,高速缓存未命中位反转索引),那么就不要聘请科学家-而是手动逐个手动优化传递,直到 挤到最后百分之几吧?如果确实很少,那么您很可能会赢。

  • ...如果是处理器-切换到汇编语言。研究处理器规格- 需要滴答,VLIW,SIMD。函数调用很可能是可替换的tick子。学习循环转换-管道,展开。乘法和除法可以用移位来替换/内插(乘以小整数可以用加法来替换)。尝试使用较短数据的技巧-如果幸运的话,一条64位的指令可能会被32位的2位甚至16位的4位或8位的8位替换。也尽量不再数据-例如,在特定处理器上,您的浮点计算可能比双精度计算慢。如果您有三角函数,请使用预先计算的表格进行处理;还请记住,如果精度损失在允许的范围内,则较小值的正弦值可能会替换为该值。

  • ...如果是网络-考虑压缩您通过的数据。用二进制替换XML传输。研究方案。如果可以某种方式处理数据丢失,请尝试使用UDP而不是TCP。

  • ...如果是数据库,那么,请转到任何数据库论坛并寻求建议。内存中的数据网格,优化查询计划等等等。

HTH :)


9

正在缓存!一种便宜的方法(在程序员的努力下),它使几乎任何东西都变得更快,是在程序的任何数据移动区域中添加一个缓存抽象层。无论是I / O还是只是对象/结构的传递/创建。通常,将缓存添加到工厂类和读取器/写入器很容易。

有时候,缓存不会给您带来太多好处,但是,这是一种简单的方法,只需全部添加缓存,然后在无用的地方禁用它即可。我经常发现这种方法无需对代码进行微观分析即可获得巨大的性能。


8

我认为这已经以不同的方式表达了。但是,当您要处理处理器密集型算法时,应该简化最内部循环中的所有内容,而要牺牲其他所有内容。

对于某些人来说,这似乎很明显,但是无论我使用哪种语言,我都试图专注于此。例如,如果您正在处理嵌套循环,并且发现将某个代码降级的机会,则在某些情况下可以大大加快代码的速度。再举一个例子,您可以考虑一些小事情,例如,尽可能使用整数而不是浮点变量,并尽可能使用乘法而不是除法。同样,这些是您最内在的循环应该考虑的事情。

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


8

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

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

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

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

  • 并行:寻找逻辑上不需要严格按顺序发出的顺序事务,并重新设计系统以并行发出它们。我处理了一种情况,端到端请求的固有网络延迟为〜2s,这对单笔交易而言不是问题,但是在用户重新获得对客户端应用程序的控制之前,需要6次连续2s往返,这成为挫败感的巨大根源。发现这些事务实际上是独立的,从而允许它们并行执行,从而将最终用户的延迟减少到非常接近单次往返的成本。

  • 合并必须依次执行顺序请求的地方,寻找将它们组合为一个更全面的请求的机会。典型示例包括创建新实体,然后是将这些实体与其他现有实体相关联的请求。

  • 压缩:寻找机会来利用有效载荷的压缩,方法是用二进制形式替换文本形式,或者使用实际的压缩技术。许多现代(即十年之内)的技术堆栈几乎都透明地支持此功能,因此请确保已对其进行配置。我经常对压缩的巨大影响感到惊讶,因为压缩的影响似乎从根本上是延迟而不是带宽,这是事实发现的,它使事务适合单个数据包,或者避免了数据包丢失,因此具有超大容量对性能的影响。

  • 重复:回到开始,并在适当的改进下重新衡量您的操作(在相同的位置和时间),记录并报告结果。与所有优化一样,某些问题可能已经解决,而其他问题则暴露无遗。

在上述步骤中,我着重于与应用程序相关的优化过程,但是当然,您必须确保以最有效的方式配置基础网络本身,以支持您的应用程序。与企业中的网络专家合作,确定他们是否能够应用容量改进,QoS,网络压缩或其他技术来解决问题。通常,他们不会理解您的应用程序的需求,因此,您有能力(在“分析”步骤之后)与他们进行讨论,并为您要让他们招致的任何费用提供业务案例,这一点很重要。我遇到过以下情况:错误的网络配置导致应用程序数据通过慢速卫星链路而不是陆上链路传输,仅仅是因为它使用了网络专家不为人所知的TCP端口;显然,纠正此类问题可能会对性能产生巨大影响,根本不需要更改软件代码或配置。


7

对这个问题很难给出一个通用的答案。这实际上取决于您的问题领域和技术实施。一种与语言无关的通用技术:识别无法消除的代码热点,并手动优化汇编代码。


7

最后几个百分比是非常依赖CPU和应用程序的东西。

  • 缓存体系结构有所不同,有些芯片具有可以直接映射的片上RAM,​​ARM的(有时)具有向量单元,SH4的是有用的矩阵操作码。是否有GPU-也许要使用着色器。TMS320对循环内的分支非常敏感(因此,请分开循环并在可能的情况下将条件移至外部)。

清单还在继续……。但是,这些事情确实是不得已的……

针对x86进行构建,并对代码运行Valgrind / Cachegrind,以进行正确的性能分析。或德州仪器(TI)的 CCStudio具有出色的分析器。然后,您将真正知道应该集中精力在哪里...


7

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

对于任何非离线项目,尽管具有最佳的软件和最佳的硬件,但是如果您的吞吐量很弱,那细线将挤压数据并给您带来延迟,尽管以毫秒为单位...但是如果您谈论的是最后一滴,对于发送或接收的任何打包,这都是24/7的下降。


7

深度或复杂性不及先前的答案,但有:(这些是初学者/中级)

  • 明显:干燥
  • 运行向后循环,因此您总是将其与0而不是变量进行比较
  • 尽可能使用按位运算符
  • 将重复的代码分解为模块/功能
  • 缓存对象
  • 局部变量具有轻微的性能优势
  • 尽可能限制字符串操作

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

1
AFAIK在大多数情况下,任何合理的优化器都可以很好地处理循环,而无需程序员明确地反向运行。优化器要么会反转循环本身,要么有另一种同样好的方法。我注意到,对于(肯定相对简单的)循环,无论是升序vs 最大值还是递减vs 0,都具有相同的ASM输出。当然,我在Z80的日子习惯于反身编写反向循环,但是我怀疑向新手提及通常是当可读代码和学习更重要的实践时,应优先考虑红鲱鱼/过早优化。
underscore_d

相反,在较低级的语言中,向后运行循环会比较慢,因为在零与零加比较与单整数比较之间的较量中,单整数比较会更快。您可以在内存中有一个指向起始地址的指针,而在内存中有一个指向结束地址的指针,而不是递减。然后,递增开始指针,直到它等于结束指针。这将消除汇编代码中多余的内存偏移操作,从而证明性能更高。
杰克·吉芬

5

不可能说。这取决于代码的外观。如果我们可以假设代码已经存在,那么我们可以简单地看一下代码并从中找出如何对其进行优化。

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

了解您的代码在做什么,并了解其运行的硬件。然后,确定提高代码性能所需要做的工作就变得相当简单。这确实是我能想到的唯一真正通用的建议。

好吧,这就是“在SO上显示代码,并为该特定代码寻求优化建议”。


5

如果可以选择更好的硬件,那么绝对可以。除此以外

  • 检查您使用的是最佳的编译器和链接器选项。
  • 如果热点例程与其他频繁调用者在不同的库中,请考虑将其移动或克隆到调用者模块中。消除了一些调用开销,并可以改善缓存命中率(请参阅AIX如何将strcpy()静态链接到单独链接的共享对象中)。当然,这也可以减少缓存命中率,这就是采取一种措施的原因。
  • 看看是否有可能使用热点例程的专用版本。缺点是要维护多个版本。
  • 看一下汇编器。如果您认为这样做会更好,请考虑为什么编译器没有解决这个问题,以及如何帮助编译器。
  • 考虑:您是否真的在使用最佳算法?这是您输入大小的最佳算法吗?

我会添加到您的第一个参数中:不要忘记关闭编译器选项中的所有调试信息
varnie 2013年

5

谷歌的方式是“缓存它..只要有可能就不要碰磁盘”的一种选择


5

这是我使用的一些快速而肮脏的优化技术。我认为这是“首过”优化。

了解花在哪里的时间确切地了解花费的时间。是文件IO吗?是CPU时间吗?是网络吗?是数据库吗?如果这不是瓶颈,则无法优化IO。

知道您的环境 知道在哪里进行优化通常取决于开发环境。例如,在VB6中,按引用传递比按值传递要慢,但是在C和C ++中,按引用传递要快得多。在C语言中,如果返回码指示失败,则尝试某些操作并做一些不同的事情是合理的;而在Dot Net中,捕获异常比在尝试之前检查有效条件要慢得多。

指数构建上频繁查询数据库字段的索引。您几乎总是可以以空间换取速度。

避免查找 在要优化的循环内部,我避免进行任何查找。在循环外部找到偏移量和/或索引,然后在内部重用数据。

最大限度地减少IO尝试设计的方式,从而减少您必须通过网络连接进行读取或写入的次数

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

带有用户界面的项目的Spawn Threads产生了一个新的线程来执行较慢的任务,这使应用程序感到响应更快,尽管没有。

预处理通常,您可以以空间换取速度。如果有计算或其他繁琐的操作,请查看是否可以在进入关键循环之前预先计算一些信息。


5

如果您有大量高度并行的浮点数学运算,尤其是单精度尝试,请使用OpenCL或(对于NVidia芯片)将其卸载到图形处理器(如果存在)。GPU在其着色器中具有巨大的浮点计算能力,比CPU强大得多。


5

添加此答案,因为我没有看到它包含在所有其他答案中。

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

这至少适用于C / C ++,即使您已经认为自己无需转换-有时也可以测试在需要性能的函数周围添加编译器警告,特别是注意循环内的转换。

特殊的GCC:您可以通过在代码周围添加一些详细的注释来进行测试,

#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

我见过一些情况,通过减少此类警告引起的转换,您可以提高百分之几的速度。

在某些情况下,我会包含一个带有严格警告的标头,以防止发生意外转换,但这是一种折衷方案,因为您可能最终会向安静的有意转换添加大量强制转换,这只会使代码更加混乱,从而使代码最少收获。


这就是为什么我喜欢OCaml中的数字类型之间的转换必须是xplicit的原因。
Gaius 2014年

@Gaius公平点-但在许多情况下,更改语言不是一个现实的选择。由于C / C ++的使用如此广泛,因此即使使C / C ++特定于其编译器,它也可以使其变得更加严格。
ideaman42 2014年

4

有时更改数据的布局会有所帮助。在C语言中,您可能会从一个或多个数组切换到一个数组的结构,反之亦然。


4

调整操作系统和框架。

听起来可能有些大材小用,但您应该这样想:操作系统和框架旨在完成许多事情。您的应用程序仅执行非常具体的事情。如果您可以让OS完全满足您的应用程序的需求,并使您的应用程序了解框架(php,.net,java)的工作方式,那么您的硬件就会变得更好。

例如,Facebook改变了Linux中的一些内核级别的东西,改变了memcached的工作方式(例如,他们编写了memcached代理,并使用udp代替tcp)。

另一个例子是Window2008。Win2K8有一个版本,您可以仅安装运行X应用程序所需的基本操作系统(例如Web应用程序,服务器应用程序)。这样可以减少操作系统在运行进程上的大量开销,并为您提供更好的性能。

当然,作为第一步,您应该始终添加更多硬件。


2
在所有其他方法都失败之后,或者如果特定的OS或Framework功能导致性能显着下降,那将是一种有效的方法,但是实现这一目标所需的专业知识和控制水平可能并不适用于每个项目。
Andrew Neely
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.