Bounds Check Elimination In Go

https://www.ardanlabs.com/blog/2018/04/bounds-check-elimination-in-go.html
https://github.com/dgryski/go-metro



Go标准编译器优化:边界检查消除(bounds check elimination)
从Go SDK 1.7开始,Go标准编译器开始支持边界检查消除。此优化避免了很多不必要的边界检查,从而使得编译器编译出的程序执行效率更高。



什么是边界检查?边界检查是指在运行时刻,Go运行时要检查切片和字符串的索引操作中的索引下标值是否越界了。如果越界了,就要产生一个恐慌,以维护内存安全。



虽然边界检查是维护内存安全的重要保障,但是某些索引操作如果被执行到的话,其中的索引下标值肯定不会越界。对这样的下标进行边界检查是无谓的,并会对程序执行性能产生负面影响。

下面将展示一些例子来理解Go标准编译器在什么条件下避免了边界检查。本文中的运行结果均基于Go标准编译器1.12版本。
// example1.go
package main



func f1(s []int) {
_ = s[0] // 需要边界检查
_ = s[1] // 需要边界检查
_ = s[2] // 需要边界检查
}



func f2(s []int) {
_ = s[2] // 需要边界检查
_ = s[1] // 边界检查消除了!
_ = s[0] // 边界检查消除了!
}



func f3(s []int, index int) {
_ = s[index:] // 需要边界检查
_ = s[:index] // 边界检查消除了!
}



func main() {}
如下编译此程序,我们将获知哪些行仍然需要边界检查。



$ go build -gcflags=”-d=ssa/check_bce/debug=1” example1.go


command-line-arguments


./aa.go:5:7: Found IsInBounds
./aa.go:6:7: Found IsInBounds
./aa.go:7:7: Found IsInBounds
./aa.go:11:7: Found IsInBounds
./aa.go:17:7: Found IsSliceInBounds
从这个结果来看,函数f2这样倒着取元素值的方式比函数f1的效率要高,因为它避免了两个边界检查。如果函数f2中的第一行不会越绝的话,则其中的第二行和第三行肯定也不会越界,所以第二行和第三行就不再需要边界检查了。



另外,函数f3中的第一行如果不会越界的话,则其中的第二行肯定也不会越界。



/ example2.go
package main



func f5(s []int) {
for i := range s {
_ = s[i]
_ = s[i:len(s)]
_ = s[:i+1]
}
}



func f6(s []int) {
for i := 0; i < len(s); i++ {
_ = s[i]
_ = s[i:len(s)]
_ = s[:i+1]
}
}



func f7(s []int) {
for i := len(s) - 1; i >= 0; i– {
_ = s[i]
_ = s[i:len(s)]
}
}



func f8(s []int, index int) {
if index >= 0 && index < len(s) {
_ = s[index]
_ = s[index:len(s)]
}
}



func f9(s []int) {
if len(s) > 2 {
_, _, _ = s[0], s[1], s[2]
}
}



func main() {}
从下面的编译结果来看,标准编译器消除了此例子2程序中的所有边界检查。酷!



$ go build -gcflags=”-d=ssa/check_bce/debug=1” example2.go
例子3:



当前的标准编译器并非足够智能到可以消除到一切应该消除的边界检查。有时候,我们需要给标准编译器一些暗示来帮助标准编译器将这些不必要的边界检查消除掉。比如下例中的函数fd2和函数fe2比函数fd和函数fe的效率要高。



// example3.go
package main



func fd(is []int, bs []byte) {
if len(is) >= 256 {
for _, n := range bs {
_ = is[n] // 需要边界检查
}
}
}



func fd2(is []int, bs []byte) {
if len(is) >= 256 {
is = is[:256] // 给编译器一个暗示
for _, n := range bs {
_ = is[n] // 边界检查消除了!
}
}
}



func fe(isa []int, isb []int) {
if len(isa) > 0xFFF {
for _, n := range isb {
_ = isa[n & 0xFFF] // 需要边界检查
}
}
}



func fe2(isa []int, isb []int) {
if len(isa) > 0xFFF {
isa = isa[:0xFFF+1] // 给编译器一个暗示
for _, n := range isb {
_ = isa[n & 0xFFF] // 边界检查消除了!
}
}
}



func main() {}
编译输出:



$ go build -gcflags=”-d=ssa/check_bce/debug=1” example3.go


