12

我正在尝试加快我的脚本。它基本上读取带有 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、强度计算。

4

4 回答 4

13

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
于 2016-04-23T10:15:21.773 回答
9

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 所指出的,您可以使用 进一步改进这一点iter_unpack,这将进一步减少字节码执行量和小切片操作。

于 2016-04-22T15:20:35.567 回答
2

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

话虽如此,仅就struct.unpack部分而言-如果您的数据恰好是本机字节顺序,则可以使用memoryview.cast. 例如,在逻辑没有任何变化的情况下short,它比 naive 快大约 3 倍。struct.unpack

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)
于 2020-09-12T14:12:53.067 回答
1

distanceInformation您可以在一次调用中将原始值和值解包intensity在一起。特别是因为您只是将它们放在一个列表中:这就是unpack()它解压缩多个值时所做的事情。在您的情况下,您需要将distanceInformationby倍数0.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)]

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

于 2016-04-22T15:13:59.740 回答