MATLAB中文论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

MATLAB性能测试框架

2017-3-24 16:59| 发布者: ilovematlab| 查看: 11073| 评论: 6|原作者: oopmatlab

摘要: MATLAB Performance Test 框架是Mathworks 在MATLAB R2016a 中推出的⼀个新的框架,该框架⽤来获得代码性能在统计意义上的数据,还可以⽤来⽐较算法的性能,并且给出详细完整的报告。 ...

目录:

为什么需要Performance Test框架

MATLAB Performance Test 框架是Mathworks在MATLAB R2016a中推出的一个新的框架,该框架用来获得代码性能在统计意义上的数据,还可以用来比较算法的性能,并且给出详细完整的报告。 如果只需要定性的性能结果,tic和toc是一个快速简单的获得代码耗时的工具,大家一定都使用过。比如下面的代码,比较对数组的不同赋值方式,衡量预先分配和不预先分配的耗时差别。
% alloc_tictoc.m
rows = 1000;
cols = 1000;
X=[];
Y=[];

% 对不预先分配的数组X赋值计时
tic
for r = 1:cols
    for c = 1:rows
        X(r,c) = 1;
    end
end
toc
  
% 对预先分配的数组Y赋值计时
tic
Y = zeros(rows,cols);
for r = 1:cols
    for c = 1:rows
        Y(r,c) = 1;
    end
end  
toc
运行结果可以预料,预先分配数组赋值比不预先分配更快。
% Command Line,fontsize=\small 
>> alloc_tictoc
Elapsed time is 0.449438 seconds.    % 不预先分配
Elapsed time is 0.016257 seconds.    % 预先分配
tic,toc可以快速简单的获得定性的结果,但是有时候,在工程计算中需要代码耗时的定量结果,比如对1000 X 1000的数组赋值,想确切知道预先分配比不预先分配究竟快多少? ,再使用tic toc就捉襟见肘了,运行上述script多次, 可以发现得到的其实是一些随机的分布的结果。
>> alloc_tictoc
Elapsed time is 0.472567 seconds.
Elapsed time is 0.014476 seconds.
>> alloc_tictoc
Elapsed time is 0.434714 seconds.
Elapsed time is 0.016879 seconds.
>> alloc_tictoc
Elapsed time is 0.448822 seconds.
Elapsed time is 0.012684 seconds.
>> alloc_tictoc
Elapsed time is 0.474179 seconds.
Elapsed time is 0.013808 seconds.
>> alloc_tictoc
Elapsed time is 0.467369 seconds.
Elapsed time is 0.014176 seconds.
定性的来说,可以肯定预先分配数组的方法要快得多,但是每次测量得到的结果,其实是符合一定分布规律的随机变量{(MATLAB的每一步的计算都要经过确定的函数和优化,从这个角度来说,每次测量应该得到精确唯一的结果。现实中,MATLAB工作在操作系统中,而操作系统会统筹分配系统的计算资源,不同的时刻,资源的分配不一定相同,从而带来了一定的随机性。) },测量结果在一定的范围内波动给获得定量结果造成困难。当两个算法的差别不是很大的时候,这样的波动可能甚至会影响定性的结果。如何得到可靠的性能测量的数据就是我们这章要解决的问题。最容易想到的一个改进就是把运行多次,把每次的结果收集起来,然后求平均,比如:
tic
for iter = 1: 100
  for r = 1:cols
    for c = 1:rows
        X(r,c) = 1;
    end
  end
end
toc
% 再把得到的结果求平均,略
但是循环的次数很难有一个统一的标准,到底循环多少次结果求平均才可靠,次数少了结果不可靠,次数多了浪费时间。还有,理论上能否保证提高循环次数就一定可以得到统计意义上可靠的结果?一个严谨的性能测试不但需要一套规范的标准,还需要统计理论的支持。 另一个测量性能时要注意的问题是:如下所示,测量结果可能对algorithm1不公平,因为MATLAB的代码在第一次运行时候会伴随编译和优化,比如Just In Time Compilation(JIT)和最新的Language Execution Engine(LXE)的加速,这就是说,前几次运行的代码会有一些编译和优化带来的的耗时,可以把它们想象成运动之前的热身,如果algorithm1和algorithm2共用一些的代码,那么algorithm1运行时,可能已经帮助algorithm2热了一部分的身,而带来的额外时间却算在了algorithm1的耗时内
% 代码优化可能带来额外的耗时,fontsize=\small 
% 计时算法1
tic
algorithm1();   
toc

% 计时算法2
tic
algorithm2();
toc
所以更公平的测时方法是,剔除前几次的运行,让要比较的代码都热完身之后再计时
% 剔除代码优化可能带来额外的耗时,fontsize=\small 
% 算法1热身4次
for iter  = 1:4
 algorithm1()