command-line-arguments


./aa.go:7:10: Found IsInBounds
./aa.go:24:11: Found IsInBounds
注意:标准编译器的每个版本都在不断地改进,所以上例中给标准编译器的暗示在以后的版本中可能将变得不再必要。



https://www.jianshu.com/p/346c18d66cc2



BoundsChecke:内存泄露;资源泄露
NuMega是一个动态测试工具,主要应用于白盒测试。该工具的特点是学习简单、使用方便、功能有效。NuMega共有三个独立的子功能——BoundsChecker、TrueCoverage、TrueTime。BoundsChecker为代码检错工具,TrueCoverage为测试覆盖率统计工具,TrueTime为程序运行性能测试工具。



本文挡通过三章对NuMega三个子功能的使用方法进行了介绍,各部分之间内容独立。如果你想了解NuMega的各项功能,建议阅读本文挡全部内容,如果你只想了解NuMega提供的某一个子功能的相关信息,按目录查看相应的章节即可。



需要说明的一点是,本文挡中所介绍的测试工具NuMega,专指NuMega for Visual C++版,对于NuMega for Visual Basic版和NuMega for Delphi版的使用说明,不在本文挡的介绍范围之内,这一点请注意。



2安装
NuMega的安装很简单。获得NuMega安装程序后,点击setup.exe进行安装即可。在安装过程中不需要什么特殊的设置。



不过有一点需要说明,在安装NuMega之前,应该确保你的机器上已经安装好了Visual C++,因为只有这样才能使NuMega成功的集成到Visual C++开发环境中去。



好了,下面我们分三个部分,分别介绍BoundsChecker、TrueCoverage、TrueTime的使用方法。



3 BoundsChecker
BoundsChecker 是一个Run-Time错误检测工具,它主要定位程序在运行时期发生的各种错误。BoundsChecker能检测的错误包括:



1))1、指针操作和内存、资源泄露错误,比如:



内存泄露;



资源泄露;



对指针变量的错误操作。



22、内存操作方面的错误,比如:



内存读、写溢出;



使用未初始化的内存。



33、API函数使用错误



BoundsChecker安装成功后,在你的VC++集成开发环境中,会多出了一个名为BoundsChecker的菜单



BoundsChecker 已经非常完好的集成到VC++集成开发环境中了。



使用BoundsChecker对程序的运行时错误进行检测,有两种使用模式可供选择。一种模式叫做ActiveCheck,一种模式叫做FinalCheck。下面分别进行介绍。



3.1 ActiveCheck
ActiveCheck是BoundsChecker提供的一种方便、快捷的错误检测模式,它能检测的错误种类有限,只包括:内存泄露错误、资源泄露错误、API函数使用错误。



要想使用ActiveCheck模式来检测程序的运行时错误,只需在VC++集成开发环境中打开BoundsChecker功能,然后从调试状态运行程序即可。此时ActiveCheck会在后台自动运行,随时检测程序是否发生了错误。下面说一下具体的使用步骤。



3.1.1 用ActiveCheck来检测错误
使用ActiveCheck的具体的操作步骤如下:



首先,在VC++集成开发环境中打开你要对其进行测试的程序,同时保证项目处于Debug编译状态下。



其次,确保VC++集成开发环境中[BoundsChecker/Integrated Debugging]菜单项和[BoundsChecker/Report Errors and Events]菜单项处于被选中的状态。只有这两项被选中,BoundsChecker才会在程序运行过程中发挥作用。



最后,在VC++集成开发环境中选择[Build/ Start Debug/Go]菜单命令,在Debug状态下运行程序,ActiveCheck也在后台开始运行了。



这时,就可以按照制定好的测试用例,对程序进行操作。凡是程序执行过的代码,如果存在错误,ActiveCheck就会记录下来。



有一个地方要说一下,在[BoundsChecker]菜单中有一项[Report Errors Immediately],如下图所示:



2.jpg
图3-2 关于[BoundsChecker / Report Errors Immediately] 菜单项



该菜单项对于ActiveCheck 模式,以及下面就要介绍的FinalCheck模式的作用是一样的,即:如果不选中该项,则BoundsChecker会记录程序运行过程中发现的各种错误,直到程序结束后再进行报告;当选中该菜单项时,在程序的运行过程中,一旦BoundsChecker发现错误,会马上弹出如下的对话框进行提示:



