我正在尝试找到在Julia中将3D布尔数组的掩码按位或缩减为2D的最佳方法。
我总是可以写一个for循环,当然:
x = randbool(3,3,3)
out = copy(x[:,:,1])
for i = 1:3
for j = 1:3
for k = 2:3
out[i,j] |= x[i,j,k]
end
end
end
但我想知道是否有更好的方法来减少。
答案 0 :(得分:2)
一个简单的答案是
out = x[:,:,1] | x[:,:,2] | x[:,:,3]
但我做了一些基准测试:
function simple(n,x)
out = x[:,:,1] | x[:,:,2]
for k = 3:n
@inbounds out |= x[:,:,k]
end
return out
end
function forloops(n,x)
out = copy(x[:,:,1])
for i = 1:n
for j = 1:n
for k = 2:n
@inbounds out[i,j] |= x[i,j,k]
end
end
end
return out
end
function forloopscolfirst(n,x)
out = copy(x[:,:,1])
for j = 1:n
for i = 1:n
for k = 2:n
@inbounds out[i,j] |= x[i,j,k]
end
end
end
return out
end
shorty(n,x) = |([x[:,:,i] for i in 1:n]...)
timholy(n,x) = any(x,3)
function runtest(n)
x = randbool(n,n,n)
@time out1 = simple(n,x)
@time out2 = forloops(n,x)
@time out3 = forloopscolfirst(n,x)
@time out4 = shorty(n,x)
@time out5 = timholy(n,x)
println(all(out1 .== out2))
println(all(out1 .== out3))
println(all(out1 .== out4))
println(all(out1 .== out5))
end
runtest(3)
runtest(500)
给出了以下结果
# For 500
simple: 0.039403016 seconds (39716840 bytes allocated)
forloops: 6.259421683 seconds (77504 bytes allocated)
forloopscolfirst 1.809124505 seconds (77504 bytes allocated)
shorty: elapsed time: 0.050384062 seconds (39464608 bytes allocated)
timholy: 2.396887396 seconds (31784 bytes allocated)
所以我选择simple
或shorty
答案 1 :(得分:2)
可以应用各种标准优化技巧和提示,但这里要做的重要观察是Julia以column-major rather than row-major顺序组织数组。对于小尺寸阵列,这是不容易看到的,但是当阵列变大时,它就说明了。提供了一种 reduce 方法,该方法经过优化,可以对集合执行功能(在本例中为 OR ),但需要付出代价。如果组合步骤的数量相对较小,那么最好简单地循环。在所有情况下,最小化内存访问的数量都是更好的。以下是使用这两个方面进行优化的各种尝试。
这是一个功能,它以你的例子为例进行概括。
function boolReduce1(x)
out = copy(x[:,:,1])
for i = 1:size(x,1)
for j = 1:size(x,2)
for k = 2:size(x,3)
out[i,j] |= x[i,j,k]
end
end
end
out
end
创建一个相当大的数组,我们可以计算它的性能
julia> @time boolReduce1(b);
elapsed time: 42.372058096 seconds (1056704 bytes allocated)
这是另一个类似的版本,但标准类型提示,使用 @inbounds 并反转循环。
function boolReduce2(b::BitArray{3})
a = BitArray{2}(size(b)[1:2]...)
for j = 1:size(b,2)
for i = 1:size(b,1)
@inbounds a[i,j] = b[i,j,1]
for k = 2:size(b,3)
@inbounds a[i,j] |= b[i,j,k]
end
end
end
a
end
花点时间
julia> @time boolReduce2(b);
elapsed time: 12.892392891 seconds (500520 bytes allocated)
第二个函数更快,并且因为没有创建临时数组而分配的内存也更少。但是,如果我们只是采用第一个函数并反转数组索引呢?
function boolReduce3(x)
out = copy(x[:,:,1])
for j = 1:size(x,2)
for i = 1:size(x,1)
for k = 2:size(x,3)
out[i,j] |= x[i,j,k]
end
end
end
out
end
现在花点时间
julia> @time boolReduce3(b);
elapsed time: 12.451501749 seconds (1056704 bytes allocated)
这和第二个功能一样快。
我们可以使用一个名为 reduce 的函数来消除第三个循环。其功能是使用前一操作的结果对所有元素重复应用操作。这正是我们想要的。
function boolReduce4(b)
a = BitArray{2}(size(b)[1:2]...)
for j = 1:size(b,2)
for i = 1:size(b,1)
@inbounds a[i,j] = reduce(|,b[i,j,:])
end
end
a
end
现在花点时间
julia> @time boolReduce4(b);
elapsed time: 15.828273008 seconds (1503092520 bytes allocated, 4.07% gc time)
没关系,但是甚至没有简单优化原版那么快。原因是,看一下分配的所有额外内存。这是因为必须从全部复制数据以生成用于reduce的输入。
但是如果我们尽可能地最大限度地提高洞察力呢?而不是最后一个索引减少,第一个是?
function boolReduceX(b)
a = BitArray{2}(size(b)[2:3]...)
for j = 1:size(b,3)
for i = 1:size(b,2)
@inbounds a[i,j] = reduce(|,b[:,i,j])
end
end
a
end
现在创建一个类似的数组并计时。
julia> c = randbool(200,2000,2000);
julia> @time boolReduceX(c);
elapsed time: 1.877547669 seconds (927092520 bytes allocated, 21.66% gc time)
导致函数比大型数组的原始版本快20倍。非常好。
如果尺寸非常大,则上述功能最佳,但如果数据集尺寸较小,则使用reduce不会支付足够的费用,以下内容会更快。包含一个临时变量可以加速版本2中的内容。另一个版本的boolReduceX使用循环代替reduce(这里没有显示)甚至更快。
function boolReduce5(b)
a = BitArray{2}(size(b)[1:2]...)
for j = 1:size(b,2)
for i = 1:size(b,1)
@inbounds t = b[i,j,1]
for k = 2:size(b,3)
@inbounds t |= b[i,j,k]
end
@inbounds a[i,j] = t
end
end
a
end
julia> b = randbool(2000,2000,20);
julia> c = randbool(20,2000,2000);
julia> @time boolReduceX(c);
elapsed time: 1.535334322 seconds (799092520 bytes allocated, 23.79% gc time)
julia> @time boolReduce5(b);
elapsed time: 0.491410981 seconds (500520 bytes allocated)
答案 2 :(得分:2)
试试any(x, 3)
。只需在此输入一点,StackOverflow就不会对此做出反应。
答案 3 :(得分:0)
启动时 更快。这只是你要投入多少工作的问题。天真的开发方法很慢,因为它是一个BitArray:提取连续区域和按位OR一次可以完成一个64位块,但天真的开发方法操作一次一个元素。最重要的是,索引BitArrays很慢,因为涉及一系列位操作,并且由于边界检查,它目前无法内联。这是一个开发出来的策略,但是利用了BitArray的结构。大部分代码都是从copy_chunks中复制粘贴的!在bitarray.jl中,我并没有尝试美化它(抱歉!)。
function devec(n::Int, x::BitArray)
src = x.chunks
out = falses(n, n)
dest = out.chunks
numbits = n*n
kd0 = 1
ld0 = 0
for j = 1:n
pos_s = (n*n)*(j-1)+1
kd1, ld1 = Base.get_chunks_id(numbits - 1)
ks0, ls0 = Base.get_chunks_id(pos_s)
ks1, ls1 = Base.get_chunks_id(pos_s + numbits - 1)
delta_kd = kd1 - kd0
delta_ks = ks1 - ks0
u = Base._msk64
if delta_kd == 0
msk_d0 = ~(u << ld0) | (u << (ld1+1))
else
msk_d0 = ~(u << ld0)
msk_d1 = (u << (ld1+1))
end
if delta_ks == 0
msk_s0 = (u << ls0) & ~(u << (ls1+1))
else
msk_s0 = (u << ls0)
end
chunk_s0 = Base.glue_src_bitchunks(src, ks0, ks1, msk_s0, ls0)
dest[kd0] |= (dest[kd0] & msk_d0) | ((chunk_s0 << ld0) & ~msk_d0)
delta_kd == 0 && continue
for i = 1 : kd1 - kd0
chunk_s1 = Base.glue_src_bitchunks(src, ks0 + i, ks1, msk_s0, ls0)
chunk_s = (chunk_s0 >>> (64 - ld0)) | (chunk_s1 << ld0)
dest[kd0 + i] |= chunk_s
chunk_s0 = chunk_s1
end
end
out
end
根据Iain的基准,这给了我:
simple: 0.051321131 seconds (46356000 bytes allocated, 30.03% gc time)
forloops: 6.226652258 seconds (92976 bytes allocated)
forloopscolfirst: 2.099381939 seconds (89472 bytes allocated)
shorty: 0.060194226 seconds (46387760 bytes allocated, 36.27% gc time)
timholy: 2.464298752 seconds (31784 bytes allocated)
devec: 0.008734413 seconds (31472 bytes allocated)