Kir*_*ar. 3 arrays performance allocation julia
我正在尝试在数组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
Run Code Online (Sandbox Code Playgroud)
并在此短循环中获得意外的内存分配(46 KB).为什么会发生这种情况?如何防止内存分配和性能下降?
tho*_*oly 15
你获得分配的原因是因为view(A, i:i+9)创建了一个名为a的小对象SubArray.这只是一个"包装器",它实际上存储了一个引用A和(i:i+9)传入的索引.因为包装器很小(一维对象大约40个字节),所以存储它有两个合理的选择:堆栈或堆栈."分配"仅指堆内存,因此如果Julia可以将包装器存储在堆栈上,它将报告没有分配(并且也会更快).
不幸的是,SubArray目前一些对象(截至2017年底)必须存储在堆上.原因是因为Julia是一种垃圾收集语言,这意味着如果A是一个不再使用的堆分配对象,那么A可能会从内存中释放出来.关键点在于:目前,仅当这些变量存储在堆上时,才会计算对A其他变量的引用.因此,如果所有SubArrays都存储在堆栈中,那么对于这样的代码会有问题:
function create()
A = rand(1000)
getfirst(view(A, 1:10))
end
function getfirst(v)
gc() # this triggers garbage collection
first(v)
end
Run Code Online (Sandbox Code Playgroud)
因为在打电话后create不再使用,所以不是"保护" .风险在于,除非防止被垃圾收集,否则调用可能最终释放与之关联的内存(从而破坏了对条目本身的任何使用,因为它依赖于此).但是目前,堆栈分配的变量无法保护堆分配的内存:垃圾收集器只扫描堆上的变量.AgetfirstAgcAvvAvA
您可以使用原始功能观察此操作,通过删除(无关,用于这些目的)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)
Run Code Online (Sandbox Code Playgroud)
从第一个(带a = collect(1:1010)),你得到
julia> @time check_alloc(a, b)
0.000022 seconds (1.00 k allocations: 47.031 KiB)
Run Code Online (Sandbox Code Playgroud)
(注意这是每次迭代大约47个字节,与SubArray包装器的大小一致)但是从a = 1:1010你得到的第二个(和)
julia> @time check_alloc(a, b)
0.000020 seconds (4 allocations: 160 bytes)
Run Code Online (Sandbox Code Playgroud)
这个问题有一个"明显的"修复:更改垃圾收集器,以便堆栈分配的变量可以保护堆分配的内存.那将在某一天发生,但这是一个非常复杂的操作,以正确支持.所以现在,规则是任何包含对堆分配内存的引用的对象都必须存储在堆上.
有一个最后的微妙之处:Julia的编译器非常聪明,并且在某些情况下省略了SubArray包装器的创建(基本上,它以一种使用父数组对象和索引的方式重写代码,因此它永远不需要包装器本身) .为了实现这一点,Julia必须能够将任何函数调用内联到创建该函数的函数中view.不幸的是,这==对于编译器来说有点太大了,不愿意内联它.如果您手动写出将要执行的操作,那么编译器将忽略该操作view,您也将避免分配.
| 归档时间: |
|
| 查看次数: |
666 次 |
| 最近记录: |