如何更快地遍历这个文本文件?

6

我有一个文件,其中有很多以这种格式为基础的部分:

section_name_1 <attribute_1:value> <attribute_2:value> ... <attribute_n:value> {
    field_1 finish_num:start_num some_text ;
    field_2 finish_num:start_num some_text ;
    ...
    field_n finish_num:start_num some_text;
};

section_name_2 ...
... and so on

文件可能有数十万行。每个部分的属性和字段数量可能不同。我想建立几个字典来保存其中一些值。我已经有一个单独的字典,其中包含所有可能的“属性”值。
import os, re
from collections import defaultdict

def mapFile(myFile, attributeMap_d):
        valueMap_d = {}
        fieldMap_d = defaultdict(dict)

        for attributeName in attributeMap_d:
            valueMap_d[attributeName] = {}

        count = 0
        with open(myFile, "rb") as fh:
            for line in fh:
                # only look for lines with <
                if '<' in line:
                    # match all attribute:value pairs inside <> brackets
                    attributeAllMatch = re.findall(r'<(\S+):(\S+)>', line)
                    attributeAllMatchLen = len(attributeAllMatch)
                    count = 0

                    sectionNameMatch = re.match(r'(\S+)\s+<', line)

                    # store each section name and its associated attribute and value into dict
                    for attributeName in attributeMap_d:
                        for element in attributeAllMatch:
                            if element[0] == attributeName:
                                valueMap_d[attributeName][sectionNameMatch.group(1).rstrip()] = element[1].rstrip()
                                count += 1
                        # stop searching if all attributes in section already matched
                        if count == attributeAllMatchLen: break

                    nextLine = next(fh)

                    #in between each squiggly bracket, store all the field names and start/stop_nums into dict
                    #this while loop is very slow...
                    while not "};" in nextLine:
                        fieldMatch = re.search(r'(\S+)\s+(\d+):(\d+)', nextLine)
                        if fieldMatch:
                            fieldMap_d[sectionNameMatch.group(1)][fieldMatch.group(1)] = [fieldMatch.group(2), fieldMatch.group(3)]
                        nextLine = next(fh)

        return valueMap_d

我的问题是匹配所有字段值的while循环比代码的其余部分明显慢:如果我删除while循环,根据cProfile,它只需要0.5秒而不是2.2秒。我想知道我可以做什么来加速它。


1
你可以使用带有正则表达式的生成器 - 如果您提供一些真实的示例,可能会更好地帮助您。 - Jan
它会慢多少? - Elis Byberi
@Jan,我无法提供原始文件,但我会尝试自己创建一个示例。 - Colin
@ElisByberi 0.5秒 vs. 2.2秒。我编辑了原帖以添加这个信息。 - Colin
必须要慢!引用你的帖子:“数十万行长”@Colin - Elis Byberi
1个回答

2
正则表达式在需要进行复杂模式匹配时非常有用,但当您不需要这样做时,使用str方法解析文本可能会更快。以下是一些比较使用正则表达式和使用str.split进行字段解析的时间的代码。
首先,我创建了一些虚假测试数据,将其存储在rows列表中。这样做使我的演示代码比从文件中读取数据更简单,但更重要的是,它消除了文件读取的开销,因此我们可以更准确地比较解析速度。
顺便说一下,在字段解析循环之外保存sectionNameMatch.group(1),而不是在每个字段行上都进行调用,这样可以提高效率。
首先,我将说明我的代码正确解析了数据。 :)
import re
from pprint import pprint
from time import perf_counter

# Make some test data
num = 10
rows = []
for i in range(1, num):
    j = 100 * i
    rows.append(' field_{:03} {}:{} some_text here ;'.format(i, j, j - 50))
rows.append('};')
print('\n'.join(rows))

# Select whether to use regex to do the parsing or `str.split`
use_regex = True
print('Testing {}'.format(('str.split', 'regex')[use_regex]))

fh = iter(rows)
fieldMap = {}

nextLine = next(fh)
start = perf_counter()
if use_regex:
    while not "};" in nextLine: 
        fieldMatch = re.search(r'(\S+)\s+(\d+):(\d+)', nextLine)
        if fieldMatch:
            fieldMap[fieldMatch.group(1)] = [fieldMatch.group(2), fieldMatch.group(3)]
        nextLine = next(fh)
