如何在Julia中沿着布尔数组的轴进行按位或缩小?

时间:2014-08-20 18:52:42

标签: julia

我正在尝试找到在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

但我想知道是否有更好的方法来减少。

4 个答案:

答案 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)

所以我选择simpleshorty

答案 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

我们可以使用一个名为 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)