【问题标题】:Python: Fastest way to iterate this through a large filePython:通过大文件进行迭代的最快方法
【发布时间】:2011-01-20 04:00:41
【问题描述】:

对,我正在遍历一个大的二进制文件

我需要尽量减少这个循环的时间:

def NB2(self, ID_LEN):
    r1=np.fromfile(ReadFile.fid,dTypes.NB_HDR,1)
    num_receivers=r1[0][0]
    num_channels=r1[0][1]
    num_samples=r1[0][5]

    blockReturn = np.zeros((num_samples,num_receivers,num_channels))

    for rec in range(0,num_receivers):
        for chl in range(0,num_channels):
            for smpl in range(0,num_samples):
                r2_iq=np.fromfile(ReadFile.fid,np.int16,2)
                blockReturn[smpl,rec,chl] = np.sqrt(math.fabs(r2_iq[0])*math.fabs(r2_iq[0]) + math.fabs(r2_iq[1])*math.fabs(r2_iq[1]))

    return blockReturn

所以,发生的事情如下: r1是文件头,dTypes.NB_HDR是我做的一个类型:

NB_HDR= np.dtype([('f3',np.uint32),('f4',np.uint32),('f5',np.uint32),('f6',np.int32),('f7',np.int32),('f8',np.uint32)])

这将获取有关即将到来的数据块的所有信息,并且很好地将我们置于文件中的正确位置(数据块的开头!)。

在这个数据块中有: 每个通道 4096 个样本, 每个接收器 4 个通道, 9 个接收器。

所以 num_receivers、num_channels、num_samples 将始终相同(目前无论如何),但正如您所见,这是相当大的数据量。每个“样本”都是一对 int16 值,我想找到它们的大小(因此是毕达哥拉斯)。

这个 NB2 代码是针对文件中的每个“块”执行的,对于一个 12GB 的文件(它们有多大),大约有 20,900 个块,我必须遍历其中的 1000 个文件(所以,总共 12TB)。任何速度优势,即使是几毫秒,我们都将不胜感激。

编辑:实际上,了解我在文件中的移动方式可能会有所帮助。我有一个功能如下:

def navigateTo(self, blockNum, indexNum):
    ReadFile.fid.seek(ReadFile.fileIndex[blockNum][indexNum],0)
    ReadFile.currentBlock = blockNum
    ReadFile.index = indexNum

在我运行所有这些代码之前,我会扫描文件并在 ReadFile.fileIndex 处创建一个索引位置列表,我使用此函数浏览这些位置,然后“寻找”到绝对位置 - 这效率高吗?

干杯

【问题讨论】:

    标签: python binary numpy iteration


    【解决方案1】:

    这与其说是一个解决方案,不如说是一种观察,但是将该函数移植到 C++ 并使用 Python API 加载它会在循环优化之前为您带来很大的速度提升。

    【讨论】:

    • 恐怕我根本不懂C++(我知道很尴尬)。关于你将如何做到这一点的任何想法?我想象 3 维数组以及二进制提取直接到 int16 没有位交换和所有低级的肮脏不是太容易?
    • 我不认为它像你想象的那么糟糕。话虽如此,我很难想象你的数据结构,因为我不是最流利的 Python。使用 ifstream::seekg() 函数,您可以根据所需的字节数按顺序获取您的数字,并在向量中转换+存储。
    • @Duncan Tait:你完全没有理由感到尴尬。
    【解决方案2】:

    我会尝试使用尽可能少的循环和尽可能多的常量。 可以以线性方式完成的所有事情都应该这样做。 如果值没有改变,请使用常量来减少查找等, 因为这会占用 CPU 周期。

    这是从理论上的观点;-)

    如果可能,请使用高度优化的库。我不知道您要实现什么目标,但我宁愿使用现有的 FFT-Lib 而不是自己编写它:>

    还有一点:http://en.wikipedia.org/wiki/Big_O_notation(可以大开眼界)

    【讨论】:

      【解决方案3】:
      import numpy as np
      def NB2(self, ID_LEN):
          r1=np.fromfile(ReadFile.fid,dTypes.NB_HDR,1)
          num_receivers=r1[0][0]
          num_channels=r1[0][1]
          num_samples=r1[0][5]
      
          # first, match your array bounds to the way you are walking the file
          blockReturn = np.zeros((num_receivers,num_channels,num_samples))
      
          for rec in range(0,num_receivers):
              for chl in range(0,num_channels):
                  # second, read in all the samples at once if you have enough memory
                  r2_iq=np.fromfile(ReadFile.fid,np.int16,2*num_samples)
                  r2_iq.shape = (-1,2) # tell numpy that it is an array of two values
      
                  # create dot product vector by squaring data elementwise, and then
                  # adding those elements together.  Results is of length num_samples
                  r2_iq = r2_iq * r2_iq
                  r2_iq = r2_iq[:,0] + r2_iq[:,1]
                  # get the distance by performing the square root "into" blockReturn
                  np.sqrt(r2_iq, out=blockReturn[rec,chl,:])
      
          return blockReturn
      

      这应该有助于您的表现。 numpy 工作中的两个主要思想。首先,您的结果数组维度应该与您的循环维度的制作方式相匹配,以用于内存局部性。
      其次,Numpy FAST。我用 numpy 打败了手工编码的 C,仅仅是因为它使用了 LAPack 和矢量加速。但是,要获得这种能力,您必须让它一次处理更多数据。这就是为什么您的样本循环已折叠以在一次大读取中读取接收器和通道的完整样本。然后使用 numpy 的 supreme vector powers 通过点积计算您的大小。

      在幅度计算中需要进行更多优化,但 numpy 会为您回收缓冲区,使其没有您想象的那么重要。我希望这会有所帮助!

      【讨论】:

      • 非常感谢这个深入的回答 - 我尝试了这个和下面的一个,而且速度稍微快了一点。
      【解决方案4】:

      最重要的是,您不应该在三重嵌套循环的最低级别进行文件访问,无论您是在 C 还是 Python 中执行此操作。您必须一次读取大量数据。

      因此,为了加快速度,一次读取大量数据,并使用 numpy 索引处理这些数据(即,对代码进行矢量化)。这在您的情况下特别容易,因为您的所有数据都是 int32。只需读入大块数据,并将数据重新整形为反映(接收器,通道,样本)结构的数组,然后使用适当的索引为毕达哥拉斯乘法和加法,并使用“求和”命令相加结果数组中的项。

      【讨论】:

        【解决方案5】:

        因为您在阅读标题后知道块的长度,所以一次阅读整个块。然后重塑数组(非常快,只影响元数据)并使用np.hypot ufunc:

        blockData = np.fromfile(ReadFile.fid, np.int16, num_receivers*num_channels*num_samples*2)
        blockData = blockData.reshape((num_receivers, num_channes, num_samples, 2))
        return np.hypot(blockData[:,:,:,0], blockData[:,:,:,1])
        

        在我的机器上,它每块运行 11 毫秒。

        【讨论】:

        • 这是一个很棒的解决方案,如果您有足够的内存来同时将所有接收器的通道加载到内存中。
        • 太棒了,真的,谢谢!每块在 10 毫秒内完成,这是 10 倍的改进!
        猜你喜欢
        • 2015-03-07
        • 2020-07-18
        • 2017-11-28
        • 2010-11-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-04-08
        • 2023-04-11
        相关资源
        最近更新 更多