3.jpg



图3-3 错误报告对话框



下面按图中标注的数字序号解释一下对话框中各个按钮的功能:



按钮1:点击该按钮,则表示先暂时不理会这个错误,继续执行程序。



按钮2:点击该按钮,则会马上跳转到出现问题的代码行处。处理完问题后,点击[Build/ Start Debug/Go]菜单项,可以继续执行程序,进行检测。



按钮3:点击该按钮,则将该错误添加到被忽略的错误列表中去,当再次出现这个问题时,BoundsChecker将不会进行报告。



按钮4:点击该按钮,则立即终止程序的执行。



按钮5:点击该按钮,会显示当前内存的申请、使用情况。



按钮6:点击该按钮,会得到当前这个错误的帮助信息。



按钮7、8: 这两个按钮与[BoundsChecker/Report Errors Immediately]和[BoundsChecker/ Report Errors and Event] 菜单命令的功能是完全一样的,在此不再赘述。



按钮9:点击该按钮,会显示/隐藏与该错误有关的函数调用堆栈情况,以及具体的出错代码行的位置。



是否选中[BoundsChecker/Report Errors Immediately]菜单项,完全取决于你自己的喜好,以及测试时的具体情况。如果你想要BoundsChecker在程序运行过程中实时向你汇报发现的错误,那么你就选中这个菜单项;如果想等到操作结束后,再对操作过程中BoundsChecker发现的错误统一进行分析,就不必选中这个菜单项。我在平常使用过程中更偏向于使用后一种。



3.1.2 分析错误
在你操作全部结束,退出程序后,



BoundsChecker 会显示一个所发现错误的列表。我们需要对列表中罗列的错误进行分析,来确定错误的原因和位置。



在错误检测结果列表中,罗列出了在程序的执行过程中ActiveCheck检测到的所有的内存泄露、资源泄露和API函数使用错误的相关信息。如下图所示:



4.jpg
图3-4 错误检测结果



在左边的窗口中,逐条列出了程序在内存、资源、API 函数使用上的问题,包括:该问题的种类,该问题发生的次数,如果是内存泄露,损失了多少内存,以及发生该问题的代码位置等等。当你用鼠标单击选中某一条记录时,在右边的窗口中会显示出与该条错误记录相对应的函数调用堆栈情况。当你用鼠标双击某一条错误记录时,会定位到引发该错误的源代码处。



好了,BoundsChecker在ActiveCheck模式下的使用方法至此介绍完了,是不是很简单?



在ActiveCheck模式下检测程序时,程序的运行速度基本不受影响,但其缺点是检测的错误种类有限,即只能检查出内存泄露错误、资源泄露错误、API函数使用错误。BoundsChecker 提供了另外一种检测错误的模式—— FinalCheck,也就是我们在前面提到的BoundsChecker的第二种使用模式。 FinalCheck可以检测出程序中更多的错误。下面我们就对它进行介绍。



3.2 用 FinalCheck检测更多的错误
FinalCheck具有BoundsChecker提供的所有检错功能。FinalCheck 是ActiveCheck的超集,它除了能够检测出ActiveCheck能够检测出的错误,还能发现很多 ActiveCheck 不能检测到的错误,包括:指针操作错误、内存操作溢出、使用未初始化的内存等等,并且,对于ActiveCheck能检测出的错误,FinalCheck能够给出关于错误更详细的信息。所以,我们可以把FinalCheck认为是ActiveCheck的功能增强版。我们付出的代价是:程序的运行速度会变慢,有时甚至会变的很慢。



要想在FinalCheck 模式下测试程序,不能使用VC++集成开发环境提供的编译连接器来构造程序,而必须要使用BoundsChecker提供的编译连接器来编译连接程序。当 BoundsChecker的编译连接器编译连接程序时,会向程序中插装一些错误检测代码,这也就是FinalCheck能够比ActiveCheck找到更多错误的原因。



下面就



介绍一下如何在FinalCheck模式下对程序进行测试:



1在VC++集成开发环境中打开你所要测试的项目。



2由于要使用BoundsChecker的编译连接器重新编译连接程序,所以我们为BoundsChecker独自构造一个文件夹。在VC++集成开发环境中,具体操作方法是:



A)点击[ Build/Configurations…]菜单命令。



B)在弹出的对话框中点击 Add 按钮。在Configuration 编辑框中添入你为BoundsChecker创建的文件夹的名称,这个名称是任意的,比如我们取名为BoundChecker。



