Julia中push!()和append!()方法的效率如何?

时间:2016-01-12 18:29:40

标签: arrays julia

this页面上,它说方法push!()append!()非常有效。

我的问题是它们的效率如何?

即,

如果一个知道最终数组的大小,那么预先分配数组还是使用append!() / push!()增量增长它会更快吗?

现在考虑一个不知道最终数组大小的情况。例如,将多个数组合并为一个大数组(称之为A)。

实现这一目标的两种方法:

  1. append!() - 将每个数组都放到A,其大小尚未预先分配。
  2. 首先求和每个数组的维度,以找到合并数组A的最终大小。然后预分配A并复制每个数组的内容。
  3. 在这种情况下哪一个会更有效率?

2 个答案:

答案 0 :(得分:13)

这样一个问题的答案通常是:“这取决于”。例如,您想要制作什么尺寸的阵列?数组的元素类型是什么?

但是,如果您只是在启发式之后,为什么不进行简单的速度测试呢?例如,以下代码段:

function f1(N::Int)
    x = Array(Int, N)
    for n = 1:N
        x[n] = n
    end
    return(x)
end

function f2(N::Int)
    x = Array(Int, 0)
    for n = 1:N
        push!(x, n)
    end
    return(x)
end

f1(2)
f2(2)

N = 5000000000
@time f1(N)
@time f2(N)

表明使用push!比预分配慢约6倍。如果您使用append!添加步数较少的较大块,则乘数几乎肯定会更少。

在解释这些数字时,抵制“什么!?慢6倍!?”的膝跳反应。这个数字需要放在数组构建对整个程序/函数/子程序的重要性的上下文中。例如,如果数组构建只包含例程运行时的1%(对于大多数典型例程,数组构建将包含 小于1%),那么如果例程运行100秒,1秒钟用于构建阵列。乘以6得到6秒。 99秒+6秒= 105秒。因此,使用push!而不是预分配会使整个程序的运行时间增加5%。除非你在高频交易中工作,否则你可能不会关心它。

对于我自己,我通常的规则是:如果我可以轻松预分配,那么我预先分配。但是如果push!使例程更容易编码,引入错误的可能性更小,并且更少考虑预先确定适当的数组大小,那么我会毫不犹豫地使用push!。 / p>

最后注意事项:如果您想真正了解push!工作原理的详细信息,则需要深入研究C例程,因为julia source只包含ccall

更新: OP在评论中质疑push!与MATLAB中array(end+1) = n之类的操作之间的差异。我最近没有在MATLAB中编码,但是我在我的机器上保留了一份副本,因为我所有旧论文的代码都在MATLAB中。我目前的版本是R2014a。我的理解是,在这个版本的MATLAB中,添加到数组的末尾将重新分配整个数组。相比之下,据我所知,朱莉娅的push!.NET中的列表非常相似。随着向量的大小增加,分配给向量的存储器被动态地添加到块中。这大大减少了需要执行的重新分配的数量,虽然我的理解是仍然需要一些重新分配(我很高兴在这一点上得到纠正)。所以push!应该比在Matlab中添加数组更快 。所以我们可以运行以下MATLAB代码:

N = 10000000;
tic
x = ones(N, 1);
for n = 1:N
    x(n) = n;
end
toc


N = 10000000;
tic
x = [];
for n = 1:N
    x(end+1) = n;
end
toc

我明白了:

Elapsed time is 0.407288 seconds.
Elapsed time is 1.802845 seconds.

所以,大约减速5倍。鉴于在时序方法中应用了极端的非严格性,人们可能会试图说这相当于朱莉娅案例。但是等等,如果我们用N = 10000000在朱莉娅重新进行练习,时间是0.01和0.07秒。这些数字的大小与MATLAB数字的巨大差异让我非常担心对实际发生的事情做出声明,以及将MATLAB中的5倍减速与6倍减速进行比较是否合理。朱莉娅。基本上,我现在已经超出了我的深度。也许有人更了解MATLAB实际上做了什么,可以提供更多的见解。关于朱莉娅,我不是一个C编码器,所以我怀疑通过查看源代码可以获得更多的洞察力(与MATLAB不同,这是公开的。)

答案 1 :(得分:13)

push!总是比插入预先分配的数组慢,如果除了push!(1)之外没有其他原因插入元素,就像手动执行时一样,(2) )增加数组的长度。当一个操作是两个操作的一部分时,两个操作不能快于一个操作。

然而,正如其他答案中所指出的那样,差距通常不会大到需要关注的事情。在内部(我上次检查代码时),Julia使用了一个2因子增长策略,因此您只需要log2(N)重新分配。

如果您事先知道数组的大小,可以使用sizehint!来消除重新分配。由于您可以轻松地自行测试,这并不能消除相对于插入预分配数组的性能损失,但它可以减少它。