
多年来,我在一些完全不同的领域(渲染,并行CPU和GPU计算,Web负载测试,文件系统I / O,分布式系统,移动设备)进行了很多性能分析,但我从未真正考虑过我的过程。 我最初打算以此作为个人练习,但后来意识到分享可能会有所帮助。
这些不是特定于域的概要分析技术,而是可以应用于任何域的高级策略的集合。
我为什么要优化?
我通常会遇到一个问题,例如“此场景的渲染速度不够快”,但是正式制定大目标很重要。 例如,“我希望该场景渲染得更快,因为连续丢失多个帧会损害用户体验”,甚至更高的目标,例如“良好的VR体验很重要,因为Tilt Brush通常是一个人拥有的第一个VR体验。”
这看起来似乎很愚蠢或显而易见,但是如果您像我一样,分析和优化一旦开始就可能是诱人的事情,诱使您进行越来越多的优化-在那些时刻,使用高级目标作为指导很重要帮助您集中精力进行优化,优化以及最重要的是何时停止。
选择指标
剖析系统通常会提供大量数据,而选择最有效的度量标准可能会令人费解。 例如,在测量帧速率时,我们是否应该优化平均帧时间,中值帧时间,每秒平均帧数,丢失帧的百分比或不丢失帧的总时间? 您可能不应该先回答“我为什么要优化?”这个问题,

