我试图在数组X中搜索所需的模式(变量模板)。模板的长度为9。
我做了类似的事情:
function check_alloc{T <: ZeroOne}(x :: AbstractArray{T}, temp :: AbstractArray{T})
s = 0
for i in 1 : 1000
myView = view(x, i : i + 9)
if myView == temp
s += 1
end
end
return s
end
并在此短循环中获得意外的内存分配(46 KB)。为什么会发生这种情况?如何防止内存分配和性能下降?
答案 0 :(得分:11)
您获得分配的原因是view(A, i:i+9)
创建了一个名为SubArray
的小对象。这只是一个“包装器”,它实际上存储了对A
的引用和您传入的索引(i:i+9
)。因为包装器很小(一维对象约为40个字节),所以存储它有两个合理的选择:on the stack or on the heap。 “Allocations”仅指堆内存,因此如果Julia可以将包装器存储在堆栈上,它将报告没有分配(并且也会更快)。
不幸的是,当前(截至2017年底)的一些SubArray
对象必须存储在堆上。原因是因为Julia是一种garbage-collected语言,这意味着如果A
是一个不再使用的堆分配对象,那么A
可能会从内存中释放出来。关键点在于:目前,只有当这些变量存储在堆上时才会计算来自其他变量的对A
的引用。因此,如果所有SubArray
都存储在堆栈中,那么对于这样的代码会有问题:
function create()
A = rand(1000)
getfirst(view(A, 1:10))
end
function getfirst(v)
gc() # this triggers garbage collection
first(v)
end
由于create
在调用A
后没有再次使用getfirst
,因此它不是“保护”A
。风险在于gc
调用可能最终释放与A
相关联的内存(从而打破v
本身条目的任何使用,因为v
依赖于{A
1}}),除非v
保护A
不被垃圾收集。但是目前,堆栈分配的变量无法保护堆分配的内存:垃圾收集器只扫描堆上的变量。
您可以使用原始功能观看此操作,通过删除(不相关的,用于这些目的)T<:ZeroOne
并允许任何T
来修改为稍微限制一些。
function check_alloc(x::AbstractArray{T}, temp::AbstractArray{T}) where T
s = 0
for i in 1 : 1000
myView = view(x, i : i + 9)
if myView == temp
s += 1
end
end
return s
end
a = collect(1:1010); # this uses heap-allocated memory
b = collect(1:10);
@time check_alloc(a, b); # ignore the first due to JIT-compilation
@time check_alloc(a, b)
a = 1:1010 # this doesn't require heap-allocated memory
@time check_alloc(a, b); # ignore due to JIT-compilation
@time check_alloc(a, b)
从第一个(a = collect(1:1010)
),你得到
julia> @time check_alloc(a, b)
0.000022 seconds (1.00 k allocations: 47.031 KiB)
(注意这是每次迭代大约47个字节,与SubArray
包装器的大小一致)但是从第二个(用a = 1:1010
)得到
julia> @time check_alloc(a, b)
0.000020 seconds (4 allocations: 160 bytes)
这个问题有一个“明显的”修复:更改垃圾收集器,以便堆栈分配的变量可以保护堆分配的内存。那将在某一天发生,但这是一个非常复杂的操作,以正确支持。所以现在,规则是任何包含对堆分配内存的引用的对象都必须存储在堆上。
有一个最后的微妙之处:Julia的编译器非常聪明,并且在某些情况下省略了SubArray
包装器的创建(基本上,它以一种使用父数组对象和索引的方式重写代码,因此它永远不需要包装器本身)。为了实现这一点,Julia必须能够inline对创建view
的函数进行任何函数调用。不幸的是,这里==
略大,编译器不愿意内联它。如果您手动写出将要执行的操作,那么编译器将忽略view
并且您也将避免分配。
答案 1 :(得分:0)
这至少适用于任意大小的temp
和x
,但仍然有~KB分配。
function check_alloc{T}(x :: AbstractArray{T}, temp :: AbstractArray{T})
s = 0
pl = length(temp)
for i in 1:length(x)-pl+1
@views if x[i:i+pl-1] == temp
s += 1
end
end
return s
end
编辑:正如@Sairus在评论中所建议的那样,人们可以本着这样的精神做点什么:
function check_alloc2{T}(x :: AbstractArray{T}, temp :: AbstractArray{T})
s = 0
pl = length(temp)
plr = 1:pl
for i in 1:length(x)-pl+1
same = true
for k in plr
@inbounds if x[i+k-1] != temp[k]
same = false
break
end
end
if same
s+=1
end
end
return s
end
这没有分配:
julia> using BenchmarkTools
julia> a = collect(1:1000);
julia> b = collect(5:12);
julia> @btime check_alloc2($a,$b);
1.195 μs (0 allocations: 0 bytes)