end
% 计时算法1
tic
algorithm1();
toc
% 算法2热身4次
for iter  = 1:4
 algorithm2()
end
% 计时算法2
tic
algorithm2();
toc

基于类的(Class-Based)性能测试框架

构造测试类

构造一个基于类的Performance测试很简单,我们只需要把第 Section 节中的脚本转成性能测试中的方法即可。 任何基于类的Performance 测试类都要继承自matlab.perftest.TestCase父类,也就是框架的提供者;下面的类定义中,还把rows和cols两个变量放到了类的属性中,这样test1和test2可以共享这两个变量。
% AllocTest, fontsize=\small 
classdef AllocTest < matlab.perftest.TestCase   % 性能测试的公共父类
    properties
        rows = 1000
        cols = 1000 
    end    
    methods(Test)
        % 不预先分配赋值 测试点
        function test1(testCase)    
            for r = 1:testCase.cols
                for c = 1:testCase.rows
                    X(r,c) = 1;
                end
            end
        end        
        % 预先分配赋值 测试点
        function test2(testCase)
            X = zeros(testCase.rows,testCase.cols);         
            for r = 1:testCase.cols
                for c = 1:testCase.rows
                    X(r,c) = 1;
                end
            end
        end
    end
end
运行runperf开始Performance测试
>> r = runperf('AllocTest')
Running AllocTest
..........
.......
Done AllocTest
__________
r = 

  1x2 MeasurementResult array with properties:

    Name
    Valid
    Samples
    TestActivity

Totals:
   2 Valid, 0 Invalid
runperf返回一个1X2的结果对象数组,两个测试点都是合格的测试,

测试结果解析

在命令行中检查对象数组中的一个元素,即test1的测试结果
>> r(1)
ans = 
  MeasurementResult with properties:

            Name: 'AllocTest/test1'
           Valid: 1
         Samples: [5x7 table]     %简报  
    TestActivity: [9x12 table]    %原始数据 
Totals:
   1 Valid, 0 Invalid.
其中属性TestActivity是测量的所有测量原始数据,原始sample是有用数据的简报,这里解析TestActivity中的原始数据
>> r(1).TestActivity 

ans = 

         Name          Passed    Failed    Incomplete    MeasuredTime    Objective        
    _______________    ______    ______    __________    ____________    _________  

    AllocTest/test1    true      false     false         0.52387         warmup       
    AllocTest/test1    true      false     false         0.44674         warmup       
    AllocTest/test1    true      false     false         0.50816         warmup       
    AllocTest/test1    true      false     false         0.38104         warmup       
    AllocTest/test1    true      false     false         0.38372         sample       
    AllocTest/test1    true      false     false          0.4197         sample       
    AllocTest/test1    true      false     false         0.38647         sample       
    AllocTest/test1    true      false     false         0.38489         sample       
    AllocTest/test1    true      false     false         0.37503         sample       
测量的结果是一个table对象(table对象的使用参考附录 Section ) , 从结果中看出,测试一共进行了9次,前4次是第 Section 节尾提到对代码的热身,这四次的结果在Objective中标记被做warmup,从数值上也可以大致看出它们和后5次测量有着不同的分布,计算均值的时候需要把它们剔除,正式的测试标记做sample测试,test1的sample测试一共运行了5次。检查r(2)得到类似的结果:
% Command Line , fontsize = \small 
>> r(2)

ans = 

  MeasurementResult with properties:

            Name: 'AllocTest/test2'
           Valid: 1
         Samples: [4x7 table]      %简报
    TestActivity: [8x12 table]     %原始数据

Totals:
   1 Valid, 0 Invalid.
>> 
>> r(2).TestActivity

ans = 

         Name          Passed    Failed    Incomplete    MeasuredTime    Objective    
    _______________    ______    ______    __________    ____________    _________    

    AllocTest/test2    true      false     false         0.018707        warmup       
    AllocTest/test2    true      false     false         0.028393        warmup       
    AllocTest/test2    true      false     false         0.013336        warmup       
    AllocTest/test2    true      false     false         0.012915        warmup       
    AllocTest/test2    true      false     false         0.013543        sample       
    AllocTest/test2    true      false     false         0.012904        sample       
    AllocTest/test2    true      false     false         0.012778        sample       
    AllocTest/test2    true      false     false          0.01312        sample       
test2有4次warmup,4次sample测试。 按照默认设置,每个测试点都要先warmup代码四次,再进入正式的sample测试,有四个sample测试意味着test2这个测试点一共被运行了四次,test2的测试次数和test1的测试次数不同,每个测试点运行几次是由测量数据集合是否到达统计目标所决定的,第 Section 将详细介绍。 有了多次测量的结果,我们可以利用一个帮助函数,从table中取出sample的数据,
function dispMean(result)
 fullTable = vertcat(result.Samples);
 varfun(@mean,fullTable,'InputVariables','MeasuredTime','GroupingVariables','Name')