C)在 Copy settings from组合框中选中 XXX—Win32 Debug项,然后点击OK按钮,接着点击Close按钮。



现在,我们已经为FinalCheck构造好了一个文件夹。



3 点击[Build/Set Active Configuration…] 菜单命令,选中你刚才为BoundsChecker建的文件夹, 然后点击OK按钮。这样BoundsChecker编译连接程序时生成的中间文件、可执行程序,都会被放到该文件夹下。



4选择[BoundsChecker/Rebuild All with BoundsChecker] 菜单命令,对程序重新进行编译连接,也就是在这时,BoundsChecker向被测程序的代码中加入了错误检测码。编译连接完成后,BoundsChecker会在你为BoundsChecker构造的文件夹中生成可执行文件。



在FinalCheck模式下对程序进行检测的准备工作都已经做好,这时可以启动程序开始测试了,



操作步骤与在ActiveChecker模式下没什么区别。具体步骤如下:



确保VC++集成开发环境中[BoundsChecker/ Integrated Debugging]菜单项和[BoundsChecker/Report Errors and Events]菜单项处于选中状态。
点击[ Build/Start Debug]菜单,选中“Go” 菜单项。程序开始在Debug状态下运行。
按照你制定好的测试用例,对程序进行操作。
当BoundsChecker检测到了错误时,会弹出窗口向你汇报,你可以当时就进行处理,也可以等到你的操作全部完成,退出程序之后再对列出的这些错误进行分析。这完全取决于你是否选中了[BoundsChecker/Report Errors Immediately] 菜单项。
退出程序后,BoundsChecker会给出错误检测结果列表。该错误列表与ActiveChecker给出的错误列表的查看方法完全一样。只不过这个列表中所报告的信息会更多、更详细一些。
好了,BoundsChecker在FinalCheck模式下的使用也介绍完了。ActiveChecker、FinalCheck这两种模式,比较而言各有长短。ActiveChecker使用方便,只需在Debug状态下直接运行程序即可,并且程序的运行速度较快,但检测的错误种类有限;FinalCheck模式下,需要使用BoundsChecker的编译连接器重新编译连接生成可执行程序,并且程序的运行速度比较慢,但检测的错误种类、提供的错误相关信息要多于ActiveChecker。所以,何时使用何种模式,应根据当时的具体情况而定。



3.3 检测Win32 API函数的兼容性
BoundsChecker还提供了一个功能——检测程序中使用的Win32 API函数在不同平台上的兼容性。该功能与前面提到的ActiveChecker、FinalCheck模式没有什么关系,它是独立的一个功能。



虽然大多数Win32 API函数都适用于Win95、Win98、Win2000、WinNT等不同的Windows操作系统平台,但并不是所有的API函数都满足这种情况。你可能不知不觉的使用了在某一个平台下允许,在另一个平台下却不允许使用的API函数,而项目的要求是:程序能够在这两种平台下运行。 BoundsChecker提供的这个检测Win32 API函数兼容性的功能,恰好能够处理这个问题。



该功能的使用方法如下:



启动[BoundsChecker/View/Compliance Report]菜单命令,如下图所示:



5.jpg
图3-5 启动Win32 API函数兼容性检测功能



会弹出下面的窗口:



6.jpg
图3-6Win32 API函数兼容性检测功能



在对话框中选择程序承诺能够运行的平台,以及被要求遵从的其他标准(标准C和扩展的标准C),点击“OK”按钮,BoundChecker会给出兼容性检测报告。



3.4 忽略错误
在某些情况下,我们需要忽略BoundsChecker报告的一些错误,这些情况包括:



1 误报。BoundsChecker 指定程序中的某段代码存在错误,但经过我们的仔细检查,证实程序确实没有这个错误,这是BoundsChecker的误报。工具毕竟是工具,它只能依照为它制定的算法行事,所以会有误报的情形发生。但千万不要轻易认定某一个错误为误报,一定要对错误进行仔细的分析,确定是真正的误报。



2第三方的代码。BoundsChecker指定的错误发生位置在第三方提供的代码中,包括第三方提供的程序库、DLL、OCX等。对于这种情况,我们也要先进行认真的检查,确定不是由于我们错误的使用第三方的代码引起的。如果最后确定不是我们的原因,则这样的错误报告可以忽略。



