函数句柄的两种定义方式有什么区别?哪个更健壮更好?

计算科学 matlab 共轭梯度
2021-12-13 02:50:42

最近一直在研究Krylov 子空间迭代方法我发现 matlab 强大的命令pcg和函数句柄的新概念可以返回矩阵向量乘积。然后我在matlab命令窗口中使用help pcg,找到一个例子如下:

%   compare the difference between two ways of definitions of function handle
clc;clear;
tol = 1e-6;
maxit = 15;
M = diag([10:-1:1 1 1:10]);
%%  way 1 from matlab "help pcg"
n1 = 21;
afun = @(x,n)gallery('moler',n)*x;%  returns A*x
b1 = afun(ones(n1,1),n1);
[x1,flag1,rr1,iter1,rv1] = pcg(@(x)afun(x,n1),b1,tol,maxit,M);



%%  way 2   from myself
n2 = 21;
afun = @(x)gallery('moler',n2)*x;%  returns A*x
b2 = afun(ones(n2,1));
[x2,flag2,rr2,iter2,rv2] = pcg(afun,b2,tol,maxit,M);
[iter1 iter2 flag1 flag2]
[rr1 rr2]

如您所见,上面的第一种方法来自 matlab,我找到了另一种方法来定义afun的函数句柄。数值结果与matlab示例相同。似乎没有区别,但我们知道 *MATLAB** 总是以最好的方式为我们提供代码。所以我问有没有区别?函数句柄定义哪种方式更好?欢迎任何提示和建议。谢谢。

2个回答

MATLAB的定义是

afun = @(x,n)gallery('moler',n)*x;%  returns A*x

n因此,您可以将矩阵的大小作为参数传递。如果您要针对不同的 n 值运行代码,您只需声明 n 并运行代码,例如:

for n1 = 10:21
    % do something
    b1 = afun(ones(n1,1),n1);
    [x1,flag1,rr1,iter1,rv1] = pcg(@(x)afun(x,n1),b1,tol,maxit,M);
    % do something
end

你的定义

afun = @(x)gallery('moler',n2)*x;

明确指定使用n2=21,因此它将创建一个 21x21 的矩阵,并且仅创建一个 21x21 的矩阵。如果您需要为不同的 值运行它n,则必须为每个不同的 定义函数n

for n2 = 10:21
    % do something
    afun = @(x)gallery('moler',n2)*x;%  returns A*x
    b2 = afun(ones(n2,1));
    [x2,flag2,rr2,iter2,rv2] = pcg(afun,b2,tol,maxit,M);
    % do something
end

在上面的简单示例中,代码将在 for 的每个循环中声明并创建afun变量,而在 MATLAB 的示例中,afun函数仅声明一次。


编辑:添加执行时间。

MATLAB 对如何测量代码的性能进行了很好的讨论,但基本上你可以使用timeittic toc函数。要测量部分代码,可以使用tic/ toc,但测量函数执行时间的建议是使用timeit. 文档内容如下:

与 tic 和 toc 不同,timeit 函数会多次调用您的代码,因此会考虑首次成本。

然后让我们重写我们的代码来检查执行时间。首先,定义函数:

function [] = matlabfun(num)
    %%  matlab's definition
    tol = 1e-6;  maxit = 1500;
    afun = @(x,n)gallery('moler',n)*x;
    for n1 = 10:num
        % do something
        b1 = afun(ones(n1,1),n1);
        [x1,flag1,rr1,iter1,rv1] = pcg(@(x)afun(x,n1),b1,tol,maxit);
        % do something
    end
end

function [] = myfun(num)
    %%  my definition
    tol = 1e-6;  maxit = 1500;
    for n2 = 10:num
        % do something
        afun = @(x)gallery('moler',n2)*x;%  returns A*x
        b2 = afun(ones(n2,1));
        [x2,flag2,rr2,iter2,rv2] = pcg(afun,b2,tol,maxit);
        % do something
    end
end

然后编写你的主脚本来测量每个函数的执行时间:

clc;clear;
num = 4e+2;%number of tests

matlab_fun = @() matlabfun(num);
my_fun = @() myfun(num);

timeit(matlab_fun)
timeit(my_fun)

结果是:

ans =

   19.9332


ans =

   19.6177

因此,看起来您的定义比 MATLAB 的定义运行得更快。但是,在我的计算机中,差异并不大。

但是,我无法确定为什么会发生这种情况,如果您更改语句的顺序,您将得到不同的响应:

clc;clear;
num = 4e+2;%number of tests

matlab_fun = @() matlabfun(num);
my_fun = @() myfun(num);

timeit(my_fun)
timeit(matlab_fun)

结果是:

ans =

   19.5554


ans =

   19.4029

现在看起来 MATLAB 的定义更快了。这样做的原因,这只是我个人的想法,是第一次调用gallery/pcg函数比随后的调用花费更长的时间。好像 MATLAB 必须找出这些函数的路径/库/工具箱,并且由于这是第一次调用,它会运行一些代码来准备您的计算机运行它。

这是我多年来在 MATLAB 中编程时注意到的一点,第一次调用函数总是比其他函数花费更长的时间。因此,无论您首先在代码中运行哪个定义,都会花费更长的时间。我仍然没有找到一个很好的解释来解释为什么会发生这种情况,但这就是 MATLAB 似乎是如何工作的。但是,嘿,不要太担心这个。

感谢泰尔斯教授的回答。您的意思是,在 for 循环中,matlab 只需声明一次函数句柄afun ,而在我的方式中,matlab 必须在每个循环中声明变量afun 。我认为 Matlab 会比我的方式更快,但我发现相反的结果。

我做了一个简单的测试来证明这两种不同的函数句柄定义方式的CPU时间如下:

clc;clear;
num = 4e+2;%number of tests
%%  matlab's definition
tic
tol = 1e-6;  maxit = 1500;
afun = @(x,n)gallery('moler',n)*x;
for n1 = 10:num
    % do something
    b1 = afun(ones(n1,1),n1);
    [x1,flag1,rr1,iter1,rv1] = pcg(@(x)afun(x,n1),b1,tol,maxit);
    % do something
end
toc


%%  my definition
tic
tol = 1e-6;  maxit = 1500;
for n2 = 10:num
    % do something
    afun = @(x)gallery('moler',n2)*x;%  returns A*x
    b2 = afun(ones(n2,1));
    [x2,flag2,rr2,iter2,rv2] = pcg(afun,b2,tol,maxit);
    % do something
end
toc

并且结果如下(我的cpu是8GB内存,在matlab2018b上),matlab的方式确实比我的方式慢。这适合threory吗?谢谢。

Elapsed time is 70.078114 seconds.
Elapsed time is 56.685017 seconds.