end
然后对它们求均值,得到的结果才是统计意义上的测量结果。
>> dispMean(r)

ans = 

         Name          GroupCount    mean_MeasuredTime
    _______________    __________    _________________

    AllocTest/test1    5              0.38996         
    AllocTest/test2    4              0.013086         
如果算法在不断的变化中,这样的测量结果也可以保留起来,从而追踪一段时间之内算法性能的变化。

误差范围和置信区间

Performance测试框架规定,一个测试点warmup四次之后,将再运行4到32不等的次数,直到测量数据达到0.05的Relative Margin of Error,0.95的置信区间为止,一但已有的测量值到达了上述的统计目标,就停止计算,如果超过32次还是没有达到0.05的Relative Margin of Error,框架仍然停止计算,但抛出一个警告。这就是为什么前节的test1运行了5次,而test2只运行了4次,它更快达到统计目标。 在每获得一次新的测量数据时,已有数据的Relative Margin of Error都将被重新计算,来决定是否需要再次运行测试点。下面的函数(需要统计工具箱) 帮助计算Relative Margin of Error, 用它来计算test1的数据可以验证相对误差在得到第4次测量结果仍然大于0.05,直到第5次计算小于0.05,于是停止继续测量
% 计算Relative Margin of Error的函数, fontsize = \small 
function er = relMarOfEr(data)
 L = length(data);
 er = tinv(0.95,L-1)*std(data)/mean(data)/sqrt(L);
end
>> relMoE(r(1).Samples.MeasuredTime(1:end-1))   % 取test1的第1到第4次测量结果
ans =
    0.0519

>> relMoE(r(1).Samples.MeasuredTime)            % 取test1所有测量结果
ans =
    0.0421
test2的测量结果类似,第4次的测量,整体数据达到统计目标
>> relMoE(r(2).Samples.MeasuredTime(1:end-1))
ans =
    0.0529

>> relMoE(r(2).Samples.MeasuredTime)
ans =

    0.0302
所谓0.95的置信区间,就是说该系列的测量将确定一个区间,有百分之95的几率实际的真实值就在该区间中。调用函数fitdist得到置信区间(需要统计工具箱。)
>> fitdist(r(1).Samples.MeasuredTime,'Normal')
ans =
  NormalDistribution

  Normal distribution
       mu =  0.389962   [0.368598, 0.411326]   % 0.95置信区间
    sigma = 0.0172059   [0.0103086, 0.049442]
0.05的Margin of Error并不是所有的测试都能达到,事实上我们如果多次运行上述的同一个测试,很有可能test2的结果会有几次含有Warning。
>> r = runperf('AllocTest')
Running AllocTest
..........
..........
..........
..........
....Warning: The target Relative Margin of Error was not met after running the MaxSamples for
AllocTest/test2. 
 % 测试点运行超过32次任没有达到统计目标
Done AllocTest
__________

r = 

  1x2 MeasurementResult array with properties:

    Name
    Valid
    Samples
    TestActivity

Totals:
   2 Valid, 0 Invalid.
>> 
>> r(2)

ans = 

  MeasurementResult with properties:

            Name: 'AllocTest/test2'
           Valid: 1
         Samples: [32x7 table]
    TestActivity: [36x12 table]    % test2运行了一共4+32=36次

Totals:
   1 Valid, 0 Invalid.
Warning说明测量的操作过于的细微,噪音影响过大。我们可以通过增大计算量,或者放松统计目标来避免这个Warning,比如修改默认的Relative Margin of Error
% 增大Relative Margion of Error, fontsize = \small 
>> import matlab.perftest.TimeExperiment
>> experiment = TimeExperiment.limitingSamplingError('RelativeMarginOfError',0.10);
>> suite = testsuite('AllocTest');
>> run(experiment,suite)
Running AllocTest
..........
......
Done AllocTest
__________


ans = 

  1x2 MeasurementResult array with properties:

    Name
    Valid
    Samples
    TestActivity

Totals:
   2 Valid, 0 Invalid.

性能测试的适用范围讨论

性能测试框架最初是Mathworks内部使用的一个框架,使用范围和单元测试一致,单元测试保证在算法的进化过程中,功能不退化;而性能测试保证算法的性能不退化。这样一个框架对MATLAB用户的算法开发显然会带来价值,但是我们要分清什么样的测量才是有价值的,第 Section 的例子是一个简单易懂的例子,但作为MATLAB的用户,我们其实没有必要去测量和记录这些简单的MATLAB的操作的性能(这是Mathworks内部性能测试的主要工作) ,我们只需要记住它们定性的结果,比如给数组赋值之前要先分配,运算尽量向量化等等就可以了。性能测试框架真正能给我们带来的价值的用例,是如下的测试实际算法性能的情况,在用户的算法myAlgorithm的开发过程中,我们可以定期的运行该测试文件,保证性能不退化
classdef AlgoTest1 < matlab.perftest.TestCase
    methods(Test)
        function test1(testCase)   
               myAlgorithm();
        end
    end
