【问题标题】:How can I speed up this bit of code (loop/lists/tuple optimization)?如何加快这段代码(循环/列表/元组优化)?
【发布时间】:2014-06-05 08:47:00
【问题描述】:

我一遍又一遍地重复下面的成语。我从一个大文件(有时多达 120 万条记录!)中读取数据并将输出存储到 SQLite 数据库中。将东西放入 SQLite DB 似乎相当快。

def readerFunction(recordSize, recordFormat, connection, outputDirectory, outputFile, numObjects):

    insertString = "insert into NODE_DISP_INFO(node, analysis, timeStep, H1_translation, H2_translation, V_translation, H1_rotation, H2_rotation, V_rotation) values (?, ?, ?, ?, ?, ?, ?, ?, ?)" 

    analysisNumber = int(outputPath[-3:])

    outputFileObject = open(os.path.join(outputDirectory, outputFile), "rb")
    outputFileObject, numberOfRecordsInFileObject = determineNumberOfRecordsInFileObjectGivenRecordSize(recordSize, outputFileObject)

    numberOfRecordsPerObject = (numberOfRecordsInFileObject//numberOfObjects)

    loop1StartTime = time.time()
    for i in range(numberOfRecordsPerObject ):  
        processedRecords = []

        loop2StartTime = time.time()

        for j in range(numberOfObjects):
            fout = outputFileObject .read(recordSize)

            processedRecords.append(tuple([j+1, analysisNumber, i] + [x for x in list(struct.unpack(recordFormat, fout))]))

        loop2EndTime = time.time()
        print "Time taken to finish loop2: {}".format(loop2EndTime-loop2StartTime)  

        dbInsertStartTime = time.time()
        connection.executemany(insertString, processedRecords)
        dbInsertEndTime = time.time()

    loop1EndTime = time.time()
    print "Time taken to finish loop1: {}".format(loop1EndTime-loop1StartTime)

    outputFileObject.close()
    print "Finished reading output file for analysis {}...".format(analysisNumber)

当我运行代码时,似乎“循环 2”和“插入数据库”是花费最多执行时间的地方。平均“循环 2”时间为 0.003 秒,但在某些分析中,它最多运行 50,000 次。将内容放入数据库所花费的时间大致相同:0.004s。目前,我每次在 loop2 完成后都会插入数据库,这样我就不必处理内存不足的问题了。

我可以做些什么来加快“循环 2”?

【问题讨论】:

    标签: python python-2.7 optimization


    【解决方案1】:

    这主要是一个 I/O 问题。

    for j in range(numberOfObjects):
        fout = outputFileObject .read(recordSize)
    

    您大部分时间都在阅读文件的微小增量位(即一次一条记录),然后使用struct 解压缩这些单独的记录。这很慢。取而代之的是,一次获取您想要的文件的整个块,然后让struct.unpack 以 C 速度翻阅它。

    您需要做一些数学运算来计算read 的字节数,并更改您的recordFormat 格式字符串以告诉struct 如何解压缩整个内容。您的示例中没有足够的信息让我更准确地告诉您应该如何做到这一点。

    我还必须指出这一点:

    tuple([j+1, analysisNumber, i] + [x for x in list(struct.unpack(recordFormat, fout))])
    

    更合理地写成这样:

    (j+1, analysisNumber, i) + struct.unpack(recordFormat, fout)
    

    ...但是如果您按照我上面的建议完全删除循环,则需要重构该行。 (您可以使用zipenumerate 在整个解包后将该数据添加到每个结构成员上)


    编辑: 示例。我将 1M 无符号整数打包到一个文件中。 yours() 是你的方法,mine() 是我的。

    def yours():
         res = []
         with open('packed', 'rb') as f:
             while True:
                 b = f.read(4)
                 if not b:
                     break
                 res.append(struct.unpack('I',b))
         return res
    
    def mine():
         with open('packed', 'rb') as f:
             return struct.unpack('1000000I',f.read())
    

    时间安排:

    %timeit yours()
    1 loops, best of 3: 388 ms per loop
    
    %timeit mine()
    100 loops, best of 3: 6.14 ms per loop
    

    所以,大约有 2 个数量级的差异。

    【讨论】:

    • facepalm 我一直以为元组不能这样更新……我的天……
    • 好吧,它们本身不能更新(它们都是不可变的),但是tuple1 + tuple2 给了你一个新的(第三个)元组。
    【解决方案2】:

    我认为使用 mmap 模块来处理内存映射文件 可能会帮助您节省两次时间。我发现太小或 非常大的块不会节省太多,但您可以尝试查看最佳大小。

    import mmap
    
    def binFileRead(chunk):   # the reading of binary file length size
        with open(filename, "rb") as f:
            for n in range(int(length/chunk)):
                dd=f.read(chunk) 
    
    def mapFileRead(chunk):  # the reading of memory mapped file length size
        with open(filename, "r+b") as f:
            mapf = mmap.mmap(f.fileno(), length, access=mmap.ACCESS_READ)  
            for n in range(int(length/chunk)):
                offset=n*chunk
                dd=mapf[offset:offset+chunk]
              #  dd=mapf.read(chunk)
            mapf.close()
    

    我对这两个函数都计时了:

    timeit("mapFileRead({})".format(n),"from __main__ import mapFileRead", number=1))
    timeit("binFileRead({})".format(n),"from __main__ import binFileRead", number=1))
    
    chunk=4096: 
      mapFileRead 0.00837285185687 
      binFileRead 0.0148429479166 
    

    编辑: 我认为,允许在读取时对文件进行索引访问,以使用并行读取多条记录的线程。如果你有兴趣,我可以写一个例子。

    【讨论】:

      【解决方案3】:

      我在循环 2 中看到的唯一内容是滥用列表理解。

      不要在列表类型对象上使用[x for x in list]。因为您在这里进行必要的迭代。可以写成list

      所以你应该写这样的东西,

      processedRecords.append(
          tuple([j+1, analysisNumber, i] + list(struct.unpack(recordFormat, fout))))
      

      【讨论】:

        猜你喜欢
        • 2019-12-11
        • 1970-01-01
        • 1970-01-01
        • 2018-10-15
        • 2013-02-15
        • 2016-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-12
        相关资源
        最近更新 更多