4

这个问题(Parallelize code which is doing bit-wise operation)得到了一个非常好的答案和非常高效的代码,我只能与 C 代码匹配。这促使我尝试至少匹配 Julia 中的 Python 代码。

以下是用 0.64 秒和 C 代码 0.27 秒将位打包成无符号整数的 Python 代码。

import numpy as np
import numba as nb
import time
colm = int(200000/8)
rows = 10000
cols = int(colm*8)
AU = np.random.randint(2,size=(rows, cols),dtype=np.int8)
A = np.empty((rows,colm), dtype=np.uint8)

@nb.njit('void(uint8[:,:],int8[:,:])', parallel=True)
def compute(A, AU):
    for i in nb.prange(A.shape[0]):
        for j in range(A.shape[1]):
            offset = j * 8
            res = AU[i,offset] << 7
            res |= AU[i,offset+1] << 6
            res |= AU[i,offset+2] << 5
            res |= AU[i,offset+3] << 4
            res |= AU[i,offset+4] << 3
            res |= AU[i,offset+5] << 2
            res |= AU[i,offset+6] << 1
            res |= AU[i,offset+7]
            A[i,j] = res

start_time = time.time()

compute(A, AU)

end_time = time.time()
print(end_time - start_time)

以下是与 Julia 的表现相匹配的各种失败尝试:

using Random
colm = 200000÷8
rows = 10000
cols = colm*8

AU = zeros(UInt8, (rows, cols))

rand!(AU)
AU .&= 0x01

A = BitArray(undef, rows, cols)
B = zeros(UInt8, (rows, colm))

function compute1(A, AU)
    A[:,:] .= AU .== 1
end

function compute2(A, AU)
    for i in 1:size(A)[2]
        start_col = (i-1) << 3
        A[:, i] .=  AU[:, start_col + 1]        .| 
                   (AU[:, start_col + 2] .<< 1) .|
                   (AU[:, start_col + 3] .<< 2) .|
                   (AU[:, start_col + 4] .<< 3) .|
                   (AU[:, start_col + 5] .<< 4) .|
                   (AU[:, start_col + 6] .<< 5) .|
                   (AU[:, start_col + 7] .<< 6) .|
                   (AU[:, start_col + 8] .<< 7)        
    end
end

function compute3(A, AU)
    for i in 1:size(A)[2]
        start_col = (i-1) << 3
        A[:, i] .|=  AU[:, start_col + 1] 
        A[:, i] .|=  AU[:, start_col + 2] .<< 1
        A[:, i] .|=  AU[:, start_col + 3] .<< 2
        A[:, i] .|=  AU[:, start_col + 4] .<< 3
        A[:, i] .|=  AU[:, start_col + 5] .<< 4
        A[:, i] .|=  AU[:, start_col + 6] .<< 5
        A[:, i] .|=  AU[:, start_col + 7] .<< 6
        A[:, i] .|=  AU[:, start_col + 8] .<< 7
    end
end

function compute4(A, AU)
    for i in 0:7
        au_columns = [((j-1) << 3) + i + 1 for j in 1:size(A)[2]] 
        A[:, :] .|=  AU[:, au_columns] .<< i
    end
end

@time compute1(A, AU)
@time compute2(B, AU)
@time compute3(B, AU)
@time compute4(B, AU)

输出:

  6.128301 seconds (553.01 k allocations: 30.192 MiB, 2.22% compilation time)
  3.640022 seconds (1.97 M allocations: 1.984 GiB, 3.05% gc time, 12.43% compilation time)
  2.956211 seconds (1.44 M allocations: 3.842 GiB, 3.73% gc time, 19.24% compilation time)
  6.720456 seconds (946.91 k allocations: 3.776 GiB, 2.40% gc time, 4.68% compilation time)

不同的方法需要 3 到 6 秒。不确定如何提高性能以至少匹配 Python / Numba

4

2 回答 2

3

如果您想比较它们的性能,为什么不尝试让 Julia 代码看起来像 Python 代码?所以像:

rowm = 200000 ÷ 8
cols = 10000
rows = rowm * 8

AU = rand(Int8.(0:1), rows, cols)
A = zeros(UInt8, rowm, cols)

function compute!(A, AU)
    for i in 1:size(A, 2)
        Threads.@threads for j in 1:size(A, 1)
            offset = (j-1) * 8 + 1
            res =  AU[offset  , i] << 7
            res |= AU[offset+1, i] << 6
            res |= AU[offset+2, i] << 5
            res |= AU[offset+3, i] << 4
            res |= AU[offset+4, i] << 3
            res |= AU[offset+5, i] << 2
            res |= AU[offset+6, i] << 1
            res |= AU[offset+7, i]
            A[j, i] = res % UInt8
        end
    end
end

请注意,Julia 是列优先的,因此应该交换索引的顺序。并且您需要使用多个线程显式启动 julia 以使多线程变得有用(julia -t8在 Julia 1.6 上)。

于 2021-04-23T11:58:15.047 回答
2

对于单线程转换为 BitArray,(见编辑说明)应该是有效的:Bool.(AU) AU .% Bool

using Random
using BenchmarkTools
AU = rand(Bool, 10_000, 200_000)
@benchmark Bool.($AU)
BenchmarkTools.Trial: 
  memory estimate:  238.42 MiB
  allocs estimate:  4
  --------------
  minimum time:     658.897 ms (0.00% GC)
  median time:      672.948 ms (0.00% GC)
  mean time:        676.287 ms (0.86% GC)
  maximum time:     710.870 ms (6.57% GC)
  --------------
  samples:          8
  evals/sample:     1

编辑:我刚刚意识到Bool.(AU)对你不起作用,因为你是从一个 8 位整数数组转换,而不是一个布尔数组,所以Bool.(AU)需要检查AUis 0or的每个元素1。取而代之的是 use AU .% Bool,它将只占用每个整数的最低位,并且应该具有上面显示的性能。

于 2021-04-23T11:51:14.547 回答