这个网站上已经有很多性能问题了,但我发现几乎所有这些问题都是针对特定问题而且相当狭窄。几乎所有人都重复这些建议,以避免过早优化。
我们假设:
我在这里寻找的是在关键算法中挤出最后几个百分点的策略和技巧,除此之外别无选择。
理想情况下,尝试使答案语言不可知,并在适用的情况下指出建议策略的任何缺点。
我将根据自己的初步建议添加回复,并期待Stack Overflow社区可以想到的任何其他内容。
答案 0 :(得分:416)
答案 1 :(得分:183)
建议:
答案 2 :(得分:159)
当您无法再提高效果时 - 看看您是否可以改善感知效果。
您可能无法更快地使用fooCalc算法,但通常有一些方法可以让您的应用程序对用户更敏感。
一些例子:
这些不会使您的程序更快,但它可能会让您的用户更快乐。
答案 3 :(得分:135)
我大部分时间都在这个地方度过。广泛的描述是运行您的探查器并将其记录下来:
__restrict
来保证编译器有关别名的信息。还有一件事我想做:
答案 4 :(得分:76)
投入更多硬件!
答案 5 :(得分:58)
更多建议:
避免I / O :任何I / O(磁盘,网络,端口等)都是 总是比任何代码慢得多 执行计算,所以摆脱你做的任何I / O. 不是严格需要。
预先移动I / O :加载您要去的所有数据 需要预先计算,所以你不需要 在关键的核心内重复进行I / O等待 算法(也许是因为重复磁盘寻找,当时 在一次点击中加载所有数据可能会避免寻找。
延迟I / O :请不要写出结果 计算结束,将它们存储在数据结构中 然后在艰苦的工作结束时将其全部丢弃 完成了。
线程I / O :对于那些大胆的人来说,结合'I / O 使用实际计算的“前期”或“延迟I / O” 将加载移动到并行线程中,以便同时进行 您正在加载更多可用于计算的数据 您已经拥有的数据,或者在计算下一个数据时 批量数据可以同时写出结果 从最后一批开始。
答案 6 :(得分:47)
由于许多性能问题都涉及数据库问题,因此在调优查询和存储过程时,我会给您一些具体的内容。
避免在大多数数据库中使用游标。避免循环。大多数情况下,数据访问应该基于集合,而不是记录处理。这包括当您想要一次插入1,000,000条记录时不重复使用单个记录存储过程。
永远不要使用select *,只返回您实际需要的字段。如果存在任何连接,则尤其如此,因为连接字段将被重复,从而导致服务器和网络上的不必要的负载。
避免使用相关子查询。使用连接(包括可能的连接到派生表)(我知道这适用于Microsoft SQL Server,但在使用不同的后端时测试建议。)
索引,索引,索引。如果适用于您的数据库,请更新这些统计信息。
进行查询sargable。意义避免使得无法使用索引的事情,例如在like子句的第一个字符或连接中的函数中使用通配符或作为where语句的左侧部分。
使用正确的数据类型。在日期字段上进行日期数学比在必须尝试将字符串数据类型转换为日期数据类型更快,然后进行计算。
永远不要将任何类型的循环放入触发器中!
大多数数据库都有办法检查查询执行的执行方式。在Microsoft SQL Server中,这称为执行计划。先检查一下,看看问题区域在哪里。
考虑查询运行的频率以及在确定需要优化的内容时运行所需的时间。有时,您可以通过轻微调整获得更多性能,每天运行数百万次的查询,而不是每月只运行一次的long_running查询擦除时间。
使用某种分析器工具来查找实际发送到数据库和从数据库发送的内容。我记得有一次我们无法弄清楚为什么页面在存储过程很快时加载速度太慢,并通过剖析网页要求查询多次而不是一次查询。
探查器还可以帮助您找到阻止谁的人。由于来自其他查询的锁定,一些在单独运行时快速执行的查询可能会变得非常慢。
答案 7 :(得分:29)
今天最重要的限制因素是有限的内存bandwitdh 。多核只会使这种情况变得更糟,因为带宽在核心之间共享。此外,用于实现高速缓存的有限芯片面积也在核心和线程之间划分,甚至更加恶化了这个问题。最后,保持不同高速缓存一致所需的芯片间信令也随着核心数量的增加而增加。这也增加了惩罚。
这些是您需要管理的效果。有时通过微观管理代码,但有时通过仔细考虑和重构。
很多评论已经提到缓存友好代码。至少有两种不同的风格:
第一个问题与使数据访问模式更加规则有关,使硬件预取器能够高效工作。避免动态内存分配,将内存中的数据对象分散开来。使用线性容器代替链表,散列和树。
第二个问题与改进数据重用有关。更改算法以处理适合可用缓存的数据子集,并在缓存仍在缓存中时尽可能多地重用这些数据。
更紧密地打包数据并确保在热循环中使用缓存行中的所有数据,这将有助于避免这些其他影响,并允许在缓存中安装更多有用的数据。
答案 8 :(得分:25)
答案 9 :(得分:16)
虽然我喜欢Mike Dunlavey的答案,但实际上这是一个很好的答案确实有支持的例子,我认为它可以很简单地表达出来:
首先了解花费最多时间的内容,然后了解原因。
时间生成器的识别过程可以帮助您了解必须优化算法的位置。这是唯一一个无所不包的语言不可知的答案,我可以找到一个已经应该完全优化的问题。还假设您希望在追求速度时独立于架构。
因此,尽管可以优化算法,但实现它可能不是。标识允许您知道哪个部分是哪个:算法或实现。所以,无论哪个时间最重要的是你的主要候选人。但是既然你说你想要挤出最后几个百分点,你可能还想检查较小的部分,那些你最初没有检查过的部分。
最后,对于实现相同解决方案或可能不同算法的不同方式的性能数据的一些试验和错误,可以带来有助于识别时间浪费和节省时间的见解。
HPH, asoudmove。
答案 10 :(得分:16)
您应该考虑“Google观点”,即确定您的应用程序如何在很大程度上并行化和并发化,这在某种程度上也意味着要考虑在不同的机器和网络上分发您的应用程序,以便理想情况下与您投入的硬件几乎呈线性关系。
另一方面,谷歌人也因为投入大量人力和资源来解决他们正在使用的项目,工具和基础设施中的一些问题而闻名,例如whole program optimization for gcc专用工程师团队攻击gcc内部组件,以便为Google典型的用例场景做好准备。
同样,对应用程序进行概要分析不再仅仅意味着简单地分析程序代码,而是分析其周围的所有系统和基础设施(比如网络,交换机,服务器,RAID阵列),以便从系统的角度来识别冗余和优化潜力。图。
答案 11 :(得分:15)
答案 12 :(得分:12)
分而治之
如果正在处理的数据集太大,则循环遍历其中的数据集。如果您已经完成了正确的代码,那么实现应该很容易。如果你有一个单片程序,现在你知道的更好。
答案 13 :(得分:12)
答案 14 :(得分:11)
首先,如前几个答案所述,了解一下您的表现是什么 - 是内存还是处理器,网络或数据库或其他东西。取决于......
...如果是记忆 - 找到很久以前由Knuth撰写的一本书,“计算机编程艺术”系列之一。很可能是关于排序和搜索的问题 - 如果我的记忆错了,那么你必须找出他谈论如何处理慢速磁带数据存储的问题。精神上将他的内存/磁带对分别转换为您的一对缓存/主内存(或一对L1 / L2缓存)。研究他描述的所有技巧 - 如果你找不到解决问题的方法,那就聘请专业的计算机科学家进行专业研究。如果您的内存问题偶然发生在FFT(执行基数为2的蝴蝶时缓存未达到位反转索引),那么就不要雇用科学家 - 而是手动优化传递,直到您获胜或获得走到尽头。你提到挤出最后几个百分点吧?如果很少,你很可能会赢。
...如果是处理器 - 切换到汇编语言。研究处理器规范 - 什么需要滴答,VLIW,SIMD。函数调用很可能是可替换的滴答滴答者。学习循环转换 - 管道,展开。乘法和除法可以用位移替换/插值(乘以小整数可以用加法替换)。尝试使用更短数据的技巧 - 如果你幸运的话,一个64位的指令可能会被替换为32位上的两个或16位上的4位或8位上的8位数。还可以尝试更长的数据 - 例如,在特定处理器上,浮点计算可能比双倍计算慢。如果您有三角函数,请使用预先计算的表格进行对抗;还要记住,如果精度损失在允许的限度内,那么小值的正弦可能会被该值替换。
...如果是网络 - 想想压缩你传递的数据。用二进制替换XML传输。研究方案。如果你能以某种方式处理数据丢失,请尝试UDP而不是TCP。
...如果是数据库,那么,去任何数据库论坛并征求意见。内存数据网格,优化查询计划等等。
HTH:)
答案 15 :(得分:9)
缓存!一种廉价的方式(在程序员的努力下)几乎可以做任何事情就是在程序的任何数据移动区域添加一个缓存抽象层。无论是I / O还是传递/创建对象或结构。通常,很容易将缓存添加到工厂类和读/写器中。
有时缓存不会给你带来太多好处,但是这是一个简单的方法,只需添加缓存,然后在没有帮助的地方禁用缓存。我经常发现这可以获得巨大的性能而无需对代码进行微观分析。
答案 16 :(得分:8)
我认为这已经以不同的方式说过了。但是,当您处理处理器密集型算法时,您应该以最重要的内容为代价来简化内部循环中的所有内容。
对某些人来说,这似乎是显而易见的,但无论我使用何种语言,我都会尝试关注这一点。例如,如果您正在处理嵌套循环,并且您发现有机会将某些代码放在某个级别,那么在某些情况下您可以大大加快代码速度。作为另一个例子,有些事情需要考虑,比如使用整数而不是浮点变量,并且尽可能使用乘法而不是除法。同样,这些是你最内循环应该考虑的事情。
有时您可能会发现在内循环内对整数执行数学运算的好处,然后将其缩小为可以在之后使用的浮点变量。这是在一个部分牺牲速度以提高另一部分的速度的一个例子,但在某些情况下,回报可能是值得的。
答案 17 :(得分:7)
我花了一些时间来优化在低带宽和长延迟网络(例如卫星,远程,离岸)上运行的客户端/服务器业务系统,并且能够通过公平的方式实现一些显着的性能提升可重复的过程。
衡量:首先了解网络的基础容量和拓扑。与业务中的相关网络人员交谈,并利用ping和traceroute等基本工具在典型的运营期间(至少)建立每个客户端位置的网络延迟。接下来,对显示有问题症状的特定最终用户功能进行准确的时间测量。记录所有这些测量值,以及它们的位置,日期和时间。考虑构建最终用户"网络性能测试"功能到您的客户端应用程序,允许您的高级用户参与改进过程;当你处理被表现不佳的系统感到沮丧的用户时,像这样授权他们会产生巨大的心理影响。
分析:使用可用的任何和所有日志记录方法准确确定在执行受影响的操作期间正在传输和接收的数据。理想情况下,您的应用程序可以捕获客户端和服务器发送和接收的数据。如果这些也包括时间戳,甚至更好。如果没有足够的日志记录(例如封闭系统,或无法将修改部署到生产环境中),请使用网络嗅探器并确保您真正了解网络级别正在进行的操作。
缓存:查找重复传输静态或不经常更改的数据并考虑适当的缓存策略的情况。典型的例子包括"选择列表"值或其他"引用实体",在某些业务应用程序中可能会出乎意料地大。在许多情况下,用户可以接受他们必须重新启动或刷新应用程序以更新不经常更新的数据,特别是如果它可以显着地显示常用的用户界面元素。确保您了解已部署的缓存元素的实际行为 - 许多常见的缓存方法(例如HTTP ETag)仍然需要网络往返以确保一致性,并且在网络延迟昂贵的情况下,您可以完全避免它一种不同的缓存方法。
并行化:查找逻辑上不需要严格按顺序发布的顺序事务,并重新编写系统以并行发布它们。我处理了一个案例,其中端到端请求具有~2s的固有网络延迟,这对于单个事务不是问题,但是在用户重新获得对客户端应用程序的控制之前需要6次连续2次往返,它成了沮丧的巨大根源。发现这些交易实际上是独立的,这使得它们可以并行执行,从而将最终用户的延迟减少到非常接近单次往返的成本。
合并:在顺序执行顺序请求 的情况下,寻找机会将它们合并为一个更全面的请求。典型的例子包括创建新实体,然后是将这些实体与其他现有实体相关联的请求。
压缩:寻找利用有效负载压缩的机会,方法是将文本形式替换为二进制文本形式,或使用实际压缩技术。许多现代(即十年内)技术堆栈几乎透明地支持这一点,因此请确保它已配置好。我经常对压缩产生的重大影响感到惊讶,因为似乎很明显问题基本上是延迟而不是带宽,事后发现它允许事务适合单个数据包或以其他方式避免数据包丢失,因此具有特大优势对绩效的影响。
重复:返回到开头并重新衡量您的操作(在相同的位置和时间),并进行改进,记录并报告结果。与所有优化一样,可能已经解决了一些问题,暴露了现在占主导地位的其他问题。
在上面的步骤中,我专注于与应用程序相关的优化过程,但当然您必须确保以最有效的方式配置底层网络本身以支持您的应用程序。让网络专家参与业务,并确定他们是否能够应用容量改进,QoS,网络压缩或其他技术来解决问题。通常情况下,他们无法理解您的应用程序的需求,因此您必须配备(在分析步骤之后)与他们讨论,以及为任何成本制定业务案例,这一点非常重要你会要求他们招致的。我遇到过错误的网络配置导致应用程序数据通过慢速卫星链路而不是陆上链路传输的情况,原因很简单,因为它使用的TCP端口并不是众所周知的#34;由网络专家组成;显然纠正这样的问题会对性能产生巨大影响,根本不需要软件代码或配置更改。
答案 18 :(得分:7)
不像以前的答案那样深度或复杂,但是这里有: (这些是初级/中级水平)
答案 19 :(得分:7)
最后几个%是一个非常依赖CPU和应用程序的东西......
这个名单还在继续......但这些事情确实如此 不得已......
为x86构建,并针对代码运行Valgrind / Cachegrind 进行适当的性能分析。或德州仪器 CCStudio有一个甜蜜的探测器。然后你真的知道在哪里 专注......
答案 20 :(得分: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发送或收到任何包装。
答案 21 :(得分:7)
很难给出这个问题的通用答案。这实际上取决于您的问题域和技术实现。一种相当语言中立的通用技术:识别无法消除的代码热点,并手动优化汇编代码。
答案 22 :(得分:5)
以下是我使用的一些快速而肮脏的优化技术。我认为这是“第一次通过”优化。
了解花费的时间确切了解花费时间的内容。它是文件IO吗?是CPU时间吗?是网络吗?是数据库吗?如果不是瓶颈,那么优化IO是没用的。
了解您的环境了解优化位置通常取决于开发环境。例如,在VB6中,通过引用传递比传递值慢,但在C和C ++中,通过引用传递速度要快得多。在C中,如果返回代码指示失败,尝试某些操作并执行不同的操作是合理的,而在Dot Net中,捕获异常比在尝试之前检查有效条件要慢得多。
索引在经常查询的数据库字段上构建索引。你几乎总能以空间换取速度。
避免查找在要优化的循环内部,我避免必须进行任何查找。找到循环外的偏移量和/或索引,并重用其中的数据。
最小化IO 尝试以减少必须通过网络连接读取或写入的次数的方式进行设计
减少抽象代码必须处理的抽象层越多,它就越慢。在关键循环内部,减少抽象(例如,揭示避免额外代码的低级方法)
产生线程用于具有用户界面的项目,产生新线程以执行较慢的任务会使应用程序感觉响应更快,尽管不是。
预处理您通常可以用空间换取速度。如果有计算或其他强烈操作,请查看您是否可以在进入关键循环之前预先计算某些信息。
答案 23 :(得分:5)
添加此答案,因为我没有看到它包含在所有其他答案中。
这至少适用于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
我见过这样的情况,你可以通过减少这样的警告引起的转换来获得几个百分点的加速。
在某些情况下,我有一个带有严格警告的标题,我会将其保留以防止意外转换,但这是一种权衡,因为您最终可能会添加大量演员阵容以安静的故意转换,这可能会使代码更多最小的收获杂乱无章。
答案 24 :(得分:5)
google方式是一种选择“缓存它......只要有可能就不要触摸磁盘”
答案 25 :(得分:5)
如果更好的硬件是一种选择,那么一定要去做。否则
答案 26 :(得分:5)
不可能说。这取决于代码的样子。如果我们可以假设代码已经存在,那么我们可以简单地看一下它并从中找出,如何优化它。
更好的缓存局部性,循环展开,尝试消除长依赖链,以获得更好的指令级并行性。在可能的情况下,首选条件移动分支。尽可能利用SIMD说明。
了解您的代码正在做什么,并了解其运行的硬件。然后,确定您需要做什么来提高代码性能变得相当简单。这真的是我能想到的唯一真正的一般建议。
嗯,那,和“在SO上显示代码并询问针对该特定代码的优化建议”。
答案 27 :(得分:4)
调整操作系统和框架。
这可能听起来有点矫枉过正,但想一想:操作系统和框架设计用于做很多事情。您的应用程序只做非常具体的事情。如果您可以让操作系统完全满足您的应用程序需求,并让您的应用程序了解框架(php,.net,java)的工作方式,那么您可以从硬件中获得更好的效果。
例如,Facebook改变了Linux中的一些kernel level thingys,改变了memcached的工作方式(例如他们编写了一个memcached代理,以及used udp instead of tcp)。另一个例子是Window2008。 Win2K8有一个版本,你可以只安装运行X应用程序所需的基本操作系统(例如Web应用程序,服务器应用程序)。这减少了操作系统在运行进程时产生的大量开销,并为您提供了更好的性能。
当然,作为第一步,你应该总是投入更多硬件......
答案 28 :(得分:4)
通过引用传递而不是按值传递
答案 29 :(得分:4)
减少变量大小(在嵌入式系统中)
如果您的变量大小大于特定体系结构上的字大小,则会对代码大小和速度产生重大影响。例如,如果你有一个16位系统,并经常使用long int
变量,后来意识到它永远不会超出范围(-32.768 ... 32.767),可以考虑将其减少到{{1} }
根据我的个人经验,如果一个程序准备好或几乎准备就绪,但我们意识到它占用目标硬件程序内存的大约110%或120%,变量的快速标准化通常会经常解决问题。
此时,优化算法或代码本身的部分可能会变得令人沮丧:
许多人错误地认为变量确实存储了他们使用变量的单位的数值:例如,他们的变量short int.
存储确切的毫秒数,即使只有时间步长说50毫秒是相关的。也许如果你的变量代表每个增量为1的50毫秒,你就可以适应一个小于或等于字大小的变量。例如,在8位系统上,即使简单地添加两个32位变量也会生成相当数量的代码,特别是如果您的寄存器数量较少,而8位增加的数据既小又快。
答案 30 :(得分:4)
有时,更改数据布局可能会有所帮助。在C中,您可以从一个或多个数组切换到数组结构,反之亦然。
答案 31 :(得分:4)
如果你有很多高度并行的浮点数学 - 特别是单精度 - 尝试使用OpenCL或(对于NVidia芯片)CUDA将其卸载到图形处理器(如果存在)。 GPU在着色器中具有巨大的浮点计算能力,远远高于CPU。
答案 32 :(得分:3)
在具有模板(C ++ / D)的语言中,您可以尝试通过模板参数传播常量值。你甚至可以通过一个开关为小组不是真正的常量值做这个。
Foo(i, j); // i always in 0-4.
变为
switch(i)
{
case 0: Foo<0>(j); break;
case 1: Foo<1>(j); break;
case 2: Foo<2>(j); break;
case 3: Foo<3>(j); break;
case 4: Foo<4>(j); break;
}
缺点是缓存压力,所以这只是深度或长期运行的调用树的增益,其中值在持续时间内是恒定的。
答案 33 :(得分:3)
没有这样的一揽子陈述可能,这取决于问题领域。一些可能性:
由于您没有完全指定您的应用程序是100%计算:
如果您使用的是数据库,那么它恰好是Microsoft SQL Server:
IF 您的应用程序纯粹是在计算,您可以查看我的关于旋转大图像的缓存优化的这个问题。速度的提高使我大吃一惊。
这是一个很长的镜头,但也许它提出了一个想法,特别是如果你的问题在成像领域:rotating-bitmaps-in-code
另一个是尽可能避免动态内存分配。一次分配多个结构,立即释放它们。
否则,识别你的最紧密的循环,并在这里发布,无论是否为伪,都有一些数据结构。