尽可能快地在Python中导入大型Tecplot块文件

9
我希望在Python中导入一些ASCII文件(来自CFD后处理软件Tecplot)。 这些文件的规则是(至少对于我需要导入的文件):
  • 文件被分成几个部分

每个部分都有两行标题,如下:

VARIABLES = "x" "y" "z" "ro" "rovx" "rovy" "rovz" "roE" "M" "p" "Pi" "tsta" "tgen" 
ZONE T="Window(s) : E_W_Block0002_ALL",  I=29,  J=17,  K=25, F=BLOCK
  • 每个部分都有一组变量,由第一行给出。当一个部分结束时,新的部分以两行相似的形式开始。
  • 对于每个变量,都有I * J * K个值。
  • 每个变量都是连续的值块。
  • 每行有固定数量的值(6个)。
  • 当一个变量结束时,下一个变量将在新的一行开始。
  • 变量是"IJK有序数据"。I索引变化最快;J索引次之;K索引最慢。I索引应为内循环,K索引应为外循环,J索引应为中间循环。

以下是数据示例:

VARIABLES = "x" "y" "z" "ro" "rovx" "rovy" "rovz" "roE" "M" "p" "Pi" "tsta" "tgen" 
ZONE T="Window(s) : E_W_Block0002_ALL",  I=29,  J=17,  K=25, F=BLOCK
-3.9999999E+00 -3.3327306E+00 -2.7760824E+00 -2.3117116E+00 -1.9243209E+00 -1.6011492E+00
[...]
0.0000000E+00 #fin first variable
-4.3532482E-02 -4.3584235E-02 -4.3627592E-02 -4.3663762E-02 -4.3693815E-02 -4.3718831E-02 #second variable, 'y'
[...]
1.0738781E-01 #end of second variable
[...]
[...]
VARIABLES = "x" "y" "z" "ro" "rovx" "rovy" "rovz" "roE" "M" "p" "Pi" "tsta" "tgen" #next zone
ZONE T="Window(s) : E_W_Block0003_ALL",  I=17,  J=17,  K=25, F=BLOCK

我对Python还很陌生,我编写了一段代码将数据导入字典,并将变量写成3D numpy.array。这些文件可能非常大(高达Gb级别)。我该如何使这段代码更快?(或者更普遍地说,我该如何尽可能快地导入这些文件?)

import re
from numpy import zeros, array, prod
def vectorr(I,  J,  K):
    """function"""
    vect = []
    for k in range(0,  K):
        for j in range(0, J):
            for i in range(0, I):
                vect.append([i, j, k])
    return vect

a = open('E:\u.dat')

filelist = a.readlines()

NumberCol = 6
count = 0
data = dict()
leng = len(filelist)
countzone = 0
while count < leng:
    strVARIABLES = re.findall('VARIABLES', filelist[count])
    variables = re.findall(r'"(.*?)"',  filelist[count])
    countzone = countzone+1
    data[countzone] = {key:[] for key in variables}
    count = count+1
    strI = re.findall('I=....', filelist[count])
    strI = re.findall('\d+', strI[0]) 
    I = int(strI[0])
    ##
    strJ = re.findall('J=....', filelist[count])
    strJ = re.findall('\d+', strJ[0])
    J = int(strJ[0])
    ##
    strK = re.findall('K=....', filelist[count])
    strK = re.findall('\d+', strK[0])
    K = int(strK[0])
    data[countzone]['indmax'] = array([I, J, K])
    pr = prod(data[countzone]['indmax'])
    lin = pr // NumberCol
    if pr%NumberCol != 0:
        lin = lin+1
    vect = vectorr(I, J, K)
    for key in variables:
        init = zeros((I, J, K))
        for ii in range(0, lin):
            count = count+1
            temp = map(float, filelist[count].split())
            for iii in range(0, len(temp)):
                init.itemset(tuple(vect[ii*6+iii]), temp[iii])
        data[countzone][key] = init
    count = count+1

