【问题标题】:Speed up python's struct.unpack加速 python 的 struct.unpack
【发布时间】:2016-08-16 06:39:17
【问题描述】:

我正在尝试加快我的脚本速度。它基本上读取带有 Velodyne 的 Lidar HDL-32 信息的 pcap 文件,并允许我获取 X、Y、Z 和强度值。我已经使用python -m cProfile ./spTestPcapToLas.py 分析了我的脚本,它在我的readDataPacket() 函数调用中花费的时间最多。在一个小型测试(80 MB 文件)中,解包部分大约需要 56% 的执行时间。

我这样调用readDataPacket函数(chunk指的是pcap文件):

packets = []
for packet in chunk:
    memoryView = memoryview(packet.raw())
    udpDestinationPort = unpack('!h', memoryView[36:38].tobytes())[0]

    if udpDestinationPort == 2368:
        packets += readDataPacket(memoryView)

readDataPacket() 函数本身是这样定义的:

def readDataPacket(memoryView):
    firingData = memoryView[42:]    
    firingDataStartingByte = 0    
    laserBlock = []

    for i in xrange(firingBlocks):
        rotational = unpack('<H', firingData[firingDataStartingByte+2:firingDataStartingByte+4])[0]        
        startingByte = firingDataStartingByte+4
        laser = []
        for j in xrange(lasers):   
            distanceInformation = unpack('<H', firingData[startingByte:(startingByte + 2)])[0] * 0.002
            intensity = unpack('<B', firingData[(startingByte + 2)])[0]   
            laser.append([distanceInformation, intensity])
            startingByte += 3
        firingDataStartingByte += 100
        laserBlock.append([rotational, laser])

    return laserBlock

关于如何加快流程的任何想法?顺便说一句,我使用 numpy 进行 X、Y、Z、强度计算。

【问题讨论】:

    标签: python performance numpy unpack lidar


    【解决方案1】:

    Numpy 让您可以非常快速地做到这一点。在这种情况下,我认为最简单的方法是直接使用ndarray 构造函数:

    import numpy as np
    
    def with_numpy(buffer):
        # Construct ndarray with: shape, dtype, buffer, offset, strides.
        rotational = np.ndarray((firingBlocks,), '<H', buffer, 42+2, (100,))
        distance = np.ndarray((firingBlocks,lasers), '<H', buffer, 42+4, (100,3))
        intensity = np.ndarray((firingBlocks,lasers), '<B', buffer, 42+6, (100,3))
        return rotational, distance*0.002, intensity
    

    这将返回单独的数组而不是嵌套列表,这应该更容易进一步处理。作为输入,它需要一个 buffer 对象(在 Python 2 中)或任何暴露缓冲区接口的东西。不幸的是,这取决于您的 Python 版本 (2/3) 您可以使用哪些对象。但是这个方法很快:

    import numpy as np
    
    firingBlocks = 10**4
    lasers = 32
    packet_raw = np.random.bytes(42 + firingBlocks*100)
    
    %timeit readDataPacket(memoryview(packet_raw))
    # 1 loop, best of 3: 807 ms per loop
    %timeit with_numpy(packet_raw)
    # 100 loops, best of 3: 10.8 ms per loop
    

    【讨论】:

    • 这导致该特定功能的速度提高了约 30 倍。太感谢了。 :D
    • 从 200 秒缩短到 3 秒!
    【解决方案2】:

    提前编译Struct,以避免使用模块级方法的 Python 级包装代码。在循环之外做,所以建设成本不会重复支付。

    unpack_ushort = struct.Struct('<H').unpack
    unpack_ushort_byte = struct.Struct('<HB').unpack
    

    Struct 方法本身是在 CPython 中用 C 语言实现的(在解析格式字符串后,模块级方法最终会委托给相同的工作),因此构建一次 Struct 并存储绑定方法可以节省非微不足道的工作量,尤其是在解包少量值时。

    您还可以通过将多个值解包在一起来节省一些工作,而不是一次一个:

    distanceInformation, intensity = unpack_ushort_byte(firingData[startingByte:startingByte + 3])
    distanceInformation *= 0.002
    

    作为Dan notes,您可以使用iter_unpack 进一步改进这一点,这将进一步减少字节码执行量和小切片操作。

    【讨论】:

    • 我建议在太确定它提高性能之前测试我的iter_unpack 方法——我认为它会创建大量临时对象。你的方法听起来更确定。
    【解决方案3】:

    对于您的具体情况,如果您可以将循环放入 numpy 调用中,那将是最快的。

    话虽如此,对于struct.unpack 部分——如果您的数据恰好是本机字节顺序,您可以使用memoryview.cast。对于 short 的例子,它比简单的 struct.unpack 快​​大约 3 倍,而没有任何逻辑变化。

    In [20]: st = struct.Struct("<H")
    
    In [21]: %timeit struct.unpack("<H", buf[20:22])
    1.45 µs ± 26.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
    
    In [22]: %timeit st.unpack(buf[20:22])
    778 ns ± 10.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
    
    In [23]: %timeit buf.cast("H")[0]
    447 ns ± 4.16 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
    

    【讨论】:

      【解决方案4】:

      您可以在一次调用中将原始 distanceInformationintensity 值解包在一起。特别是因为您只是将它们放在一个列表中:这就是 unpack() 在解包多个值时所做的。在您的情况下,您需要将distanceInformation0.002 相乘,但是您可以将其留到以后来节省时间,因为您可以使用iter_unpack() 在一次调用中解析整个原始对列表。该函数为您提供了一个生成器,可以使用itertools.islice() 对其进行切片,然后变成一个列表。像这样的:

      laser_iter = struct.iter_unpack('<HB', firingData[firingDataStartingByte + 4])
      laser = [[d * 0.002, i] for d, i in itertools.islice(laser_iter, lasers)]
      

      不幸的是,这有点难以阅读,因此您可能想找到一种方法将其分散到更多代码行中,使用更具描述性的变量名称,或者在您忘记为什么编写此代码时添加注释以备将来使用…

      【讨论】:

      • 不幸的是我不能使用 Python 3。我正在使用 Python 2.7.11。你知道另一种解决方案吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-03
      • 1970-01-01
      相关资源
      最近更新 更多