3.5 其他
还有一点需要强调,使用BoundsChecker对程序进行测试时,需要有程序的源代码。如果没有源码,BoundsChecker虽然也可以打开EXE文件将其执行起来,但得出的测试结果经常是不正确的,因此也就没有太大的意义。



另外,除了可以在VC++集成开发环境中使用BoundChecker外,从 [开始菜单] 中启动BoundChecker,然后打开经BoundChecker编译连接生成的可执行文件,也可以对程序进行测试,操作方法与集成到VC++集成开发环境中的BoundChecker的操作方法是一样的,在此就不赘述了。



至此,BoundChecker所提供的功能全部介绍完了。



4 TrueCoverage
覆盖率对于测试来说是一项重要的数据。在我们执行了针对一个功能模块的所有测试用例后,非常想了解测试对于模块代码的覆盖情况,也就是测试覆盖率到达了多少,以此来判断测试工作是否可以结束,如果还未达到测试目标,如何进一步补充测试用例。



对于这些问题,如果没有覆盖率统计工具的帮助,而想通过手工来进行,几乎是不可能的。



TrueCoverage的功能就是统计测试覆盖率,它恰好能为我们就上面这个问题提供帮助。TrueCoverage给出的覆盖率是“语句覆盖”,是最低覆盖强度的覆盖率,所以我们测试的项目,用TrueCoverage统计后,应尽量达到100%的覆盖。



TrueCoverage的应用阶段为单元测试阶段和集成测试阶段。



和BoundChecker一样,TrueCoverage安装成功后,在你的VC++集成开发环境中,会多出一个名为TrueCoverage的菜单,如下图所示:



7.jpg
图4-1 TrueCoverage在VC++集成开发环境中添加的菜单



这说明,TrueCoverage已经完好的集成到VC++集成开发环境中了。



下面开始介绍TrueCoverage的具体使用步骤。



4.1 使用TrueCoverage
TrueCoverage使用步骤:



1 在VC++集成开发环境中,打开你所要测试的项目。



2 为TrueCoverage构造一个文件夹,方法为:



首先,点击[ Build/Configurations…]菜单命令。



其次,在弹出的对话框中点击 Add 按钮。



然后,在Configuration 编辑框中添入你为TrueCoverage创建的文件夹的名称,这个名称是任意的,比如我们叫做TrueCoverage。



最后,在 Copy settings from组合框中选中 XXX—Win32 Debug项,然后点击OK按钮,接着点击Close按钮。



现在,我们已经为TrueCoverage构造好了一个文件夹。



3 点击[Build/Set Active Configuration…] 菜单命令,选中你刚才为TrueCoverage建的文件夹,然后点击OK按钮。



4 选择[TrueCoverage/Rebuild All with TrueCoverage] 菜单命令,用TrueCoverage的编译连接器对程序重新进行编译连接。TrueCoverage对程序进行编译连接时生成的中间文件、可执行程序,会放到你刚才为TrueCoverage创建的文件夹下。在这个编译连接过程中,TrueCoverage向可执行程序中插入了一些信息,这也就是 TrueCoverage能够跟踪、统计测试覆盖率的原因。



5点击[TrueCoverage/Run with TrueCoverage] 菜单命令,TrueCoverage被启动,接着你的程序也被执行起来。现在就可以按照事先已经制定好的测试用例,在程序中逐个进行执行了, TrueCoverage会在后台记录程序的执行覆盖情况。



在TrueCoverage界面中有一个工具条,我们有必要在这里对它做一下说明。如下所示:



8.jpg
图4-2 运行控制工具条



这个工具条在程序运行起来后,会由不可用状态变为可用状态。工具条上各按钮功能为:



按钮1:如果在程序的运行过程中你按下了该按钮,则覆盖率的统计只计算到此,你还可以继续操作程序,但此时对程序的任何操作都不再计入到覆盖率统计中去了。



按钮2:如果在程序的运行过程中你按下了该按钮,则TrueCoverage会马上显示截止到目前为止的测试覆盖率情况。之后你可以继续操作程序,TrueCoverage会继续在后台记录程序的执行覆盖情况。



按钮3:如果在程序的运行过程中你按下了该按钮,则TrueCoverage会清除在这之前的覆盖数据,使各项覆盖率的数据均为零。你可以继续操作,TrueCoverage在后台从零开始重新记录程序的执行覆盖情况。



