Julia 在索引中的数组时避免内存分配

Julia 在索引中的数组时避免内存分配,julia,Julia,问题:我希望在不触发内存分配的情况下对数组进行索引,特别是在将索引元素传递到函数中时。通过阅读Julia文档,我怀疑答案围绕着使用sub函数,但不太明白如何 工作示例:我构建了一个大的Float64(x)向量,然后为x中的每个观察值建立索引 N = 10000000 x = randn(N) inds = [1:N] 现在,我将mean函数计时到x和x[inds](我首先运行mean(randn(2)),以避免编译器在计时中出现任何不规则的情况): 这是一个相同的计算,但正如预期的那样,计时结

问题:我希望在不触发内存分配的情况下对数组进行索引,特别是在将索引元素传递到函数中时。通过阅读Julia文档,我怀疑答案围绕着使用
sub
函数,但不太明白如何

工作示例:我构建了一个大的
Float64
x
)向量,然后为
x
中的每个观察值建立索引

N = 10000000
x = randn(N)
inds = [1:N]
现在,我将
mean
函数计时到
x
x[inds]
(我首先运行
mean(randn(2))
,以避免编译器在计时中出现任何不规则的情况):

这是一个相同的计算,但正如预期的那样,计时结果如下:

elapsed time: 0.007029772 seconds (96 bytes allocated)
elapsed time: 0.067880112 seconds (80000208 bytes allocated, 35.38% gc time)
那么,对于任意选择
inds
(以及任意选择数组和函数),有没有办法解决内存分配问题呢

编辑:也阅读tholy的答案以获得完整的图片

当使用一系列指数时,Julia 0.4-pre(2015年2月初)目前的情况并不理想:

  • 因此,虽然我们可以避免内存分配,但实际上我们仍然较慢(!)
  • 问题是通过数组而不是范围进行索引。您不能在0.3上使用
    sub
    ,但可以在0.4上使用
  • 如果我们可以通过一个范围进行索引,那么我们可以使用0.3上的值,并将其内置在0.4上。这种情况与原来的
    方法差不多
我注意到,使用的索引数量较少(而不是整个范围),差距小得多,内存分配也较低,因此
sub
可能值得:

N = 100000000
x = randn(N)
inds = [1:div(N,10)]

@time mean(x)
@time mean(x)
@time mean(x)
@time mean(x[inds])
@time mean(x[inds])
@time mean(x[inds])
xi = sub(x,inds)
@time mean(xi)
@time mean(xi)
@time mean(xi)
给予

只需使用xs=sub(x,1:N)
。注意,这不同于
x=sub(x[1:N])
;在julia 0.3上,后者将失败,而在julia 0.4-pre上,后者将比前者慢得多。在julia0.4-pre上,
sub(x,1:N)
view
一样快:

julia> N = 10000000;

julia> x = randn(N);

julia> xs = sub(x, 1:N);

julia> using ArrayViews

julia> xv = view(x, 1:N);

julia> mean(x)
-0.0002491126429772525

julia> mean(xs)
-0.0002491126429772525

julia> mean(xv)
-0.0002491126429772525

julia> @time mean(x);
elapsed time: 0.015345806 seconds (27 kB allocated)

julia> @time mean(xs);
elapsed time: 0.013815785 seconds (96 bytes allocated)

julia> @time mean(xv);
elapsed time: 0.015871052 seconds (96 bytes allocated)
有几个原因导致
sub(x,inds)
sub(x,1:N)
慢:

  • 每个访问
    xs[i]
    对应于
    x[inds[i]]
    ;我们必须查找两个内存位置,而不是一个
  • 如果
    inds
    不正常,则在访问
    x
  • 它破坏了使用SIMD矢量化的能力
在这种情况下,后者可能是最重要的影响。这不是一个限制;如果用C、Fortran或汇编语言编写等效代码,也会发生同样的情况


请注意,说
sum(sub(x,inds))
比说
sum(x[inds])
要快得多(直到后者变成前者,julia 0.4正式发布时应该是后者)。但是,如果您必须使用
xs=sub(x,inds)
执行许多操作,在某些情况下,制作副本是值得的,即使它分配内存,这样您就可以利用在连续内存中存储值时可能进行的优化。

信息量非常丰富(一如既往)。非常感谢。也许值得一提的是,在v0.3.5中,
sub
似乎对向量索引根本不起作用(
sub(x,inds)
在我的机器上抛出一个错误,但是如果
sub
的第二个参数是范围对象,它就起作用)注意
1:N
[1:N]
之间的区别,请参阅我的答案,澄清一下:如果你说“现在不太好,但是在0.4发布之前会被修复”,那根本不是真的。它已经在计算机硬件允许的范围内进行了优化,当使用范围
sub
进行索引时,其性能与
视图
相同。没有办法使向量索引的性能与范围索引的性能一样。希望我已经修正了我的答案,这样就不会产生误导,你能检查一下吗?请参阅。谢谢你的关注,伊恩丹宁。这很有趣。我没有考虑在连续内存中创建新对象的固有优势。非常感谢。出于好奇,要将数组子集转换为索引子集,是否值得在适当的位置进行排序、子索引,然后在适当的位置使用?
N = 100000000
x = randn(N)
inds = [1:div(N,10)]

@time mean(x)
@time mean(x)
@time mean(x)
@time mean(x[inds])
@time mean(x[inds])
@time mean(x[inds])
xi = sub(x,inds)
@time mean(xi)
@time mean(xi)
@time mean(xi)
elapsed time: 0.092831612 seconds (985 kB allocated)
elapsed time: 0.067694917 seconds (96 bytes allocated)
elapsed time: 0.066209038 seconds (96 bytes allocated)
elapsed time: 0.066816927 seconds (76 MB allocated, 20.62% gc time in 1 pauses with 1 full sweep)
elapsed time: 0.057211528 seconds (76 MB allocated, 19.57% gc time in 1 pauses with 0 full sweep)
elapsed time: 0.046782848 seconds (76 MB allocated, 1.81% gc time in 1 pauses with 0 full sweep)
elapsed time: 0.186084807 seconds (4 MB allocated)
elapsed time: 0.057476269 seconds (96 bytes allocated)
elapsed time: 0.05733602 seconds (96 bytes allocated)
julia> N = 10000000;

julia> x = randn(N);

julia> xs = sub(x, 1:N);

julia> using ArrayViews

julia> xv = view(x, 1:N);

julia> mean(x)
-0.0002491126429772525

julia> mean(xs)
-0.0002491126429772525

julia> mean(xv)
-0.0002491126429772525

julia> @time mean(x);
elapsed time: 0.015345806 seconds (27 kB allocated)

julia> @time mean(xs);
elapsed time: 0.013815785 seconds (96 bytes allocated)

julia> @time mean(xv);
elapsed time: 0.015871052 seconds (96 bytes allocated)