Python3:快速解码字节为有符号整数,特殊编码

时间:2019-08-25 14:21:48

标签: python decode numpy-ndarray

我通过以太网连接(UDP协议)从PC到传感器之间的连接向我发送了数据。 数据看起来是一个长字节数组,像这样

data
Out[260]: b'03000248023003e802a003a002f8044003c80478038802f002d8024002b00258030003a80300035002a803c0031002e802c8030802e001f8029002a003c8045002d803f003100378038002a002d803700308029003e00260032002e0027002c0028002a802e80338036804c803300398'

此数据实际上是经过编码的,我想从中生成由有符号整数组成的numpy.ndarray。每个ndarray元素一个数据样本。 解码后的每个数组元素代表一个ADC数据样本:12位+符号整数。 编码如下:字节必须按4分组。每个字节实际上代表一个十六进制数。然后将4个十六进制数字放在一起,除以8,我们得到2的补码。

我到目前为止使用的代码工作正常,如下所示:

 j = 0
 while j < nb_sample:    # loop on each adc data sample
        adc_sample_str = chr(udp_frame[4*j]) + chr(udp_frame[4*j+1]) + chr(udp_frame[4*j+2]) + chr(udp_frame[4*j+3])    # create the sample by concatenating 4 hexadecimals characters (2 bytes)
        adc_sample_int = int(adc_sample_str, 16)    # convert into hexa number
        adc_sample_int = (adc_sample_int >> 3)      # right shift by 3 (divide by 8)
        if adc_sample_int > 2047:                   # check if it is a negative value (2048 = 0b 0000 1000 0000 0000)
                adc_sample_int = (adc_sample_int - (2*4096) + 1)    # 2's complement

        result[j] = adc_sample_int    
        j+=1

最大的问题是此循环超级慢。

所以我正在寻找另一种更快的方法(〜10倍) 因此,我尝试了很多事情:使用.decode('UTF-8')转换为字符串或使用具有许多不同dtypes的numpy.frombuffer。 但是我找不到可以读取我奇怪格式的编码(dtype)。

有人知道我应该朝哪个方向看吗? 也许我应该为.decode编写定制编码方案?但是我不知道如何表达我的编码方案。 还是我宁愿转换为字符串?但是之后 ...? 到目前为止,我所做的一切都使我陷入困境。 任何提示都会帮助我... 谢谢

循环代码的结果如下:

result[0:260]
Out[268]: 
array([ 96,  73,  70, 125,  84, 116,  95, 136, 121, 143, 113,  94,  91,
        72,  86,  75,  96, 117,  96, 106,  85, 120,  98,  93,  89,  97,
        92,  63,  82,  84, 121, 138,  91, 126,  98, 111, 112,  84,  91,
       110,  97,  82, 124,  76, 100,  92,  78,  88,  80,  85,  93, 103,
       109, 153, 102, 115,  89, 134, 105, 108,  84, 100,  76, 101,  81,
        96,  98, 106,  98, 116, 109,  98,  93, 118, 111,  94,  95,  98,
        91, 141,  76,  97, 110,  92, 104, 103,  89,  86, 101,  85, 114,
        82,  83, 104,  72, 103, 118,  92, 133, 111, 104,  85, 101,  92,
       108, 108, 108, 100,  81, 102,  99, 102, 125, 121,  68,  75, 104,
        85,  90,  96, 127, 102, 112, 118, 106,  92,  78,  98,  98,  96,
       105,  77,  79, 107, 100,  88,  89, 115,  86,  98, 106, 100, 105,
        79, 121, 109, 115,  80, 113,  84, 131,  91, 114, 126,  93,  95,
       119,  73, 100, 121, 102,  98, 100, 117, 111,  63,  99,  97, 108,
       109,  95,  75, 102,  93, 127, 112,  91,  86,  79,  68, 104, 104,
        84, 116,  85,  79, 120,  95,  91,  75, 135, 116, 115, 119, 102,
        90, 131,  57, 102,  86, 104,  99, 106,  97,  95, 116, 116, 123,
        99,  87,  61, 105,  81, 104,  91, 108, 114,  82, 122,  84, 108,
       107,  93, 101,  95,  76,  84,  74, 104, 113, 110, 104, 123,  91,
        99, 120,  92, 107, 120,  97, 119,  76,  87, 118,  73,  85, 113,
       104, 123,  99,  94, 101,  97, 103,  65, 103])

1 个答案:

答案 0 :(得分:0)

您没有提供完整的最小可重现性问题,因此我临时改善了您提供的代码的工作。

data = b"03000248023003e802a003a002f8044003c80478038802f002d8024002b00258030003a80300035002a803c0031002e802c8030802e001f8029002a003c8045002d803f003100378038002a002d803700308029003e00260032002e0027002c0028002a802e80338036804c803300398"
from numpy import array
import numpy as np


def func(data):
    nb_sample = len(data) // 4
    udp_frame = data
    result = array(range(nb_sample))
    j = 0
    while j < nb_sample:  # loop on each adc data sample
        adc_sample_str = (
            chr(udp_frame[4 * j])
            + chr(udp_frame[4 * j + 1])
            + chr(udp_frame[4 * j + 2])
            + chr(udp_frame[4 * j + 3])
        )  # create the sample by concatenating 4 hexadecimals characters (2 bytes)
        adc_sample_int = int(adc_sample_str, 16)  # convert into hexa number
        adc_sample_int = adc_sample_int >> 3  # right shift by 3 (divide by 8)
        if (
            adc_sample_int > 2047
        ):  # check if it is a negative value (2048 = 0b 0000 1000 0000 0000)
            adc_sample_int = adc_sample_int - (2 * 4096) + 1  # 2's complement
        result[j] = adc_sample_int
        j += 1
    return result


def func4(data):
    for j in range(len(data) // 4):
        adc_sample = int(data[4 * j : 4 * j + 4], 16) >> 3
        if adc_sample > 2047:
            adc_sample -= 2 * 2096 + 1
        yield adc_sample


def func5(data):
    result = np.fromiter(func4(data), int)
    return result

这些是我通过timeit模块得到的结果

>>> import timeit
>>> from functools import partial
>>> def avg(seq): return sum(seq) / len(seq)
...
>>> avg(timeit.repeat(partial(func, data), number=10_000))
0.9369430501999887
>>> avg(timeit.repeat(partial(func5, data), number=10_000))
0.3753632108000602

编辑:

所以我对numpy有点陌生。在我的原始代码中有一个错误, 要从生成器创建numpy数组,应使用np.fromiter而不是np.array 我在测试中遇到问题,导致我没有发现无效的func5。

修复代码会导致性能急剧下降, 现在, func5的速度仅是func的两倍,我认为大部分性能提升来自内部循环的重组。 例如使用data [j:j + 4]代替data [j] + data [j + 1] + data [j + 2] + data [j + 3]

我也误解了何时使用发电机, 生成器更多的是关于空间性能,而不是时间性能。 对于您的应用程序来说可能仍然是一个好主意,但这超出了此问题的范围