6 当你退出程序时,TrueCoverage会显示本次操作结束后,程序的执行覆盖情况,包括:整个可执行程序的覆盖情况、每个代码文件的覆盖情况、每个代码文件中每个函数的覆盖情况,对于这些覆盖率统计结果,可以文件的形式进行保存。要想再次执行程序,点击TrueCoverage 中的[Program/ Start]菜单命令即可。



我们不太可能一次执行完所有的测试用例。TrueCoverage 为我们想到了这一点。在我们每次使用TrueCoverage运行起程序,执行了一些用例,关闭程序,并保存这一次的覆盖率统计结果后, TrueCoverage会询问你是否将本次的测试结果合并到总的覆盖率统计结果中去,一般情况下,我们合并进去就可以了。



下面介绍一下TrueCoverage的界面,按图中的编号分别给出说明。



9.jpg
图4-3 TrueCoverage的运行界面



1:表示程序在某一次执行结束后的测试覆盖情况。双击某一个条目,会在3、4部分的窗口中显示关于本次覆盖率的详细信息。本图中的情况,表示测试人员执行了两次程序。



2:表示程序当前总的测试覆盖情况,是多个1合并后的结果。双击该条目,会在3、4部分的窗口中显示关于总覆盖率的详细信息。



3:该窗口中显示的数据与你当前选中的某一次或总的测试覆盖统计结果相对应(即前面说到的1、2)。该窗口中显示的数据包括:程序的覆盖率、每个代码文件的覆盖率。更详细的信息显示在窗口4中。



4:该窗口中显示的数据与你在窗口3中的选择相对应,显示了各个函数的测试覆盖情况。用鼠标双击某一个函数,会显示该函数的源代码,TrueCoverage用不同的颜色标识执行过的和未被执行过的代码,如下图所示:



10.jpg
图4-4 TrueCoverage用不同颜色标识执行过的和未被执行过的代码



TrueCoverage 默认情况下,用绿色代表已执行的代码,用红色代表未被执行的代码,用黑色代表不可执行的代码(你可以通过选择TrueCoverage的 [program/setting]菜单命令,在启动的对话框中的setting标签页中进行设置,来改变这种默认情况)。通过这些信息,我们可以有针对性的增加、修改用例,来提高测试覆盖率。



4.2 对内联函数的处理
内联函数虽然具有函数的形式,但其机制与函数完全不同,所以,在用TrueCoverage统计测试覆盖率时,对内联函数需要采用不同的处理方式。



默认情况下,TrueCoverage是不会统计内联函数的执行覆盖率的。如果你需要得到内联函数的覆盖率数据,则需要进行一些额外的设置,具体方法是,在VC++的集成环境中选择[TrueCoverage/TrueCoverage Setting…]菜单命令,弹出如下对话框:



11.jpg
图4-5 内联函数设置



选中“Instrument inline function”复选框,点击OK。



然后选择[TrueCoverage/Rebuild All with TrueCoverage] 菜单命令,重新进行编译连接,对于这个可执行程序,TrueCoverage在其执行过程中就能够记录内联函数的覆盖率数据了。



4.3 TrueCoverage与BoundsChecker的结合使用
TrueCoverage与BoundsChecker可以结合起来使用。在[BoundsChecker]和[TrueCoverage]菜单下,都有一个[Rebuild with BoundsChecker and TrueCoverage]项,通过这一菜单命令编译连接生成的可执行文件,在程序运行结束后,能同时得到TrueCoverage和BoundsChecker(FinalCheck)的检测结果。



但这里有一个限定,那就是:必须由BoundChecker打开该exe程序执行,或是在VC++集成开发环境中通过Debug来运行该exe。这样在执行完程序后,能同时得到TrueCoverage和BoundsChecker(FinalCheck)的检测结果,如果你通过TrueCoverage来执行程序,则只会得到TrueCoverage检测结果。这一点请注意。



4.4 其他
与BoundsChecker一样,要使用TrueCoverage,一定要有被测程序的源代码。如果没有源码,TrueCoverage无法统计覆盖率。



除了可以在VC++的集成环境中使用TrueCoverage外,从 [开始菜单] 中启动TrueCoverage,然后打开经TrueCoverage编译连接生成的可执行文件,也可以进行覆盖率的统计,操作方法与集成到VC++集成开发环境中的TrueCoverage的操作方法一样,在此不再赘述。



