如何解决Julia 与 Python 中的位打包性能
这个问题 (Parallelize code which is doing bit wise operation) 得到了非常好的答案和非常高效的代码,我只能用 C 代码来匹配。这促使我尝试至少匹配 Julia 中的 Python 代码。
以下是将位打包为无符号整数的 Python 代码需要 0.64 秒,而 C 代码需要 0.27 秒。
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,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,i] .|= AU[:,start_col + 1]
A[:,start_col + 2] .<< 1
A[:,start_col + 3] .<< 2
A[:,start_col + 4] .<< 3
A[:,start_col + 5] .<< 4
A[:,start_col + 6] .<< 5
A[:,start_col + 7] .<< 6
A[:,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
解决方法
如果要比较它们的性能,为什么不尝试使 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 上)。
对于到 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 数组进行转换,因此 Bool.(AU)
需要检查每个元素AU
的值为 0
或 1
。相反,使用 AU .% Bool
,它只会取每个整数的最少一位,并且应该具有如上所示的性能。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。