加速将24位二进制数据加载到16位numpy数组中

5 投票
2 回答
1614 浏览
提问于 2025-04-17 20:30

我用下面的代码把24位的二进制 data 加载到一个16位的 numpy 数组里:

temp = numpy.zeros((len(data) / 3, 4), dtype='b')
temp[:, 1:] = numpy.frombuffer(data, dtype='b').reshape(-1, 3)
temp2 = temp.view('<i4').flatten() >> 16       # >> 16 because I need to divide by 2**16 to load my data into 16-bit array, needed for my (audio) application
output = temp2.astype('int16')

我想这可能有办法提高 速度效率,但是怎么做呢?

2 个回答

1

受到@amaurea的回答启发,这里有一个cython版本(我在原始代码中已经使用了cython,所以我会继续使用cython,而不是混合使用cython和fortran):

import cython
import numpy as np
cimport numpy as np

def binary24_to_int16(char *data):
    cdef int i
    res = np.zeros(len(data)/3, np.int16)
    b = <char *>((<np.ndarray>res).data)
    for i in range(len(data)/3):
        b[2*i] = data[3*i+1]
        b[2*i+1] = data[3*i+2]
    return res            

速度提升了4倍哦 :)

4

看起来你这里说得有点绕。这样做不是能达到同样的效果吗?

output = np.frombuffer(data,'b').reshape(-1,3)[:,1:].flatten().view('i2')

这样做可以节省一些时间,因为不需要给临时数组填充零,省去了位移操作,也避免了一些不必要的数据移动。不过,我还没有实际测试过这个方法,预计节省的时间不会太多。

更新:我现在已经进行了测试。对于长度为1200万的数据,我的版本用时39毫秒,而你的版本用时80毫秒,速度大约快了两倍。虽然提升不算大,但也在预期之内,因为你的方法本身就已经很快了。

更新2:我应该提到我这里假设使用的是小端格式。不过,原问题中的代码也隐含地假设了小端格式,所以这并不是我新加的假设。

(如果是大端格式(数据和架构),你需要把1:替换成:-1。如果数据的字节顺序和CPU不同,你还需要反转字节的顺序(::-1)。)

更新3:为了获得更快的速度,我觉得你可能需要用到Python以外的东西。这个Fortran函数也使用了openMP,相比我的版本,它的速度提升超过两倍(所以比你的快四倍以上)。

subroutine f(a,b)
        implicit none
        integer*1, intent(in)  :: a(:)
        integer*1, intent(out) :: b(size(a)*2/3)
        integer :: i
        !$omp parallel do
        do i = 1, size(a)/3
                b(2*(i-1)+1) = a(3*(i-1)+2)
                b(2*(i-1)+2) = a(3*(i-1)+3)
        end do
        !$omp end parallel do
end subroutine

编译时使用FOPT="-fopenmp" f2py -c -m basj{,.f90} -lgomp。然后你可以在Python中导入并使用它:

import basj
def convert(data): return def mine2(data): return basj.f(np.frombuffer(data,'b')).view('i2')

你可以通过环境变量OMP_NUM_THREADS来控制使用的核心数量,但默认是使用所有可用的核心。

撰写回答