大型二进制文件(经纬度/高度转ECEF)的快速转换(2.1GB+)

1 投票
3 回答
501 浏览
提问于 2025-04-17 04:39

现在,我正在尝试把大量的二进制文件,这些文件里的数据是经纬度和高度的格式,转换成基于文本的ECEF笛卡尔坐标格式(x, y, z)。目前的问题是,这个过程非常非常慢。

我有超过100GB的数据需要处理,而且可能还会有更多的数据进来。我希望能把这段代码尽可能地加快。

现在我的代码大致是这样的:

import mmap
import sys
import struct
import time

pointSize = 41

def getArguments():
    if len(sys.argv) != 2:
        print """Not enough arguments.
        example:
            python tllargbin_reader.py input_filename.tllargbin output_filename
        """
        return None
    else:
        return sys.argv

print getArguments()

def read_tllargbin(filename, outputCallback):
    f = open(filename, "r+")
    map = mmap.mmap(f.fileno(),0)
    t = time.clock()
    if (map.size() % pointSize) != 0:
        print "File size not aligned."
        #return
    for i in xrange(0,map.size(),pointSize):
        data_list = struct.unpack('=4d9B',map[i:i+pointSize])
        writeStr = formatString(data_list)
        if i % (41*1000) == 0:
            print "%d/%d points processed" % (i,map.size())
    print "Time elapsed: %f" % (time.clock() - t)
    map.close()


def generate_write_xyz(filename):
    f = open(filename, 'w', 128*1024)
    def write_xyz(writeStr):
        f.write(writeStr)
    return write_xyz

def formatString(data_list):
    return "%f %f %f" % (data_list[1], data_list[2],data_list[3])
args = getArguments()
if args != None:
    read_tllargbin(args[1],generate_write_xyz("out.xyz"))

convertXYZ()基本上就是这里的转换公式:http://en.wikipedia.org/wiki/Geodetic_system

我在想,如果用一个线程每次读取大约4MB的数据,把它们放进一个有限的缓冲区,然后用另一个线程进行字符串格式的转换,最后再用一个线程把字符串写回到不同硬盘上的文件,这样会不会更快。不过我可能有点急于求成……

我现在正在用Python进行测试,但如果能更快地处理这些文件,我也不介意换其他语言。

任何建议都很好。谢谢!

编辑:

我又用cProfile对代码进行了分析,这次把字符串格式和输入输出分开了。结果发现,实际上是字符串格式的处理拖慢了速度……这是分析报告:

         20010155 function calls in 548.993 CPU seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000  548.993  548.993 <string>:1(<module>)
        1    0.016    0.016  548.991  548.991 tllargbin_reader.py:1(<module>)
        1   24.018   24.018  548.955  548.955 tllargbin_reader.py:20(read_tllargbin)
        1    0.000    0.000    0.020    0.020 tllargbin_reader.py:36(generate_write_xyz)
 10000068  517.233    0.000  517.233    0.000 tllargbin_reader.py:42(formatString)
        2    0.000    0.000    0.000    0.000 tllargbin_reader.py:8(getArguments)
 10000068    6.684    0.000    6.684    0.000 {_struct.unpack}
        1    0.002    0.002  548.993  548.993 {execfile}
        2    0.000    0.000    0.000    0.000 {len}
        1    0.065    0.065    0.065    0.065 {method 'close' of 'mmap.mmap' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.000    0.000    0.000    0.000 {method 'fileno' of 'file' objects}
    10003    0.955    0.000    0.955    0.000 {method 'size' of 'mmap.mmap' objects}
        2    0.020    0.010    0.020    0.010 {open}
        2    0.000    0.000    0.000    0.000 {time.clock}            

有没有更快的方法来格式化字符串呢?

3 个回答

0

考虑到 formatString 是最慢的操作,可以试试这个:

def formatString(data_list):
    return " ".join((str(data_list[1]), str(data_list[2]), str(data_list[3])))
0

读取2.1 GB的数据大约需要21秒(如果速度是100 MB/s)到70秒(如果速度是30 MB/s)。然后你还要把这些数据格式化并写入,可能会产生五倍大小的数据。这就意味着总共需要处理13 GB的数据,读取和写入的时间大约在130到420秒之间。

你的测试显示读取数据需要24秒。因此,写入数据大约需要两分钟。使用固态硬盘(SSD)可以提高读取和写入的速度。

当我转换文件时(使用我自己写的C语言程序),我认为转换的时间不应该超过读取数据的时间,通常可以更快。重叠的读取和写入操作也可以减少输入输出的时间。我自己编写格式化的代码,因为printf通常太慢了。

24秒到底有多长呢?在现代CPU上,这至少相当于处理400亿条指令。这意味着在这段时间内,你可以用至少19条指令处理每一个字节的数据。对于C语言程序来说,这很容易做到,但对于解释型语言(比如Python、Java、C#、VB)来说就不那么简单了。

你提到的525秒的处理时间(549-24)表明,Python在处理时至少消耗了8750亿条指令,也就是每读取一个字节的数据需要415条指令。这是一个常见的比例,解释型语言和编译型语言之间的效率差距。一个设计良好的C语言程序应该能做到每个字节大约只需要十条指令或更少。

2

为了更准确地解决这个问题,我建议你先测量一下文件读取的操作。可以把'convertXYZ'这个函数变成一个什么都不做的函数,然后看看执行的时间。接着,测量转换函数的时间,把'read'改成总是返回一个简单的点,但调用转换和输出的次数要和真正读取文件时一样。(可能还要再做一次测试,把最后转换后的输出也变成什么都不做。)根据时间的消耗情况,可能会更有意义去解决其中一个问题。

你可以尝试让本地操作系统帮你做一些交替处理,比如把输出写到Python的标准输出,然后让命令行来处理实际的文件输入输出。类似地,你也可以通过将文件流入标准输入来实现(例如,cat oldformat | python conversion.py > outputfile)。

输入和输出文件存储在哪里?存储的特性可能对性能影响更大,而不是Python代码本身。

更新:考虑到输出是最慢的,而且你的存储速度也很慢,并且在读写之间是共享的,试着增加一些缓冲区。从Python文档中你应该能找到方法,通过在os.open调用中添加第三个参数来增加缓冲。可以试试比较大的值,比如128*1024?

撰写回答