Jac*_*oni 3 sum broadcast in-place julia arrayofarrays
我在我的代码中观察到了“.+=”的意外行为(可能只是我,我对 Julia 还是比较陌生)。考虑以下示例:
julia> b = fill(zeros(2,2),1,3)
1×3 Array{Array{Float64,2},2}:
[0.0 0.0; 0.0 0.0] [0.0 0.0; 0.0 0.0] [0.0 0.0; 0.0 0.0]
julia> b[1] += ones(2,2)
2×2 Array{Float64,2}:
1.0 1.0
1.0 1.0
julia> b
1×3 Array{Array{Float64,2},2}:
[1.0 1.0; 1.0 1.0] [0.0 0.0; 0.0 0.0] [0.0 0.0; 0.0 0.0]
julia> b[2] .+= ones(2,2)
2×2 Array{Float64,2}:
1.0 1.0
1.0 1.0
julia> b
1×3 Array{Array{Float64,2},2}:
[1.0 1.0; 1.0 1.0] [1.0 1.0; 1.0 1.0] [1.0 1.0; 1.0 1.0]
Run Code Online (Sandbox Code Playgroud)
可以看出,最后一条命令不仅改变了 b[2] 的值,也改变了 b[3] 的值,而 b[1] 保持不变(*),我们可以确认运行:
julia> b[2] .+= ones(2,2)
2×2 Array{Float64,2}:
2.0 2.0
2.0 2.0
julia> b
1×3 Array{Array{Float64,2},2}:
[1.0 1.0; 1.0 1.0] [2.0 2.0; 2.0 2.0] [2.0 2.0; 2.0 2.0]
Run Code Online (Sandbox Code Playgroud)
现在,简单地使用“+=”代替我可以获得我对“.+=”也期望的行为,即:
julia> b = fill(zeros(2,2),1,3); b[2]+=ones(2,2); b
1×3 Array{Array{Float64,2},2}:
[0.0 0.0; 0.0 0.0] [1.0 1.0; 1.0 1.0] [0.0 0.0; 0.0 0.0]
Run Code Online (Sandbox Code Playgroud)
谁能解释我为什么会这样?我当然可以只使用 +=,或者可能与数组数组不同的东西,但由于我正在努力提高速度(我的代码需要在更大的矩阵上执行数百万次这些操作)和 . += 快得多,如果我仍然可以利用此功能,我想了解一下。谢谢大家!
编辑:(*) 显然只是因为 b[1] 不为零。如果我运行:
julia> b = fill(zeros(2,2),1,3); b[2]+=ones(2,2);
julia> b[1] .+= 10 .*ones(2,2); b
[10.0 10.0; 10.0 10.0] [1.0 1.0; 1.0 1.0] [10.0 10.0; 10.0 10.0]
Run Code Online (Sandbox Code Playgroud)
您可以看到只有零值发生了变化。这打败了我。
这是由于多种因素的综合作用造成的。让我们试着把事情说清楚。
首先,b = fill(zeros(2,2),1,3)不会zeros(2,2)为 的每个元素创建一个新的b;相反,它创建一个 2x2 零数组,并将 的所有元素设置b为该唯一数组。简而言之,这条线的行为等同于
z = zeros(2,2)
b = Array{Array{Float64,2},2}(undef, 1, 3)
for i in eachindex(b)
b[i] = z
end
Run Code Online (Sandbox Code Playgroud)
因此,修改z[1,1]或任何一个b[i,j][1,1]也会修改其他值。为了说明这一点:
julia> b = fill(zeros(2,2),1,3)
1×3 Array{Array{Float64,2},2}:
[0.0 0.0; 0.0 0.0] [0.0 0.0; 0.0 0.0] [0.0 0.0; 0.0 0.0]
# All three elements are THE SAME underlying array
julia> b[1] === b[2] === b[3]
true
# Mutating one of them mutates the others as well
julia> b[1,1][1,1] = 42
42
julia> b
1×3 Array{Array{Float64,2},2}:
[42.0 0.0; 0.0 0.0] [42.0 0.0; 0.0 0.0] [42.0 0.0; 0.0 0.0]
Run Code Online (Sandbox Code Playgroud)
第二,b[1] += ones(2,2)相当于b[1] = b[1] + ones(2,2)。这意味着一系列操作:
tmp)来保存b[1]和ones(2,2)b[1]被反弹到该新数组,从而失去z与b.这是经典主题的变体,虽然两者都=在其符号中包含符号,但变异和分配并不是一回事。再次说明:
julia> b = fill(zeros(2,2),1,3)
1×3 Array{Array{Float64,2},2}:
[0.0 0.0; 0.0 0.0] [0.0 0.0; 0.0 0.0] [0.0 0.0; 0.0 0.0]
# All elements are THE SAME underlying array
julia> b[1] === b[2] === b[3]
true
# But that connection is lost when `b[1]` is re-bound (not mutated) to a new array
julia> b[1] = ones(2,2)
2×2 Array{Float64,2}:
1.0 1.0
1.0 1.0
# Now b[1] is no more the same underlying array as b[2]
julia> b[1] === b[2]
false
# But b[2] and b[3] still share the same array (they haven't be re-bound to anything else)
julia> b[2] === b[3]
true
Run Code Online (Sandbox Code Playgroud)
第三,b[2] .+= ones(2,2)是一个完全不同的野兽。它并不意味着将任何内容重新绑定到新创建的数组;相反,它会b[2]在原地改变数组。它有效地表现如下:
for i in eachindex(b[2])
b[2][i] += 1 # or b[2][i] = b[2][i] + 1
end
Run Code Online (Sandbox Code Playgroud)
b它本身甚至都没有b[2]被重新绑定到任何东西,只有它的元素被修改到位。而在你的例子这会影响b[3]为好,因为两者b[2]并b[3]绑定到相同的底层数组。