TrueCoverages的使用介绍到此结束了,该工具对于我们进行测试覆盖率统计、补充测试用例的工作很有用处。



5 TrueTime
如何提高代码的运行效率,是开发过程中的一个重要问题。一个应用程序运行速度慢,但不容易找到究竟是在哪里出了问题,所以查找出性能瓶颈的位置是调整代码性能的关键。TrueTime 就是一个对应用程序的运行性能进行分析,查找程序性能瓶颈的工具。



TrueTime 能够收集、显示应用程序运行性能的相关数据,这些数据包括每个模块(EXE、DLL、OCX等)的运行性能,每一个函数的运行性能,对于有源代码的部分,TrueTime还可以给出每一行代码的运行性能。通过这些数据,我们可以确定系统的性能瓶颈,进一步优化程序的性能。



值得一提的是,TrueTime 在收集应用程序运行性能数据时,使用了一种叫做“Quantum”的技术。“Quantum”技术能够将你测试的应用程序所包含的线程的运行时间,与操作系统中同时运行着的其他应用程序的线程的运行时间区分开来计算,也就是说,TrueTime只会计算花费在你的应用程序的线程上的时间片。这样一来,在同一台计算机上对同一应用程序的性能测试结果,不会因为在该计算机系统中所运行的程序的多少而改变。所以,只要程序运行的硬件条件不发生改变, TrueTime的测试结果也基本不会变化,所以TrueTime对应用程序性能测试的结果是可复现的。



TrueTime安装成功后,在你的VC++集成开发环境中,会多出一个名为TrueTime的菜单,如下图所示:



12.jpg
图5-1 TrueTime在VC++集成开发环境中添加的菜单



这说明,TrueTime已经完好的集成到VC++集成开发环境中了。



下面开始介绍TrueTime的具体使用步骤。



5.1 使用TrueTime
TrueTime使用步骤:



1在VC++集成开发环境中打开你所要测试的项目。



2 为TrueTime构造一个文件夹 ,具体方法是:



首先,点击[ Build/Configurations…]菜单命令。



其次,在弹出的对话框中点击 Add 按钮。



然后,在Configuration 编辑框中添入你为TrueTime创建的构造文件夹的名称,这个名称是任意的。比如我们取名为TrueTime。



最后,在 Copy settings from组合框中选中 XXX—Win32 Debug项,然后点击OK按钮,接着点击Close按钮。



现在,我们为TrueTime构造好了一个文件夹。



3 点击[Build/Set Active Configuration…] 菜单命令,选中你刚才为TrueTime创建的文件夹, 然后点击OK按钮。



4 选中[TrueTime/Rebuild All with TrueTime] 菜单命令,用TrueTime的编译连接器对程序重新进行编译连接。TrueTime对程序进行编译连接生成的中间文件、可执行程序,都会被放到你为 TrueTime创建的文件夹下。在这个编译连接过程中,TrueTime向可执行程序中插入了一些信息,这也就是TrueTime能够记录程序运行性能的原因。



5点击[TrueTime/Run with TrueTime] 菜单命令,TrueTime被启动,接着你的程序也被启动并执行起来。



这时,按照事先制定好的测试用例,执行测试用例。TrueTime会记录下被执行到的程序代码的性能数据。



在TrueTime界面中有一个工具条,它的外观与我们在TrueCoverage中说到的那个工具条完全一样,实现的功能也基本一样,只是在TrueCoverage中,它控制的是覆盖率,在这里,它控制的是性能数据。我们现在来对它做一下说明。如下所示:



13.jpg
图5-2 运行控制工具条



这个工具条在程序运行起来后,会由不可用状态变为可用状态。工具条上各按钮功能为:



按钮1:如果在程序的运行过程中你按下了该按钮,则性能统计只计算到此,虽然还可以继续操作程序,但此时对程序的任何操作都不再计入到性能统计中去了。



按钮2:如果在程序的运行过程中你按下了该按钮,则TrueTime会马上显示截止到目前为止的性能统计情况。之后你可以继续操作程序,TrueTime会继续在后台记录程序的性能数据。



按钮3:如果在程序的运行过程中你按下了该按钮,则TrueTime会清除在这之前所记录的性能数据,使各项性能数据为零。你可以继续操作,TrueTime在后台从零开始重新记录程序的性能数据。