else:
    while not "};" in nextLine: 
        if nextLine:
            data = nextLine.split(maxsplit=2)
            fieldMap[data[0]] = data[1].split(':')
        nextLine = next(fh)

print('time: {:.6f}'.format(perf_counter() - start))
pprint(fieldMap)

输出

 field_001 100:50 some_text here ;
 field_002 200:150 some_text here ;
 field_003 300:250 some_text here ;
 field_004 400:350 some_text here ;
 field_005 500:450 some_text here ;
 field_006 600:550 some_text here ;
 field_007 700:650 some_text here ;
 field_008 800:750 some_text here ;
 field_009 900:850 some_text here ;
};
Testing regex
time: 0.001946
{'field_001': ['100', '50'],
 'field_002': ['200', '150'],
 'field_003': ['300', '250'],
 'field_004': ['400', '350'],
 'field_005': ['500', '450'],
 'field_006': ['600', '550'],
 'field_007': ['700', '650'],
 'field_008': ['800', '750'],
 'field_009': ['900', '850']}

这里是使用 use_regex = False 的输出结果;我不会再重新打印输入数据。
Testing str.split
time: 0.000100
{'field_001': ['100', '50'],
 'field_002': ['200', '150'],
 'field_003': ['300', '250'],
 'field_004': ['400', '350'],
 'field_005': ['500', '450'],
 'field_006': ['600', '550'],
 'field_007': ['700', '650'],
 'field_008': ['800', '750'],
 'field_009': ['900', '850']}

现在进行真正的测试。我将设置num = 200000并注释掉打印输入输出数据的行。

Testing regex
time: 3.640832

Testing str.split
time: 2.480094

正如你所见,正则表达式的版本要慢大约50%。

这些时间是在我的古老的2GHz 32位机器上运行Python 3.6.0时获得的,所以你的速度可能会有所不同。 ;) 如果你的Python没有time.perf_counter,你可以使用time.time代替。


nextLine中不包含"};"时,执行循环体:这将从开头到结尾扫描每一行(至少我认为是这样的,我不懂Python)。因此,只检查行的前两个字节可能会更快(略微)。 - Danny_ds
@Danny_ds 我决定保留OP代码中的那行,因为我想专注于正则表达式部分,因为这是可以改进的主要内容。是的,在 nextLine 中使用 "};" 进行线性扫描,但该扫描以 C 速度运行,因此比尝试使用 Python 循环搜索它要快。当然,我可以只检查行的前两个字符(在 Python 3 中它们不是字节,因为它使用 Unicode 进行文本),例如使用 .startswith 方法,但那么我就必须假设没有前导空格,或者先通过 .strip 将行传递进行修剪。 - PM 2Ring
是的,C语言速度很快,而且可能已经在L1缓存中了 - 这就是为什么我用了“稍微”这个词 :) 而且使用.strip可能会使情况更糟,这取决于具体实现。无论如何,加一分。 - Danny_ds
@Danny_ds 谢谢!如果我们可以确定 "};" 在行的开头,那么使用 .startswith 而不是 in 绝对是明智的选择。另一方面,在 Python 文本处理中,每行通常会使用 .strip;我在这段代码中没有这样做,因为当您调用没有分隔符参数的 .split 时,它会自动为您删除所有空格。与完全编译语言相比,Python 中的速度可能会很奇怪:对于一个大字符串或列表的操作,其 C 速度运行可能需要与更基本对象上的简单显式 Python 操作大致相同的时间。 - PM 2Ring
@PM2Ring 谢谢!我已经将 while 循环改为 .split,并将运行时间从 2.2 秒降至 0.7 秒 :). 另外,我没有看到任何明显的 .startswith 改进,也没有在将 sectionNameMatch.group(1) 移出循环后看到任何改进。不过,我还是进行了这些更改,因为这是合乎逻辑的事情。 - Colin
@Colin 没问题。你还可以通过使用 .split 解析 section_name_1 <attribute_1:value> ... { 行来获得一些加速。如果这样做,你可能会通过使用 .endswith('{') 来检测这些行而获得轻微的速度提升。或者你可以完全消除这个测试:跳过空行,并假设如果你当前不在一个部分内,那么一行就是一个 section_name 行。 - PM 2Ring

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