end
或者比较两个算法,algorithm1可以代表一个旧的算法,algorithm2代表新的改进的算法,依靠Performance Testing 框架,我们可以得到可靠的数据到底algorithm2改进了多少
classdef AlgoTest1 < matlab.perftest.TestCase
    methods(Test)
        function test1(testCase)   
               algorithm1();
        end
        
        function test2(testCase)            
               algorithm2();
        end
    end
end


关于作者

oopmatlab,计算物理博士,计算机硕士,《MATLAB面向对象编程-从入门到设计模式》作者,现就职一家科学工程计算公司的任架构组C++软件工程师。业余兴趣包括如何把软件工程中的现代手段,应用到科学和工程计算当中去,来更好的解决复杂的问题。包括如何从整体上设计科学计算程序的结构;如何让程序便于扩展和修改;在改进和开发算法的时候,如何保证程序已有的功能没有收到影响;如何让算法开发和测试系统的建立齐头并进。

声明:本版块所有文章版权归作者个人所有,未经允许,不得作为出版物出版。如需转载,请联系论坛管理员
14

鲜花

握手

雷人
5

路过

鸡蛋

刚表态过的朋友 (19 人)

上一篇:MATLAB单元测试

相关阅读

发表评论

最新评论

引用 admin 2017-11-9 05:28
讲解的非常详细,感谢分享
引用 account_ch 2017-11-3 16:55
6666666666666666
引用 zlw070602 2017-10-30 21:53
讲解的非常详细,感谢分享
引用 l2l5l0ll 2017-10-8 14:02
真心很实用 面向对象那本书有空好好拜读下
引用 admin 2017-9-22 00:14
非常适合大型复杂项目的实例运用!
引用 死侍 2017-7-10 11:31
666
引用 五味鱼头 2017-5-29 10:31
谢谢谢谢
引用 stillmove 2017-5-18 10:19
666666666666666666666666666
引用 oopmatlab 2017-3-31 23:39
简单的问题不需要使用框架,框架适用于工业级的问题和复杂的工程项目
引用 qibbxxt 2017-3-30 09:18
对于简单的性能测试,是否可以用timeit函数呢?

查看全部评论(6)

MATLAB table数据结构 首篇

MATLAB table是R2013b中引入的一个新的数据结构,虽然不像常用的基本数据类型为人熟悉,但是在编程中非常有用。它用来存放表状类型的数据结构,并且支持常见的表和表之间的运算。 ... ... ... ... ...

MATLAB映射表数据结构

除了常用的基本数据类型,MATLAB还有很多其它实用的数据类型不为人熟悉,例如映射表containers.Map,常用的MATLAB高级数据类型。它最大的特点使用方便的索引方式进行快速的查找。本篇介绍为什么需要这种数据结构,以 ...

MATLAB table数据结构 再篇

MATLAB table是R2013b中引入的一个新的数据结构,虽然不像常用的基本数据类型为人熟悉,但是在编程中非常有用。它用来存放表状类型的数据结构,并且支持常见的表和表之间的运算。 ... ... ... ... ...

MATLAB单元测试

本篇是把现代软件工程思想应用到MATLAB工程开发中的精髓,希望高级MATLAB用户仔细研读。作者用实际的例子解释在开发和逐渐改进算法的时候,如何保证程序已有的功能没有收到影响,步步为营,让算法开发和测试系统的建 ...

对函数的输入进行检查和解析

在工程计算中,如果函数的输入有错误,我们总是希望能尽早捕捉到这些错误,并及时终止程序。在MATLAB 中,可以使用validateattributes,validatestring和inputParser 类来对输入进行检查。它们提供全面的检查功能和清 ...

MATLAB性能测试框架

MATLAB Performance Test 框架是Mathworks 在MATLAB R2016a 中推出的⼀个新的框架,该框架⽤来获得代码性能在统计意义上的数据,还可以⽤来⽐较算法的性能,并且给出详细完整的报告。 ...

MATLAB单元测试

本篇是把现代软件工程思想应用到MATLAB工程开发中的精髓,希望高级MATLAB用户仔细研读。作者用实际的例子解释在开发和逐渐改进算法的时候,如何保证程序已有的功能没有收到影响,步步为营,让算法开发和测试系统的建 ...

对函数的输入进行检查和解析

在工程计算中,如果函数的输入有错误,我们总是希望能尽早捕捉到这些错误,并及时终止程序。在MATLAB 中,可以使用validateattributes,validatestring和inputParser 类来对输入进行检查。它们提供全面的检查功能和清 ...
关闭

站长推荐上一条 /2 下一条

返回顶部