首页 文章

最后的性能优化策略[关闭]

提问于
浏览
578

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

我们假设:

  • 代码已经正常工作

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

  • 已经测量了代码,并且已经隔离了有问题的例程

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

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

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

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

30 回答

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

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

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

    • 本地化内存访问,以免烧毁缓存

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

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

  • 29

    扔更多的硬件!

  • 6

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

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

  • 7

    Reduce variable sizes (in embedded systems)

    如果您的变量大小大于特定体系结构上的字大小,则会对代码大小和速度产生重大影响 . 例如,如果你有一个16位系统,并经常使用 long int 变量,后来意识到它永远不会超出范围(-32.768 ... 32.767)考虑将其减少到 short int.

    根据我的个人经验,如果一个程序准备好或几乎准备就绪,但我们意识到它占用目标硬件程序存储器的大约110%或120%,变量的快速标准化通常会经常解决问题 .

    到这个时候,优化算法或代码本身的部分可能会变得令人沮丧:

    • 重组整个结构,程序不再按预期工作,或者至少你引入了很多错误 .

    • 做一些聪明的技巧:通常你花了很多时间优化一些东西,并发现代码大小没有或很小的减少,因为编译器无论如何都会优化它 .

    很多人错误地认为变量确实存储了他们使用变量的单位的数值:例如,他们的变量 time 存储精确的毫秒数,即使只有50毫秒的时间步长是相关的 . 也许如果你的变量代表每个增量1的50毫秒,你就可以适应一个小于或等于字大小的变量 . 例如,在8位系统上,即使简单地添加两个32位变量也会产生相当数量的代码,特别是如果您的寄存器数量较少,而8位加法既小又快 .

  • 9

    由于许多性能问题都涉及数据库问题,因此在调优查询和存储过程时,我会给您一些特定的内容 .

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

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

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

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

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

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

    永远不要放一个循环任何形式的触发器!

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

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

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

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

  • 4

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

    Find out what takes the largest amounts of time first, and understand why.

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

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

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

    HPH,asoudmove .

  • 5

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

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

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

    • 避免内存提取延迟 .

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

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

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

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

  • 12

    建议:

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

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

    • Do use library methods :为了反驳自己,语言库是由比你或我更聪明的人写的;可能性越大越好 . 不要自己实现,除非你能真正加快它(即:总是测量!)

    • Cheat :在某些情况下,虽然您的问题可能存在精确计算,但您可能不需要'exact',有时近似可能是'good enough'并且在交易中要快得多 . 问问自己,如果答案是1%,那真的很重要吗? 5%?甚至10%?
      向下:嗯......答案不准确 .

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

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

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

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

    • 你能禁用断言吗?

    • 针对您的编译器和平台进行微优化 . 风格,"at an if/else, put the most common statement first"

  • 7

    Divide and conquer

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

  • 134

    Tweak the OS and framework.

    这可能听起来有点矫枉过正,但想一想:操作系统和框架设计用于做很多事情 . 您的应用程序只做非常具体的事情 . 如果您可以让操作系统完全满足您的应用程序需求,并让您的应用程序了解框架(php,.net,java)的工作原理,那么您可以从硬件中获得更好的效果 .

    例如,Facebook在Linux中更改了一些kernel level thingys,改变了memcached的工作方式(例如,他们编写了一个memcached代理,以及used udp instead of tcp) .

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

    当然,作为第一步,你应该总是投入更多的硬件......

  • 4

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

    Learn where the time is spent 确切了解花时间 . 它是文件IO吗?是CPU时间吗?是网络吗?是数据库吗?它's useless to optimize for IO if that'不是瓶颈 .

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

    Indexes 在经常查询的数据库字段上构建索引 . 你几乎总能以空间换取速度 .

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

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

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

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

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

  • 4

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

  • 5

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

    • 明显:干燥

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

    • 尽可能使用按位运算符

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

    • 缓存对象

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

    • 尽可能限制字符串操作

  • 11

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

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

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

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

  • 15

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

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

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

  • 5
    • 当你达到自己的目的时使用有效的算法,你需要更多的问题 speed or memory . 在内存中使用缓存到"pay"以获得更高的速度,或使用计算来减少内存占用 .

    • 如果可能(并且更具成本效益) throw hardware at the problem - 更快的CPU,更多内存或HD可以更快地解决问题,然后尝试编码 .

    • Use parallelization 如果可能 - 在多个线程上运行部分代码 .

    • Use the right tool for the job . 一些编程语言创建更高效的代码,使用托管代码(即Java / .NET)加速开发,但本机编程语言创建更快的运行代码 .

    • Micro optimize . 只有适用时才能使用优化的程序集来加速小块代码,在正确的位置使用SSE /向量优化可以大大提高性能 .

  • 5

    通过引用而不是值传递

  • 411

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

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

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

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

  • 47

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

  • 16

    好的,你从一个传统设计精良的非平凡程序开始,没有明显的浪费,并通过一系列的优化,直到它的挂钟时间从48秒减少到1.1秒,源代码大小减少了4倍 . 我的诊断工具was this . 变化的顺序如下:

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

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

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

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

    然后我进行了重新设计,其中程序代码实际上是从较小的源集生成(通过预处理器宏),并且程序不会经常找出程序员知道的相当可预测的事情 . 换句话说,不要“解释”要做的事情的顺序,“编译”它 .

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

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

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

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

    • 现在最大的时间是调用malloc和free . 回收对象 - 2.6秒 .

    • 继续抽样,我仍然发现非必要的操作--1.1秒 .

    总加速系数:43.6

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

    附:可能想知道为什么我没有使用分析器 . 答案是,这些“问题”中几乎每一个都是一个函数调用站点,它堆栈样本精确定位 . 即使在今天,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(类似于列表类) . 它们以通常的方式实现,"information hiding"意味着该类的用户不应该关心它们是如何实现的 . 当写出这些行(大约800行代码中)时,我们没有想到这些行可能是"bottleneck"(我讨厌那个词) . 它们只是推荐的做事方式 . 事后很容易说,应该避免这些,但根据我的经验,所有性能问题都是这样的 . 通常,尽量避免产生性能问题是很好的 . 找到并修复创建的内容会更好,即使它们是事后的(后见之明) . 我希望这会带来一些味道 .

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

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

    这些是通过将项目附加到其末尾来构建列表 . (解决方法是收集数组中的项目,并一次性构建列表 . )有趣的是,这些语句只花费原始时间的3/48(即在调用堆栈中),因此它们不在事实上一开始就是一个大问题 . 但是,在删除第一个问题后,它们花费了3/20的时间,所以现在是"bigger fish" . 总的来说,就是这样 .

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

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

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

  • 8

    当你无法再提高性能时 - 看看你是否可以改善 perceived 性能 .

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

    几个例子:

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

    • 显示结果,而不是最后一次显示结果

    • 精确的进度表

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

  • 159

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

  • 4

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

    • ...如果它's memory - find one of the books written long time ago by Knuth, one of 233232 series. Most likely it'一个关于排序和搜索 - 如果我的记忆是错的,那么你将不得不找出他谈论如何处理慢速磁带数据存储 . 精神上将他的内存/磁带对分别转换为你的一对缓存/主内存(或一对L1 / L2缓存) . 研究他描述的所有技巧 - 如果你不雇用科学家 - 而是手动优化通过,直到你要么获胜还是走向死胡同 . 你提到挤出最后几个百分点吧?如果确实很少,你很可能会获胜 .

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

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

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

    HTH :)

  • 12

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

    另一方面,谷歌人也因为投入大量人力和资源来解决他们正在使用的项目,工具和基础设施中的一些问题而闻名,例如whole program optimization for gcc有一个专门的工程师团队攻击gcc内部为Google典型的用例场景做好准备 .

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

  • 57

    更多建议:

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

    • Move I/O up-front :预先加载计算所需的所有数据,以便在关键算法的核心内没有重复的I / O等待(并且可能因此在加载时重复磁盘搜索)一次点击中的所有数据可能会避免寻找) .

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

    • Threaded I/O :对于那些胆大的人,将'I/O up-front'或'Delay I/O'与实际计算结合起来,将加载移动到并行线程中,这样当您加载更多数据时,您可以对已有数据进行计算,或者在计算时进行计算下一批数据可以同时写出最后一批的结果 .

  • 182

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

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

  • 25

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

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

    • Analyze :使用可用的任何和所有日志记录方法准确确定在执行受影响的操作期间正在传输和接收的数据 . 理想情况下,您的应用程序可以捕获客户端和服务器发送和接收的数据 . 如果这些也包括时间戳,甚至更好 . 如果在网络级别上没有足够的日志记录 .

    • Cache :查找重复传输静态或不经常更改的数据并考虑适当的缓存策略的情况 . 典型示例包括"pick list"值或其他"reference entities",在某些业务应用程序中可能会出乎意料地大 . 在许多情况下,用户可以接受他们必须重新启动或刷新应用程序以更新不经常更新的数据,特别是如果它可以显着减少从常用用户界面元素的显示时间 . 请确保您了解已部署的缓存内容的真实行为 - 许多常见的缓存方法(如HTTP ETag的)仍然需要一个网络往返,以确保一致性,并在网络延时是昂贵的,你可能能够避免它完全一种不同的缓存方法 .

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

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

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

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

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

  • 76

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

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

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

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

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

    • Sequential floating-point ops . 制作这些SIMD .

    还有一件事我喜欢做:

    • Set your compiler to output assembly listings 并查看它为代码中的热点函数发出的内容 . 所有那些聪明的优化"a good compiler should be able to do for you automatically"?有可能你的实际编译器没有看到GCC发出真正的WTF代码 .
  • 7

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

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

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

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

    • 看看汇编程序 . 如果您认为它可能会更好,请考虑编译器为什么没有解决这个问题,以及如何帮助编译器 .

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

相关问题