这出现在其他更复杂的代码中,但我已经写了我认为最小的工作示例。
我发现这种行为令人惊讶:
function byvecdot!(a,b,c)
for i in eachindex(a)
a[i] = vecdot(b[:,i],c[:,i])
end
return
end
function byiteration!(a,b,c)
for i in eachindex(a)
a[i] = 0.0
for j in 1:size(b,1)
a[i] += b[j,i]*c[j,i]
end
end
return
end
a = zeros(Float64,1000)
b = rand(Float64,1000,1000)
c = rand(Float64,1000,1000)
@time byvecdot!(a,b,c)
fill!(a,0.0) # Just so we have exactly the same environment
@time byiteration!(a,b,c)
结果(热身后的JIT):
0.089517 seconds (4.98 k allocations: 15.549 MB, 88.70% gc time)
0.003165 seconds (4 allocations: 160 bytes)
我对分配的数量比时间更令我感到惊讶(前者肯定会导致后者,特别是考虑到所有的gc时间)。
我预计vecdot或多或少与通过迭代进行(使用一些额外的长度检查分配等)。
让这更令人困惑:当我单独使用vecdot时(甚至在切片/视图/子数组/无论它们被称为b [:,i]之类),而不将结果插入数组元素中,它会表现与迭代基本相同。我查看了Julia base中的源代码,毫不奇怪,vecdot只是迭代并累积结果。
我的问题是:当我尝试将数据插入数组元素时,有人可以向我解释为什么vecdot会生成这么多(不必要的)分配吗?我在这里没有掌握什么技巧?
答案 0 :(得分:3)
b[:,i]
分配一个新的Array
对象,因此两个版本之间存在很大差异。第一个版本创建了许多GC必须跟踪和释放的临时版本。另一种解决方案是
function byvecdot2!(a,b,c)
for i in eachindex(a)
a[i] = vecdot(view(b,:,i),view(c,:,i))
end
return
end
视图也分配但比b[:,1]
创建的完整副本少得多,因此GC将减少工作量。