可视化动态指标的性能分析蓝图
在软件开发中,代码性能分析是一项重要的工作。传统的代码性能分析方法虽然能帮助定位性能瓶颈,但在消除这些瓶颈方面的支持有限。本文介绍了两种性能分析蓝图,它们可以帮助识别和消除性能瓶颈,并且以可视化框架 Mondrian 为例进行了说明。
1. 引言
尽管计算资源日益丰富,但通过代码性能分析进行执行优化仍然是软件开发中的重要活动。CPU 时间分析器是识别瓶颈(即占用大量执行时间的程序元素)的关键工具。如今,没有包含代码分析器或由第三方提供代码分析器的编程环境是不可想象的。
然而,回顾代码分析工具的历史,我们发现工具的可用性和分析开销的减少在不断改进,但所提供的抽象集却保持不变。大多数代码分析领域的研究都集中在减少代码插桩和观察所引发的开销上,并且面向对象应用程序的分析抽象与过程式应用程序的分析抽象非常接近。
本文的贡献在于将先前用于静态软件分析的一些可视化方法应用于动态指标的性能分析。我们提出了一种可视化机制,用于呈现动态信息,从而有效地比较与程序执行相关的不同指标。结构分布蓝图和行为分布蓝图这两种可视化方法旨在识别瓶颈,并提供消除瓶颈的提示。
2. 性能分析蓝图
2.1 性能分析蓝图概述
时间性能分析蓝图是一种图形化表示,旨在帮助程序员评估时间分布、识别瓶颈,并提供消除瓶颈的提示。其本质是促进对构成程序结构和行为的元素进行更好的比较。这些蓝图使用由节点和边组成的图隐喻来呈现信息。
节点的大小暗示其在执行中的重要性。如果节点代表方法,大节点可能表示程序执行在该方法上花费了“大量时间”,这个“大量时间”可以通过与其他节点的高度和/或宽度进行视觉比较来量化。颜色用于传达布尔属性(如灰色节点表示总是返回相同值的方法)或指标(如颜色梯度映射到方法被调用的次数)。
我们提出的两种蓝图有助于识别代码优化的机会,为程序员提供了两条重构原则:一是让常用方法更快,二是减少调用慢速方法的次数。本文采用的指标有助于找到不太可能产生副作用或总是返回相同结果的方法,这些方法是简单缓存优化的良好候选者。
2.2 多指标视图
我们提出的蓝图以多指标视图的形式进行图形化渲染。多指标视图是一种轻量级的软件可视化方法,它通过软件指标进行增强,已成功用于提供有助于软件理解和可视化的“软件地图”。
对于表示实体的二维节点,我们可以将多达 5 个指标映射到节点的特征上:位置(X 和 Y)、大小(宽度和高度)和颜色。
-
位置
:节点位置的 X 和 Y 坐标可以反映两个测量值。
-
大小
:节点的宽度和高度可以呈现两个测量值,通常节点越宽越高,关联的指标值越大。
-
颜色
:白色和黑色之间的颜色区间可以呈现一个测量值,通常的约定是测量值越高,节点颜色越深,例如浅灰色表示比深灰色更小的测量值。
边也可以沿着多个维度(宽度、颜色、方向等)呈现属性,但在本文的工作中,所有边都是相同的。
2.3 结构分布蓝图
面向对象程序的执行会产生大量信息,但并非所有这些维度都能以有意义的方式进行可视化。结构分布蓝图展示了选定的一组指标,这些指标指示了执行时间在程序静态结构(即类、方法和类层次结构)上的分布。
| 结构分布蓝图 | 详情 |
|---|---|
| 范围 | 全系统执行时间 |
| 边 | 类继承关系(上方是下方的超类) |
| 布局 | 外部节点采用树布局,内部节点采用网格布局(内部节点按高度递增排序) |
| 指标尺度 | 线性(节点宽度除外) |
| 节点 | 外部节点是类,内部节点是方法 |
| 内部节点颜色 | 不同接收者的数量 |
| 内部节点高度 | 方法的总执行时间 |
| 内部节点宽度 | 执行次数(对数尺度) |
| 示例 | 图 2 |
以可视化框架 Mondrian 为例,结构分布蓝图可以帮助我们分析不同类和方法的 CPU 时间消耗情况。例如,在一个简单的可视化代码片段中,涉及到 MOGraphElement、MOViewRenderer、MONode 和 MORoot 这四个类。
ProfilingPackageSpy
viewProfiling: [
| view |
view := MOViewRenderer new.
view nodes: (1 to: 100)
forEach: [:each | view nodes: (1 to: 100)].
view root applyLayout ]
inPackage: ’Mondrian’
在这个例子中,MOGraphElement 类包含许多“大而黑”的方法,这表明该类在代码片段执行中处于核心地位,这些方法消耗了大量的 CPU 时间,并且被许多不同的实例调用。MONode 类中的
translateTo:
方法消耗了 22% 的总 CPU 时间。而 MOViewRenderer 类的方法调用次数少,消耗的 CPU 时间也少,并且这些方法被调用的实例数量较少。MORoot 类的方法执行频率不高,其中
applyLayout
方法虽然只执行一次,但消耗了 97% 的 CPU 时间,并且该方法会调用 MOGraphElement 类的
applyLayout
方法。
通过结构分布蓝图,我们可以快速定位到消耗大量 CPU 时间的方法,如 MONode>>
translateTo:
、MOGraphElement>>
bounds
、MOGraphElement>>
shapeBoundsAt:ifPresent:
和 MOGraphElement>>
applyLayout
。
2.4 行为分布蓝图
在纯面向对象的环境中,计算完全通过对象之间的消息传递来执行,CPU 时间消耗分布在方法执行中。评估方法调用的运行时分布可以补充前面所述的结构分布。为了反映这种基于方法调用的性能分析,我们提供了行为分布蓝图。
| 行为分布蓝图 | 详情 |
|---|---|
| 范围 | 给定起始方法直接或间接调用的所有方法 |
| 边 | 方法调用(上方方法调用下方方法) |
| 布局 | 树布局 |
| 指标尺度 | 线性(节点宽度除外) |
| 节点 | 方法 |
| 节点颜色 | 灰色:总是返回 self;黄色:每个对象接收者返回相同值;白色:其他方法 |
| 节点高度 | 总执行时间 |
| 节点宽度 | 执行次数(对数尺度) |
| 示例 | 图 3 |
以 MOGraphElement>>
bounds
方法为例,在之前的结构分布蓝图中,我们发现该方法是优化的候选对象。在行为分布蓝图中,右键单击 MORoot>>
applyLayout
方法会打开该方法的行为分布蓝图。
从行为分布蓝图中可以看出,
bounds
、
applyLayout
、
shapeBoundsAt:ifPresent:
和
translateTo:
方法在 CPU 时间消耗方面成本较高。
bounds
方法的返回值随时间保持不变,因此在蓝图中用黄色表示。该方法参与了一个复杂的调用图,通过右键单击方法获得的上下文菜单可以提供对感兴趣实体的过滤视图。
通过对
bounds
方法的详细视图分析,我们可以得出以下结论:
-
bounds
属于多个执行路径,其中每个方法的返回值都是恒定的。
-
bounds
调用了
shapeBoundsAt:ifPresent:
方法,该方法的返回值也是恒定的。
-
bounds
和
shapeBoundsAt:ifPresent:
被调用的次数相同。
在下半部分,我们将继续介绍如何利用这些蓝图来优化 Mondrian 中的性能瓶颈。
可视化动态指标的性能分析蓝图
3. 优化 Mondrian
结构分布蓝图和行为分布蓝图的结合帮助我们识别了 Mondrian 中的多个性能瓶颈。接下来,我们将针对这些瓶颈进行优化。
3.1 优化
bounds
方法
从行为分布蓝图中可知,
MOGraphElement>>bounds
方法消耗了大量的 CPU 时间,且其返回值恒定。这为我们提供了一个优化的方向:通过缓存机制来减少该方法的重复计算。
以下是优化步骤:
1.
添加缓存变量
:在
MOGraphElement
类中添加一个实例变量用于存储
bounds
方法的计算结果。
!MOGraphElement methodsFor: 'private'!
cachedBounds
^ cachedBounds ifNil: [ cachedBounds := self computeBounds ].
computeBounds
"原 bounds 方法的计算逻辑"
^ ...
-
修改
bounds方法 :将bounds方法修改为返回缓存的结果。
!MOGraphElement methodsFor: 'accessing'!
bounds
^ self cachedBounds
通过这种方式,当
bounds
方法被多次调用时,只有第一次调用会进行实际的计算,后续调用将直接返回缓存的结果,从而显著减少了 CPU 时间的消耗。
3.2 分析其他瓶颈
在优化
bounds
方法后,我们使用性能分析蓝图继续分析 Mondrian,发现了另一个瓶颈。
MOGraphElement>>shapeBoundsAt:ifPresent:
方法也消耗了较多的 CPU 时间,且与
bounds
方法的调用次数相同。
我们可以采用类似的缓存策略对该方法进行优化:
1.
添加缓存变量
:在
MOGraphElement
类中添加一个实例变量用于存储
shapeBoundsAt:ifPresent:
方法的计算结果。
!MOGraphElement methodsFor: 'private'!
cachedShapeBounds
^ cachedShapeBounds ifNil: [ cachedShapeBounds := Dictionary new ].
cachedShapeBoundsAt: anIndex ifPresent: aBlock
^ cachedShapeBounds at: anIndex ifAbsentPut: [ self computeShapeBoundsAt: anIndex ].
computeShapeBoundsAt: anIndex
"原 shapeBoundsAt:ifPresent: 方法的计算逻辑"
^ ...
-
修改
shapeBoundsAt:ifPresent:方法 :将shapeBoundsAt:ifPresent:方法修改为返回缓存的结果。
!MOGraphElement methodsFor: 'accessing'!
shapeBoundsAt: anIndex ifPresent: aBlock
^ self cachedShapeBoundsAt: anIndex ifPresent: aBlock
3.3 优化效果评估
为了评估优化效果,我们可以再次运行性能分析蓝图,对比优化前后的 CPU 时间消耗情况。
优化前,
MOGraphElement>>bounds
方法消耗了 38% 的 CPU 时间,
MOGraphElement>>shapeBoundsAt:ifPresent:
方法消耗了 33% 的 CPU 时间。优化后,这两个方法的 CPU 时间消耗显著降低,整体性能得到了明显提升。
4. 总结
本文介绍的结构分布蓝图和行为分布蓝图为代码性能分析提供了有效的可视化方法。通过这两种蓝图,我们可以直观地了解程序的执行时间分布,快速定位性能瓶颈,并根据蓝图提供的信息进行针对性的优化。
在实际应用中,我们可以按照以下步骤使用性能分析蓝图进行代码优化:
1.
运行性能分析蓝图
:使用性能分析工具生成结构分布蓝图和行为分布蓝图。
2.
分析蓝图
:从蓝图中找出消耗大量 CPU 时间的方法和类。
3.
确定优化策略
:根据方法的特点(如返回值是否恒定、是否产生副作用等)确定优化策略,如缓存机制、代码重构等。
4.
实施优化
:按照确定的优化策略对代码进行修改。
5.
评估优化效果
:再次运行性能分析蓝图,对比优化前后的性能指标,评估优化效果。
通过以上步骤,我们可以不断优化代码性能,提高软件的运行效率。
graph LR
A[运行性能分析蓝图] --> B[分析蓝图]
B --> C[确定优化策略]
C --> D[实施优化]
D --> E[评估优化效果]
E -->|效果不佳| B
E -->|效果良好| F[结束优化]
总之,性能分析蓝图是一种强大的工具,可以帮助我们更好地理解程序的运行时行为,从而有效地优化代码性能。
超级会员免费看
966

被折叠的 条评论
为什么被折叠?



