计算与Julia中某些条件匹配的数组中行的最快方法

时间:2017-09-18 15:36:47

标签: arrays julia

在朱莉娅,我有一系列像这样的布尔人:

3×5 Array{Bool,2}:
  true  false  true  false   true
  true   true  true   true  false
 false  false  true   true  false

除了我有超过三行。例如,计算第二列和第四列true的行数以及第二列和第三列为真的行数的最快/最佳方法是什么?现在我有这样的事情,其中​​N是行数:

num_2and3true = 0
num_2and4true = 0
for i = 1:N                                                                                                            
  if m[i,2] == 1                                                                                                        
    if m[i,3] == 1                                                                                                         
      num_2and3true += 1                                                                                                  
    end                                                                                                                  
    if m[i,4] == 1                                                                                                         
      num_2and4true += 1                                                                                                 
    end                                                                                                                
  end                                                                                                                
end 

有更快的方法吗?我有一种预感,我这样做的方式太简单了,不是最好的方法。感谢....

3 个答案:

答案 0 :(得分:4)

问题中的计算确实很简单,也非常优秀。我发现的一点点改进是:

num_2and3true = 0
num_2and4true = 0
for i = indices(m,1)
    num_2and3true += Int(m[i,2] && m[i,3])
    num_2and4true += Int(m[i,2] && m[i,4])
end

我的机器速度提高了约20%。可能是因为支化减少了。要测试我是否使用了随机m,但结果可能因特定m(尺寸和内容)而异。

为了获得更好的性能,请在for@inbounds之前添加,这样可以避免在数组访问中出现一些越界错误检查:

@inbounds for i = indices(m,1)

更好的是,添加@simd,以便在可能的CPU支持下使用SIMD指令:

@inbounds @simd for i = indices(m,1)

导致:

num_2and3true = 0
num_2and4true = 0
@inbounds @simd for i = indices(m,1)
    num_2and3true += Int(m[i,2] && m[i,3])
    num_2and4true += Int(m[i,2] && m[i,4])
end

比问题版本快10倍(再次,在我的机器上)。

更新:请参阅评论,原因indices(m,1)优于1:size(m,1)

更新:将Bool转换为0/1整数(感谢DNF的评论),使代码更清晰。

使用不同方法添加了另一个答案(速度提高了3倍)。这个答案可能更清楚。

答案 1 :(得分:2)

更快但不同的方法是使用BitVectors来压缩Bool向量并使用单个AND指令来替换64个逻辑AND运算。当然,这需要分配一些内存,但事实证明,它的好处超过了成本,而且它的出现速度更快( caveat emptor ,在我的机器上)。功能是:

function bar(m)
    num_2and3true = 0
    num_2and4true = 0
    v2 = BitVector(view(m,:,2))
    v3 = BitVector(view(m,:,3))
    v4 = BitVector(view(m,:,4))
    num_2and3true += sum(v2 .& v3)
    num_2and4true += sum(v2 .& v4)
    return (num_2and3true, num_2and4true)
end

答案 2 :(得分:1)

我从simd获得的加速度不高。这是我发现的最快的版本(实际上,Int产生了一些小的差异):

function foo(m)
    n23 = n24 = 0
    @inbounds for i in indices(m, 1)
        !m[i, 2] && continue
        n23 += Int(m[i, 3])
        n24 += Int(m[i, 4])
    end
    return n23, n24
end

我在m = rand(Bool, 1000, 5)上对此进行了测试。请注意,由于分支,你不能在这里使用@simd,但正如我所说,@simd对我来说做的很少。

替代方案是:

function bar(m)
    n23 = n24 = 0
    @inbounds @simd for i in indices(m, 1)
        n23 += Int(m[i, 2] && m[i, 3])
        n24 += Int(m[i, 2] && m[i, 4])
    end
    return n23, n24
end

编辑:我看到一个非常奇怪的事情:如果我将!m[i, 2] && continue替换为m[i, 2] || continue,我会看到3倍的减速。可能导致什么?