6当你退出程序时,TrueTime会显示本次操作过程中被执行到的模块、函数、代码行的执行性能数据。要想对程序开始一次新的性能统计,点击TrueTime 中的[Program / Start]菜单命令即可。



TrueTime检测结果的界面如下:



14.jpg
图5-3 TrueTime的运行界面



区域1:



列出了多次运行应用程序后,得到的多个性能检测结果。双击某一项,会在区域2、区域3中显示应用程序在这一次运行过程中的详细性能数据。



区域2:



该窗口中显示的数据与你在区域1中选中的某一次性能检测相对应。窗口中显示的数据以模块(exe、dll等)为单位,列出了程序在某一次执行过程中,各模块的性能数据。对于各模块更为详细的性能数据,显示在区域3中。



区域3:



该窗口中显示的数据与你在区域2中的选择相对应,显示了模块中各个函数的性能数据。在区域3中有三个标签页——Function List标签页、Source标签页、Session Summary标签页。每个标签页中都列出了一组性能数据。其实要使用TrueTime,也就是要了解这些性能数据的含义。下面按照标签页对各项性能数据的含义进行解释:



Function List标签页



在Function List标签页中,用鼠标左键双击某一个函数,会弹出一个对话框,如下图所示:



15.jpg
图5-4 性能数据查看对话框



通过该对话框,能够查看该函数的父函数(调用该函数的函数)、子函数(被该函数调用的函数)的性能信息。



Function List标签页列出了选定模块所包含的函数的性能数据,这些数据包括:



Function Name——函数名称。



% in Function——这是一个百分比,分子为该函数执行的时间,分母为系统从开始运行到结束的时间。



% with Children —— 这是一个百分比,分子为该函数执行的时间,其中包括在该函数中又调用的子函数的运行时间,分母为系统从开始运行到结束的时间。



Called——函数在本次的执行过程中被调用的次数。



Image——函数所在的模块名称(模块指EXE、DLL、OCX等)。



% in Image——这是一个百分比,分子为该函数执行的时间,分母为该函数所在的模块(EXE、DLL、OCX)运行的时间。



Average——函数多次运行时的平均执行时间,其中不包括该函数调用的子函数的运行时间。



First——函数第一次执行所耗费的时间。



Minimum——函数的多次执行中,耗费时间最短的那一次所用的时间。



Maximum——函数的多次执行中,耗费时间最长的那一次所用的时间。



Average with Children——函数被多次执行时,平均执行时间,其中包括该函数调用的子函数运行的时间。



Real——函数的平均执行时间,包括该函数调用的子函数所运行的时间,除此之外,还包括了花费在系统中同时运行着的其他应用程序的线程所耗费的时间。



Address——函数的入口地址。



Source标签页



Source标签页列出了选定模块的指定源文件所包含的各行程序代码的性能数据,这些数据包括:



Count——该行代码在测试过程中被执行的次数。



% of Function——这是一个百分比,分子为该行代码执行的时间,分母为该函数执行的时间。



% With Children——这也是一个百分比,分子为该行代码执行的时间,分母为系统从开始运行到结束的时间。



Time——在系统的运行过程中,该行代码运行时间的累计和。



Functions——该行代码调用的函数的数量。



Line#——代码所在行的行号。



Source——源代码。



Session Summary标签页



Session Summarye标签页列出了该次性能统计的概要信息,内容很好理解,在此就不详细介绍了。



TrueTime为性能数据提供了四种不同的单位,分别是:CPU时钟周期、微妙、毫秒、秒。我们可通过TrueTime中的[View/Scale]菜单项进行设置,如下:



16.jpg
图5-5 性能数据单位设置菜单



在菜单中选中哪一项,性能数据就会以该时间单位显示。



5.2 其他
TrueTime可以收集没有源码部分(比如第三方的DLL、OCX等)的运行性能数据,但如果我们想获得某一个模块的详细运行性能数据,还是需要该模块的源代码。



除了可以在VC++集成环境中使用TrueTime外,从 [开始菜单] 中启动TrueTime,然后打开经TrueTime编译连接生成的可执行文件,也可以进行性能数据的收集,操作方法与集成到VC++集成开发环境中的TrueTime的操作方法一样,在此不再赘述。



TrueTime的介绍到此就结束了。



https://www.ctolib.com/mip/cristaloleg-go-advices.html


Category golang