A==0真的比~A更好吗?

25

问题设置介绍

我正在进行一些基准测试,涉及到一个没有NaN的双精度数组的~AA==0,两者都将A转换为逻辑数组,其中所有的0被转换为true值,其余的则设为false值。

对于基准测试,我使用了三组输入数据 -

  • 非常小到小型数据 - 15:5:100
  • 小到中等大小的数据 - 50:40:1000
  • 中等到大型数据 - 200:400:3800

输入是用A = round(rand(N)*20)创建的,其中N是从大小数组中取出的参数。因此,N将从15到100以5为步长变化,第二和第三组类似。请注意,我定义数据大小为N,因此元素的数量将是datasize^2或N^2。

基准测试代码

N_arr = 15:5:100; %// for very small to small sized input array
N_arr = 50:40:1000; %// for small to medium sized input array
N_arr = 200:400:3800; %// for medium to large sized input array
timeall = zeros(2,numel(N_arr));
for k1 = 1:numel(N_arr)
    A = round(rand(N_arr(k1))*20);

    f = @() ~A;
    timeall(1,k1) = timeit(f);
    clear f

    f = @() A==0;
    timeall(2,k1) = timeit(f);
    clear f
end

结果

enter image description here

enter image description here

enter image description here

最后是问题:

可以看到在所有数据大小上,A==0 的表现比 ~A 好。因此,这里有一些观察和相关问题 -

  1. A==0 有一个关系运算符和一个操作数,而 ~A 只有一个关系运算符。两者都产生逻辑数组并都接受双精度数组。实际上,A==0 也可以处理 NaNs,但 ~A 不能。那么,为什么 ~A 至少不像看起来的 A==0 那样好,因为 A==0 正在做更多的工作,或者我在这里漏掉了什么吗?

  2. A==0N = 320,即 102400 个元素的 A 上出现了经过时间的奇怪下降,因此性能有所提高。我在两个不同系统上的多次运行中都观察到了这一点。那么这是怎么回事?


此外,您还可以考虑使用 nnz() 函数! - Nematollah Zarmehi
2
@NematollahZarmehi 这个问题不仅仅是关于计算零的数量,它更为普遍。 - Divakar
1
无论如何,我都会使用A == 0,因为这个表达式恰好说明了你要做的事情,而~A则依赖于布尔和数值的歧义,基本上是一个技巧。而且正如你发现的那样,它也更有效率。至于实现细节,我不清楚,但我通常发现Matlab足够高效,可以让你所编写的代码就是你的意思(WYCIWYM);-) - A. Donda
1
好问题!我一直想知道哪个更快(也包括 logical(A)A~=0)。 - Luis Mendo
1
好的。不知道为什么更快,但这是真的。 然而,在第一次访问后(第8行),A被缓存。因此,A==0比之前没有访问它时更快。 在第一次clear f之后再触发A = round(rand(N_arr(k1))*20);,看看是否有很大的差异。 此外,randi()可以简化您的代码。 - Markus
@Markus 好的,我刚试了一下,性能图似乎保持不变。randi是个好建议!我可能会加上它。 - Divakar
2个回答

5

这不是一个严格的答案,而是我对讨论的贡献

我使用了 profiler 来研究您代码的稍作修改的版本:

N_arr = 200:400:3800; %// for medium to large sized input array

for k1 = 1:numel(N_arr)

    A = randi(1,N_arr(k1));
    [~]=eq(A,0);
    clear A

    A = randi(1,N_arr(k1));
    [~]=not(A);
    clear A   

end

我使用了以下分析器标志(根据UndocumentedMatlab有关Profiler的系列文章):

profile('-memory','on');
profile('on','-detail','builtin');

以下是关于分析器结果的摘录(链接到更大的图片):
链接Profiler output 关于你的第二个问题:在删除保留timeall之前,我尝试用Excel绘制了与你相同的图表。我没有观察到你提到的N = 320的行为。我怀疑这可能与你代码中使用的额外包装(即函数句柄)有关。
下面是所讨论函数的可用文档的附加内容。 ~ 的文档(\MATLAB\R20???\toolbox\matlab\ops\not.m):
%~   Logical NOT.
%   ~A performs a logical NOT of input array A, and returns an array
%   containing elements set to either logical 1 (TRUE) or logical 0 (FALSE).
%   An element of the output array is set to 1 if A contains a zero value
%   element at that same array location.  Otherwise, that element is set to
%   0.
%
%   B = NOT(A) is called for the syntax '~A' when A is an object.
%
%   ~ can also be used to ignore input arguments in a function definition,
%   and output arguments in a function call.  See "help punct"

%   Copyright 1984-2005 The MathWorks, Inc.

==的文档(\MATLAB\R20???\toolbox\matlab\ops\eq.m):

%==  Equal.
%   A == B does element by element comparisons between A and B
%   and returns a matrix of the same size with elements set to logical 1
%   where the relation is true and elements set to logical 0 where it is
%   not.  A and B must have the same dimensions unless one is a
%   scalar. A scalar can be compared with any size array.
%
%   C = EQ(A,B) is called for the syntax 'A == B' when A or B is an
%   object.

%   Copyright 1984-2005 The MathWorks, Inc.

那个奇怪的 N = 320 可能是因为 timeit。我正在研究第一部分建议中提到的 eqnot 的问题。我之前也有类似的想法,试图在 logicalA==0A~=0 之间进行基准测试,但看起来你的想法会更好。 - Divakar

0

虽然不是严格的答案,但我想要增加一些讨论。也许这归结于你的函数timeit

我尝试了Dev-iL的函数。我进行了分析并得到了相同的结果:EQ似乎比NOT更快,并且EQ似乎比NOT分配了稍微多一点的内存。如果EQ运算符分配的内存更多,那么随着数组大小的增加,内存分配也会增加,这似乎是合理的。可疑的是,它并没有增加!

接下来,我删除了所有不必要的内容,并重复了循环N=1000次。分析器似乎仍然认为EQNOT更快。但我并不信服。

接下来我做的是删除奇怪的[~] = ~A[~] = A == 0,换成更人性化的代码,如tmp1 = ~Atmp2 = A == 0,结果就出来了!运行时间几乎相等。

profiler results

所以我猜测你在你的timeid函数内部做了类似的事情。值得注意的是,赋值运算符[~]会减慢两个函数的速度,但NOT似乎比EQ更受影响。

现在的大问题是:为什么运算符[~]会减慢函数的速度?我不知道。也许只有Mathworks能回答这个问题。您可以在Mathworks网页上开一个工单。

部分答案:它们的运行时间几乎相同,即使对于大数组(我尝试过的最大数组是10K)。

未解决的部分:为什么[~]赋值会减慢代码。为什么NOTEQ更受影响。

我的代码:

clear all
clear classes

array_sizes = [1000:1000:10000];
repetitions = 10000;

for i = 1:length(array_sizes)
    A1 = randi([0, 1], array_sizes(i), 1);
    for j = 1:repetitions
        tmp1 = eq(A1, 0);
    end
end

for i = 1:length(array_sizes)
    A2 = randi([0, 1], array_sizes(i), 1);
    for j = 1:repetitions
        tmp2 = not(A2);
    end
end

可能相关:做某事比什么都不做更快 - Dennis Jaheruddin
@DennisJaheruddin 我认为这与此无关。如果您删除tmp1 = eq(A1, 0)tmp2 = not(A2)这两行,并将它们替换为eq(A1, 0);not(A2),则可以获得相同的运行时间。 - gire

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接