Dynamic Shape Analysis via Degree Metrics

概述
应用程序的大小和复杂性不断增加,这使得调试和程序理解更具挑战性。用托管语言(如Java,C#和Ruby)编写的程序进一步加剧了这一挑战,因为它们倾向于在堆中编码大部分状态。本文介绍了dynamic shape analysis,该分析旨在通过动态总结对象指针关系并基于类检测动态度量度量来表征堆中的数据结构。该分析可识别递归数据结构,自动发现动态度量指标,并在违反度数指标时报告错误。动态形状分析的使用包括帮助程序员在开发期间发现数据结构错误,通过静态或动态分析生成验证断言,以及检测部署中的细微错误。我们在Java虚拟机(JVM)中实现动态形状分析。使用SPECjvm和DaCapo基准测试,我们可以看出堆中的大多数对象都是递归数据结构的一部分,可以保持强大的动态度量指标。我们表明,一旦动态形状分析通过正确的执行建立度数度量,它就可以在微量标记的后续执行中找到自动插入的错误。这表明它可以用于部署以提高软件可靠性。

关键词 动态形状分析,度量度量,动态不变量

1.简介
面向对象的语言编码对象中的程序状态。 随着堆中分配的对象数量不断增加,在堆分配的数据结构中出现许多语义,数据结构和并发错误并不令人惊讶。 因此,堆分析有可能帮助开发人员检测错误并正确指定其程序。

为了管理对象,程序员使用常规数据结构,例如数组和递归数据结构。递归数据结构(RDS)形成节点和引用(指针)的规则模式,使得移除任何两个节点之间的引用导致生成相同数据结构的两个实例。例如,具有n个节点的单链接列表是简单的递归数据结构,并且可以被分成两个较小的单链接列表:一个是大小x,另一个是大小为n-x。长期以来,研究人员使用静态形状分析来基于使用[9,10,16,28,29]创建和操作它们的代码来表征堆中的递归数据结构。通过分析程序语句,静态形状分析检测递归数据结构及其不变量。例如,它可以检测到单链表有两个不变量:n-1个节点只有一个输入指针和一个输出指针。尽管最近取得了进展[9],但静态形状分析并未广泛使用,因为它需要流分析和上下文敏感性,这使得它非常昂贵且必然保守。

在本文中,我们介绍了动态形状分析,它通过搭载定期垃圾收集来动态检测在特定程序执行期间保持的递归数据结构和度不变量。 我们的分析计算了一个类域摘要图(CFSG),它根据类定义总结了动态对象图。 CFSG将对象数量及其递归度量指标记录为入度和出度不变量。 当数据结构的特定数量的节点表现出特定的度数时,我们将其称为固定度量并跟踪表现出固定度量的对象的数量。 例如,在具有n个节点的单链表中,恰好n-1个节点具有等于1的出度,而最后一个节点具有等于0的出度。 对于任何不固定的度数不变量,CFSG将范围度量记录为具有给定属性及其方差的对象的分数。

完全准确的动态形状分析需要在每次指针突变之后分析堆,这是非常昂贵的。为了使成本更容易处理,我们在一个名为ShapeUp的工具中捎带垃圾收集,我们将其添加到Java虚拟机(JVM)中。由于垃圾收集是周期性的并且相对不频繁,因此可以使动态形状分析有效。例如,ShapeUp在总运行时间中平均增加4到8%,在系统中增加不到1%的空间开销。不经常执行动态形状分析可以提高效率的准确性。 ShapeUp失去了准确性,因为程序可能违反集合之间的度数指标。但是,我们的结果表明,相对不频繁地执行动态形状分析足以产生足够准确的信息以发现许多错误。
我们通过首先在SPECjvm和DaCapo Java基准测试中识别各种库和自定义递归数据结构来评估ShapeUp。我们证明堆中的绝大多数对象是递归数据结构的一部分,并且为这些数据结构在其整个执行过程中维护动态度指标。虽然各个数据结构维持度指标(例如,度数为1),但是堆由于其瞬态性质而不是整体。使用微基准测试,我们展示了动态形状分析的独特应用,其中ShapeUp使用正确的执行来开发不变量,然后在不正确的执行中发现错误。我们自动生成错误的执行并发现一些错误触发ShapeUp检测到的度量指标的异常和报告。这些结果表明,ShapeUp可能对难以发现的错误很有用,这些错误会使其部署。

总之,动态堆分析有效地按类概括堆中的对象,查找动态不变量,并通过从对象图中挖掘堆的大部分常规结构来查找违规。 本文通过展示度标准如何用于检测和报告微基准测试中的错误,开始通过度标准进行动态形状分析的研究。 它留下了未来的工作,研究更精确的摘要,可以处理单个数据结构的多个实例以及实际程序的应用。 我们将在下一节开始,更详细地讨论动态形状分析的潜在用途,并解释它与静态形状分析的不同之处。

2.动机
动态和静态形状分析的用例不同。
静态形状分析是一种流和上下文敏感的分析,它证明程序正确地构造和操作递归数据结构。 静态分析努力检测数据结构永远不会违反某些不变量,例如二叉树中的节点具有至多一个的入度。 不幸的是,程序中存在指针(即,当操纵数据结构时),临时违反不变量。 为了帮助进行静态形状分析,程序员指定了不变量以及它们期望不变量保持的点。 即使有这种帮助,静态分析也必然是保守的,即使它们确实存在,也不能总是证明某些属性。 此外,静态形状分析对于除了适度大小的程序之外的所有程序仍然是难以处理的。

相反,动态形状分析会对给定程序运行的堆中的数据结构进行采样。它分析数据结构的当前形状,并且给定正确的不变量,可以确定当前动态形状是否违反这些不变量。它不能保证数据结构永远不会违反其不变量,因为样本之间可以违反不变量。但是,动态形状分析可以帮助开发人员找到在开发早期,测试期间和部署后持续存在的错误。开发期间的数据结构报告可以帮助程序员发现明显的错误。例如,考虑开发人员打算创建双向链接列表但忘记设置后向指针并创建单链接列表的情况。 ShapeUp不变量报告将清楚地表明n-1个节点显示的是1度而不是2的预期值。
或许更有趣的是,在部署期间或之后检测不变违规的情况。本文证明了动态形状分析可以通过观察正确的执行来开发不变量,然后可以找到异常的,例如当经过良好测试的程序由于竞赛而出现运行时错误时。如果错误持续存在,则定期分析堆会检测到它们。此外,来自动态形状分析的信息增强了静态分析和验证。例如,用户可以提供动态检测的不变量作为静态或动态验证的输入。因此,动态形状分析有可能帮助程序员处于开发和部署的多个阶段。

三。相关工作

相关工作包括基于程序计数器位置的动态不变量、静态形状分析、使用不变量规范的错误检测和更正以及C堆分析。

扫描二维码关注公众号,回复: 11075679 查看本文章

长期以来,静态形状分析一直试图通过分析代码来识别递归数据结构来理解堆结构[9、10、16、28、29]。不幸的是,它并没有被广泛使用,因为它需要流和上下文敏感性,这使得它非常昂贵,而且必然是保守的。我们的分析有效地提供了相同的信息,但特定于一个或多个程序执行,因为它观察堆的当前状态,而不是分析所有可能的堆状态。动态形状分析和静态形状分析一样,可以用来生成规范和测试。

最近,动态分析通过挖掘动态程序行为、将其与程序位置关联,然后识别异常执行[15、17、18、22、21、23、25、31]发现了可能的不变量。例如,hangal和lam表明,崩溃之前通常会有异常行为,即程序违反了一个或多个动态不变量,这些不变量是在以前的执行或当前执行的早期建立的。它们表明,在报告未看到的值时记录变量和条件值有助于调试。我们表明,该假设也适用于堆,即堆对象图编码语义,异常堆关系揭示软件缺陷。

最近的工作还展示了如何使用程序员指定的不变量[1、4、11]或用户定义的谓词例程[8、14]检测和更正数据结构错误。这种方法要求程序员指定数据结构的性质,然后使用模型检查和部分评估来检测和修复在野外发生的错误。用户定义的谓词可以对有价值的信息进行编码,例如,哪个值对数据结构中应该存在的节点数进行编码,而这些节点的形状将无法发现。shapeup的优点是它是完全自动化的,不需要谓词例程。它通过自动发现用户谓词包含的许多相同属性来检测类似的错误。开发人员可以使用我们的分析结果为复杂的递归数据结构编写谓词例程。

HeapMD检查C程序[12]中的简单堆属性。具体来说,它表明许多C堆包含一个稳定的部分,对象的进出度为零、一或二。虽然HEAPMD提供了灵感,但我们的工作表明,Java堆的更短暂的性质和对象图中更复杂的关系很少提供稳定的整堆不变量。然而,通过按类和连接区分堆,可以发现递归数据结构确实具有许多稳定度不变量。

Jump和McKinley从图中引入了类点,它按用户类总结了整个堆以及它们之间的指针关系[20]。此堆表示帮助开发人员通过识别图中不断增长的部分来查找内存泄漏。我们的工作是正交的,免费的。本文通过添加字段边缘,创建类字段汇总图,从图中扩展类点。此外,我们使用生成的图形来描述specjvm和dacapo java中的项目的数据结构,在正确的程序执行中识别递归程度不变量,并快速准确地识别错误的数据结构。 

4.数据结构分析              

为了管理大量的数据,用现代语言编写的程序使用递归数据结构。开发人员在数据结构和分配和操作它们的代码中隐式和显式地维护不变量。递归数据结构(rds)是一组由引用(指针)以规则模式链接的对象,使得任何部分都由相同数据结构的较小或简单实例组成。例如,连续链接列表的子集也是单个链接列表。虽然数据结构的定义是无边界的,但堆中任何特定RDS的大小都是有界的。 

我们根据specjvm和dacapo基准的递归数据结构检查堆的组成,并将它们显示在图1中。我们根据递归数据结构的实现位置来分离它们。自定义数据结构是由应用程序专门实现的结构。其他的数据结构是在库中实现的,并且被应用程序简单地使用。图1显示递归数据结构在分析的基准中普遍使用。虽然compressand 和 mpegaudio严格依赖数组来处理数据,但在其他基准中,91%的对象都是一个rds的一部分,其中33%包含在自定义数据结构中。 

在Java和其他面向对象语言中,递归数据结构与数据结构包含的数据分开实现。因此,我们将rds的定义细化为includeObject类,并区分实现数据结构的递归主干的类的对象。递归主干定义了rds的形状,由引用同一类的其他对象的单个类的对象组成。例如,树是由较小的树(子树)组成的,其中smallesttree是单个节点。树的给定类定义包含一个类节点,其中包含一些对其他节点的引用,以及对一个或多个数据对象的引用。表1详细介绍了我们为评估形状而实现的微基准,包括链接列表、双链接列表、二进制树、带父指针的二进制树以及简化的哈希图。对于每个数据结构,第一列说明示例实例化。第二列显示节点类的定义。第三列显示了类字段摘要图(cfsg,在下一节中进行了解释),它总结了递归主干并反映了rds的形状。这就是静态和动态形状分析所寻求的特征。接下来,我们将描述如何利用垃圾收集器的shapeup来创建这个摘要。 

4.1汇总数据结构进行RDS分析              

动态形状分析在每次垃圾收集时检查堆。程序堆的状态可以表示为有向图G={V,E},其中V是所有堆分配对象的集,E是堆中对象之间的引用集。也就是说,如果对象O的字段F引用对象P,则边缘(o.f,p)存在于图G。对象的入度o是堆中引用o的其他对象的数目。对象o的出度是o实际引用的对象的数目(不是对象类指定的潜在数目)。堆图的根是存储在静态(全局变量)、堆栈和寄存器中的引用。跟踪垃圾收集器从这些根开始,并通过在堆中的所有活动对象引用中执行传递关闭来检测活动对象。ShapeUp在这个扫描上进行回溯,并总结堆INA类字段摘要图(CFSG)的结构,它描述了每个类的对象的动态形状。 

cfsg按类节点和字段边缘汇总整个堆。类摘要节点记录堆中此类的对象总数。对于在堆的垃圾回收跟踪过程中发现的每个可到达(live)对象OC1,ShapeUp确定对象的类C1并递增相应的类节点的计数器。字段边缘将每个引用(o.f,p)表示为O和P之间的有向边缘,由字段F区分。为了捕获递归数据结构的形状,shapeup使用度指标。度指标定义为对象实例的进出口。由于动态形状分析图描述递归主干定义的RDS形状,因此shapeup只跟踪与定义RDS递归主干的对象和边缘对应的度指标。shapeup在分布柱状图的cfsg类节点中跟踪这些边。 

根据定义,RDS的主干边缘定义为(oC1.f,pC2),这样c1=c2。在这种情况下,shapeup递增,并将oC1的出度和pc2的入度累积到一个柱状图中,该柱状图跟踪cfsg类节点中每个入和出的对象数。对象扫描允许shapeup以低成本计算每个单独对象的进出程度。当收集器扫描对象时,已知出度,而ShapeUp则直接递增对应于非空出引用数的出度直方图。由于在收集器完成堆上的可传递关闭之前,对象实例的度数并不完全已知,因此不确定度柱状图是递增计算的。我们在图2和下面的部分中用伪代码详细描述了这个过程。 

图1.堆中的递归数据结构 
图2.用于在对象扫描期间累积入/出度度量的伪代码。 

 4.2分步示例       

图3显示了为双重链表的递归主干构建cfsg的逐步示例。在图中,同一类的实例具有相同的形状(即,doubleLinkedList对象是一个正方形,节点是圆形)。图的左侧显示垃圾收集器对象扫描期间的堆图。在三种颜色抽象之后[13],黑色对象已经被收集器扫描,灰色对象排队等待扫描,白色对象还没有被发现。右侧显示相应的CFSG。我们一步一步走过这个数字:

表1。微基准中的数据结构显示示例堆图、实现和相应的cfsg

图3.逐步构建CFSG。二进制柱状图用in=k(#)表示,其中#是二进制中的对象数。

第一步:在扫描了doubleLinkedList对象(黑色)之后开始。在每个对象的上方,图中显示了该对象实例的程度。在这种情况下,两个对象都用†标记,表示两个对象都没有被标识为递归主干对象。在cfsg中,doubleLinkedList类的节点显示一个实例,其中一个实例的边指向节点类。请注意,cfsg中的节点对象数为零,因为节点对象只排队,但没有扫描(灰色)。没有递归的骨干对象已经被识别。 

步骤2:收集器扫描定义递归主干的第一个节点对象。cfsg用表示节点中下一个字段的自边缘捕获和标识递归主干对象,并增加此根对象的度数柱状图(in=0(1))。我们在cfsg节点中使用单独的柱状图跟踪进出度。由于出度是已知的,直方图中的相应条条递增(OUT=1(1))。必须递增计算的in degree需要对象头中的一个字节来计算每个实例的degree。当收集器扫描每个对象时,将检查子对象,并增加对象头中的度数。对于已排队扫描的每个灰色子级,shapeup将以度数递减与子级上一级对应的直方图条,并根据子级的新不确定度递增相应的条。对于每个白色子节点,shapeup只增加对应于in=1(out=1(1))的条。 

步骤3:第二个节点对象的扫描在cfsg中添加第二个selfedge,表示节点中的前一个指针,out=2添加到cfsg节点。对每个子级(in=0(0),in=1(3))递增计算in度。              

步骤4-5:扫描继续处理双链接列表的每个节点。 

步骤6:在数据结构的尾部,我们处理数据结构的最后一个节点。此时,shapeup已经捕获了cfsg中整个数据结构的形状。 

图3的底部显示了正确的双链接列表的最一般的cfsg形式。综上所述,n−2对象的入度和出度等于2,而2对象(头部和尾部)的入度和出度等于1。我们称之为数据结构的这些固定指标。表1的第三列显示了几种不同数据结构的通用CFSG。在收集结束时,cfsg完整地汇总了每个类的对象数量以及收集时每个入出程度的对象数量。

最后,我们在垃圾收集结束时添加一个阶段,在此期间,我们将当前的cfsg添加到程序的累积cfsg中。我们用每个度数指标(例如in=0、in=1、in=2等)汇总对象的平均百分比。对于单个数据结构,如果0、1、2、n、n−1或n−2对象显示度指标,则记录固定度量。否则,我们将范围度量记录为在一个或多个执行期间通过形状观察到的对象的百分比。我们发现,当引入异常时,固定度量对违规非常敏感,而范围度量则更为宽容。在下一节中,我们将评估cfsg并说明如何使用它来检测递归数据结构中的错误。

5.实验方法论 

我们在mmtk中实现了shapeup,这是一个内存管理工具kitin jikes rvm版本2.9.1[2,3]。MMTK实现了许多高性能的收集器[5,6]。我们使用全堆标记扫描和世代标记扫描收集器来测量Pecjvm[30]和Dacapo V.06-10-MR2[7]基准套件的性能。我们使用尽可能预编译的配置,包括密钥库和优化编译器(Fast BuildTimeConfiguration打开),然后关闭断言检查。此外,我们通过应用重播编译来消除自适应编译系统的不确定性行为[19]。

开销:我们将shapeup配置为在堆满时触发的自然垃圾收集点执行动态形状分析和垃圾收集。我们按照最佳实践[7]对一系列与给定基准的活动内存大小成比例的堆大小进行了试验。我们选择堆大小,范围从程序可以执行的最小值到最小值的6倍。生产系统的常见做法是选择大约是最小值两倍的堆大小。当使用完整堆收集器(PMD的最大值为39%)时,CFSG的当前实现使所有基准的总时间平均增加了4.6%,而当使用世代收集器(HSQLDB的最大值为92%)时,平均增加了8.4%,同时使程序的空间需求增加了<1%。为了简洁起见,我们省略了这些结果。我们相信,通过性能调整,这种开销可以进一步减少。如果用户可以承受更多的开销,并且希望更多的示例用于调试,例如在测试或开发期间,shapeup可以更频繁地分析堆。用户可以通过在源代码中插入对system.gc()的调用,在调试期间在特定的程序兴趣点轻松获取示例。 

7。结论

程序员对他们创建的程序的规模和复杂性的挑战越来越大。随着程序在堆中分配越来越多的对象,堆分析对于程序理解和调试变得至关重要。本文提出了一种动态形状分析工具,该工具通过将类域汇总图(cfsg)中的堆图进行非常低开销的汇总,来描述递归数据结构的形状。cfsg按字段完整地汇总了每个类的对象数量以及它们之间的引用。递归主干对象的度标量以动态不变量的形式捕获递归数据结构的形状。我们通过描述specjvm和dacapo基准中的递归数据结构来评估形状。我们证明堆中绝大多数对象都是递归数据结构的一部分。虽然总结Java中的整个堆的度标准不足以理解大多数程序行为,但我们表明对于单个递归数据结构的度度量保持其整个执行的不变量。我们将演示如何使用动态发现的度度量来查找错误执行的微基准,这表明对于某些数据结构,单个错误足以触发由shapeup报告的冲突。  

未来的工作应该:(1)探索更精确的程度度量总结,包括使用轻量级技术分离RDS实例的多个递归数据结构实例的情况[27];(2)除了递归类型外,还要考虑数据对象;(3)评估实际应用程序中的形状。本文表明,动态堆分析可以有效地对堆进行分类汇总,从而从堆的正则结构中挖掘程度度量,找到动态不变量,发现冲突。

发布了43 篇原创文章 · 获赞 23 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/zhang14916/article/details/89819339