在此示例中,我想知道用户具有良好的VR体验,那么中值帧时间是否可以代表这一点? 可能不会。 丢帧的百分比可能是更好的指标。
当面对大量指标时,我寻找一组相关指标的方法是将它们全部记录下来,然后对性能状况的好坏进行几次测试。 然后,我对结果进行筛选,以确定哪些结果与好和坏测试案例最紧密相关。
最后,了解指标之间的关系可以让您计算派生指标。
维修费用
如果长期维护很重要(几乎始终如此),则应权衡每次优化的成本和对未来工作造成的成本。 改进10倍可能会立即带来好处,但是如果它在以后的所有工作中也降低了10倍,那可能就不值得了。 过早的优化理所当然会引起很多关注,但必要的优化成本也很重要。
衡量每项改进
当然,这应该很明显,但是有时进行优化非常诱人,因为“ 我 知道 这是一件好事。 ”我甚至尝试评估最明显的优化,因为:
- 测得的改进应记录为源代码提交消息的一部分。
- 改进可能比我预期的要好,或者优化可能出奇地无效。
- 该数字可能对以后重新访问有用,例如,当尝试确定将来的回归时。
衡量性能所花费的时间总是值得的,毕竟,除非您知道它可以有所改进,否则您不能将其称为“改进”。
估计,仿真和原型
在进行优化之前,了解系统的“光速”很有用。 也就是说,我可能*希望*实现最快/最小/最佳结果。 在剖析并确定了瓶颈之后,我开始制定解决方案。
我没有直接进入实现,而是尝试估计变更对当前系统和光速的影响。 分析改进的复杂性也很重要:这是持续的改进还是渐近的改进? 如果这是一个持续的改进,那么在已知的复杂度曲线上值得还是会很快变得无效?
一旦完成分析并且似乎值得进行更改,我仍将保留完整的实现,而是尝试针对具有代表性的原型。 这可以像禁用代码以模拟最终解决方案一样简单,也可以像实际优化那样快速简便地实现。 关键是在投入时间使变更生产就绪之前,即通过利用80/20规则来发挥您的优势,从而验证分析并证明优化有效。
记录数据
即使是简单的实验,我也倾向于正式记录数字,但这并不意味着它们必须进入数据库。 通常,我只是将它们刮到笔记本上或存储在电子表格中。 无论使用哪种方法,我都会尝试包含几个关键项目。
每个指标的值 (当然),但是在四舍五入和汇总时要格外小心,以确保每个值在每次运行中均得到一致处理。
度量单位 (秒,兆字节等)至关重要。 这是一件非常简单的事情,并且可以保证在内存消失之后您的数据将变得有意义。
实验日期 。 同样,这很简单,但很重要。
实验本身应该被描述。 也就是说,记录数字时系统的状态是什么? 我通常会尝试为我所变化的系统方面找到一些描述性的词。 对于正式测试, 应记录建造图章 。
还应记录汇总详细信息 ; 例如,如果该次数是10次运行的平均值,则应将其与数据一起记录下来。 在直接比较的所有试验中,汇总数据应保持一致。
静止
静止状态是安静或不活动的状态。 性能测试几乎永远不会作为一个完全隔离的系统运行,因此总会有外部因素会影响测试结果。 在运行测试时,我尝试在主机系统中尽可能地达到静态。 确切的方法必须根据具体情况决定,但是下面是一些常见的示例:
- 停止不相关的后台服务。
- 每次测试运行后重新启动该过程。
- 加载资源并等待任何异步处理完成。
- 禁用CPU和GPU的动态时钟速率。
- 在没有共享硬件(非VM)的专用计算机上运行。
- 通过在同一过程中运行两次测试来热缓存。
- 或者,在进程运行之前显式刷新缓存。
- 在运行测试之前,明确触发垃圾回收。
对于以上所有内容,您应该问自己对自己想要实现的目标是否有意义。 在某些情况下,您希望将上面列出的某些项目组合在一起,例如,您可能希望对缓存进行预热,但同时也可能希望对冷缓存进行测量,以设置“最坏情况”的预期。
可重复的实验
很容易陷入困境,在这里我快速进行迭代,更改测试用例,记录数字并快速更改代码,而不会保存任何可重复的内容。 我尝试避免在此模式下停留太长时间,而应使用此快速迭代来找到有用的实验。 找到有用的实验后,我将其作为正式测试进行检入,其他人可以用来复制发现。 除了其他工程师之外,在将重点转移到其他任务之后,自动化系统还可以使用可重现的测试来监视进度并防止随着时间的推移而出现退化。
自动化与回归
对于任何认真的优化工作,您都需要自动化测试。 任何重要的优化都应进行回归测试,以确保优化不会回归。 良好的自动性能分析系统的属性如下:
- 存储上述重要数据,包括构建标记。
- 记录的数据是不可变的。
- 以静态运行测试。
- 发送测试失败的通知。
- 用户定义的故障条件阈值。
- 自动识别嘈杂/不一致的测试。
- 能够临时启用/禁用测试。
- 测试可以连续运行,也可以基于触发器(例如新更改)运行。
- 提供度量数据的可视化。
定位和平衡资源
通常,系统中有许多资源可以协同工作以产生最终效果。 在设计性能测试时,我会尝试考虑哪些资源受到压力以及我打算针对哪些资源。 例如,在测试片段着色器的性能时,考虑测试网格中的顶点数非常重要,因为顶点着色器实际上可能成为瓶颈并隐藏了片段着色器的性能。
在测试的初始构造以及性能提高或降低时会发生什么都必须考虑这一点,因为瓶颈可能在优化的粗略过程中从一种资源过渡到另一种资源。
综合基准和微观基准
综合测试是一种创建代表实际性能的方案的测试,但它是用户永远不会实际运行的方案。 微型基准测试是一项综合测试,非常关注系统范围极为狭窄的方面。
综合测试的风险在于它们可能无法代表实际性能。 通过扩展,微型基准也是如此,但是微型基准会带来额外的风险。 由于微观基准测试是在整个系统中独立执行的,因此它们通常会显示出微小的变化,作为大规模的改进。
例如,在隔离状态下,我可能提高了紧密循环的缓存一致性,从而使速度提高了10倍,但是当在整个系统的上下文中运行时,优化可能会消失,或者收益可能并不重要,例如,循环就是仅占整体相关工作量的0.01%。
通讯
如果我想与他人分享我的进步,仅记录原始数字是不够的,相反,我提供了数字的书面分析,并试图以一种易于理解的方式直观地呈现数据。 在编写分析时,我会尽量保持重点,并牢记要传达的内容。

图形是有帮助的,但我也认为它们具有可读性。 例如,图形是否集中在数据的一个方面,并且是否明显促进了我试图分享的想法? 创建一个充满数据的图很容易(也很有趣),但实际上它比信息少,更集中的图有用。

生成报告时,我的工作流程是将数据存储在电子表格中,并将其相关部分链接到实际的分析文档中。 我使用电子表格以各种方式处理数据并生成图形。 同样,链接到文档的任何图形或表格都经过有针对性的优化,以提高可读性。 诸如MS Office或Google Docs之类的工具可让您在文档之间链接和嵌入数据,从而更轻松地使它们保持同步。
结论
优化很有趣,一旦上手就容易上瘾。 我希望分享我的经验会有所帮助。 随时在这里发表评论/问题/更正,或在推特上ping我!