附注:在Python中,没有Cython或其他语言


a = open('E:\8-Documenti\onera stage\u.dat')更改为with open('E:\8-Documenti\onera stage\u.dat') as a开始。其次,您的代码似乎没什么问题,看不出有什么会使它变得非常慢的地方。PS:你在ONERA实习吗?;) - Aleksander Lidtke
1
使用RunSnakeRun来对您的代码进行分析,以了解时间花费在哪里。我认为在大文件上使用正则表达式不是一个好主意。尝试使用PEG或一些自定义解析器? - mguijarr
额,我不知道你在说什么:-)。自定义解析? - Pierpaolo
您需要至少75个声望才能开始悬赏,而您现在已经拥有。现在您应该可以在这些评论下方看到“开始悬赏”的链接了。祝您好运! - Bill the Lizard
1
你的电脑有多少内存?如果速度变得非常慢,可能是因为你使用了所有的内存(第一个f.readlines()调用将所有内容读入内存,而你的Numpy数据结构占用的空间可能也同样多)。从文件格式的外观来看,你可以按顺序读取它,而不必将所有内容复制到RAM中。就像mguijarr建议的那样,对你的代码进行分析以找出导致其变慢的原因。 - GomoX
2
为什么你有几个GB大小的ASCII文件?如果它们以二进制格式(.fits或类似格式)存储,速度会更快。 - usethedeathstar
2个回答

2
将大量字符串转换为数字总是会稍微慢一些,但假设三重嵌套的for循环是瓶颈,也许将其更改为以下内容可以使速度提高足够:
# add this line to your imports
from numpy import fromstring

# replace the nested for-loop with:
count += 1
for key in variables:
    str_vector = ' '.join(filelist[count:count+lin])
    ar = fromstring(str_vector, sep=' ')
    ar = ar.reshape((I, J, K), order='F')

    data[countzone][key] = ar 
    count += lin

很不幸,目前我只能使用智能手机(没有电脑),所以无法测试这个程序的速度,也无法确定它是否正常工作!


更新

最终我进行了一些测试:

  • 我的代码包含了一个小错误,但现在似乎已经可以正确地工作了。
  • 提议更改后的代码运行速度是原始代码的4倍。
  • 你的代码大部分时间都花费在ndarray.itemset上,可能是循环开销和浮点转换。不幸的是,cProfile并没有显示这些细节。
  • 改进后的代码大约70%的时间花费在numpy.fromstring上,这表明在Python / NumPy中,这种方法对于你可以实现的功能来说是相当快的。

更新2

当然,更好的方法是迭代文件而不是一次性加载所有内容。在这种情况下,这样做略微更快(我试过了),并且显着减少了内存使用量。您还可以尝试使用多个CPU核心来加载和将数据转换为浮点数,但这样就难以将所有数据放在一个变量下。最后要警告一句:我使用的fromstring方法随着字符串长度的增加而扩展效果越来越差。例如,从某个字符串长度开始,使用类似于np.fromiter(itertools.imap(float, str_vector.split()), dtype=float)这样的方法更有效。


0

如果您在此处使用正则表达式,有两件事情我会改变:

  • 编译使用频率更高的REs(我想这适用于您示例中的所有REs)。对它们进行regex=re.compile("<pattern>"),并使用生成的对象与match=regex.match()一起使用,如Python文档中所述。

  • 对于I、J、K REs,请考虑使用分组功能(如上所述),将两个REs减少为一个,通过搜索形式为"I=(\d+)"的模式,并使用regex.group(1)获取括号内匹配的部分。进一步地,您可以定义一个单一的正则表达式来一次性捕获所有三个变量。

至少对于开始部分,REs似乎有点过度:您需要查找的字符串没有变化,string.find()在这种情况下已足够且可能更快。

编辑:我刚看到您已经在变量中使用了分组...


1
这些只是用于解析每个部分标题的工具。与实际读取一个GB的数字相比,它们不应该花费太多时间。 - GomoX

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接