Python 使用内置函数和并行化读取大型(>5GB)日志文件的最快方法?

Python 使用内置函数和并行化读取大型(>5GB)日志文件的最快方法?,python,python-3.x,Python,Python 3.x,作为python新手,我的任务是找到用python解析大型日志文件的最快方法 这些是我迄今为止尝试过的方法,它们给了我33到43秒的处理时间。 这一次耗时最长,为43秒: tflines1 = tfile.readlines() time_data_count = 0 for line in tflines1: if 'TIME_DATA' in line : time_data_count += 1 if time_data_count > 20 : p

作为python新手,我的任务是找到用python解析大型日志文件的最快方法

这些是我迄今为止尝试过的方法,它们给了我33到43秒的处理时间。 这一次耗时最长,为43秒:

tflines1 = tfile.readlines()

time_data_count = 0
for line in tflines1:
    if 'TIME_DATA' in line :
        time_data_count += 1
if time_data_count > 20 :
    print("time_data complete")
else:
    print("incomplete time_data data")
这一次平均耗时34秒:

with open(filename) as f:
    time_data_count = 0
    while True:
        memcap = f.read(102400)
        memcaplist = memcap.split("\n")
        for line in memcaplist:
            if 'TIME_DATA' in line:
                time_data_count += 1
        if not memcap:
            break
这一次平均36秒:

with open(filename, 'r', buffering=102400) as f:
    time_data_count = 0
    for line in f:
        if 'TIME_DATA' in line:
            time_data_count += 1
这一次平均36秒:

logfile = open(filename)
time_data_count = 0
for line in logfile:
    if 'TIME_DATA' in line:
            time_data_count += 1
最快的是这个在26.8秒内完成任务的,我不知道为什么它是最快的。我不明白是什么让它如此特别。对于这个和其他类似的文件,我指定了字节,我担心可能会有一两个文件,其中一行在字节块之间被分割,而我要查找的字符串被分割成两半。这将导致错误的计数。如何解决这个问题:

with open(filename) as f:
    time_data_count = 0
    while True:
        memcap = f.read(102400)
        time_data_count += memcap.count('TIME_DATA')
        if not memcap:
            break
    if time_data_count > 20:
        print("time_data complete")
    else:
        print("incomplete time_data data")
不管怎么说,老板让我研究其他可能使事情更快的方法。他建议列表理解和读取二进制文件对象。我不认为列表理解对从文件中提取所需数据有多大帮助。我觉得将文件作为二进制文件阅读会产生额外的代码行,代码需要知道何时预测某些字符。甚至将文件读取为二进制文件也会有不同吗?这不是C++,可以使用指针。

我简要地阅读了并行化,但我不确定这是否适用于我们的用例。我需要跟踪特定字符串出现的次数,因此我不确定当您要跟踪一系列内容时,将文件拆分为不同的线程将如何工作。这可能吗

@TimPeters这里是使用二进制文件和seek对最终方法的编辑:

with open(filename, 'rb') as f:
    time_data_count = 0
    while True:

        memcap = f.read(102400)
        f.seek(-tdatlength, 1)
        time_data_count += memcap.count(b'TIME_DATA')

        if not memcap:
            break
    if time_data_count > 20:
        print("time_data complete")
    else:
        print("incomplete time_data data")
我尝试的另一种方法是:

with open(filename, 'rb', buffering=102400) as f:
    time_data_count = 0
      #ask tenzin about seek in this situation
    for line in f:
        if b'TIME_DATA' in line:
            time_data_count += 1
    f.seek(-tdatlength, 2)
    if time_data_count > 20:
        print("time_data complete")
    else:
        print("incomplete time_data data")
    print(time_data_count)

您可以使用python的多处理库并行解析多个日志文件:

这样,如果您有4个内核,您将在1次处理4个日志文件。此外,每个文件都是单独处理的,因此时间_数据计数器保持不变

如果要处理大量日志文件,我建议使用:


通过这种方式,您可以跟踪进度并估计整个操作所需的时间。

您可以使用python的多处理库并行解析多个日志文件:

这样,如果您有4个内核,您将在1次处理4个日志文件。此外,每个文件都是单独处理的,因此时间_数据计数器保持不变

如果要处理大量日志文件,我建议使用:

通过这种方式,您可以跟踪进度并估计整个操作所需的时间。

以下是用于提高I/O性能的方法。没有您的日志文件,我无法对其进行基准测试,但我相信它将比基于行的I/O快得多

如果您希望将其并行化,可以在此基础上进行扩展,因为mmap对象支持随机访问。因此,您可以同时启动多个线程,从文件中的多个点开始搜索

import mmap
import sys

with open(sys.argv[1], 'rb') as f:
    mm = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
    target = b'TIME_DATA'
    tl = len(target)
    idx = -tl
    counter = 0
    while True:
        idx = mm.find(target, idx + tl)
        if idx < 0:
            break
        counter += 1
    print(counter)
    mm.close()

下面是用于提高I/O性能的示例。没有您的日志文件,我无法对其进行基准测试,但我相信它将比基于行的I/O快得多

如果您希望将其并行化,可以在此基础上进行扩展,因为mmap对象支持随机访问。因此,您可以同时启动多个线程,从文件中的多个点开始搜索

import mmap
import sys

with open(sys.argv[1], 'rb') as f:
    mm = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
    target = b'TIME_DATA'
    tl = len(target)
    idx = -tl
    counter = 0
    while True:
        idx = mm.find(target, idx + tl)
        if idx < 0:
            break
        counter += 1
    print(counter)
    mm.close()


提示在这里不起作用,所以以一种具体的方式充实检查重叠的想法,相当于-但不使用-.seek

WindowsSize太小,目标无法在取自last或memcap的重叠部分中匹配,因此,如果在重叠中找到目标,则它必须在每个片段中至少匹配一个字符:这确实是一个重叠匹配。在另一个方向,如果相邻块之间存在匹配,则它必须从last的最后一个窗口大小字符之一开始,并在memcap的第一个窗口大小字符之一结束,因此窗口大小足够大,可以找到任何此类匹配

编辑:修复了下面一个太强的索赔

有一个模棱两可的地方:如果目标的某个前缀也是目标的后缀,则匹配可能重叠。例如,abab将ab作为前缀和后缀。因此,如果一个块以结束,下一个块以abab开始,重叠将是bababa。你或者你不想把ABAB算在中间吗?也就是说,abab在abababab中发生2次还是3次?上面的代码是3

但是,对于没有前缀等于后缀的目标(如时间数据),这种模糊性不会出现。例如,对于带有后缀和前缀的ATIME_数据,可能会出现这种情况:这种情况在ATIME_DATATIME_数据中出现一次还是两次?上面的代码可以说两次,如果它被分割成块,例如,…ATIME_DATATI和ME_DATA

如果您关心这一点,可以通过进行简短的搜索来解决,以确保粘贴在一起的片段中的匹配不会与左侧块的末尾或开头附近的匹配重叠
正确的块。

提示在这里不起作用,因此以一种具体的方式充实检查重叠的想法,相当于-但不使用-.seek

WindowsSize太小,目标无法在取自last或memcap的重叠部分中匹配,因此,如果在重叠中找到目标,则它必须在每个片段中至少匹配一个字符:这确实是一个重叠匹配。在另一个方向,如果相邻块之间存在匹配,则它必须从last的最后一个窗口大小字符之一开始,并在memcap的第一个窗口大小字符之一结束,因此窗口大小足够大,可以找到任何此类匹配

编辑:修复了下面一个太强的索赔

有一个模棱两可的地方:如果目标的某个前缀也是目标的后缀,则匹配可能重叠。例如,abab将ab作为前缀和后缀。因此,如果一个块以结束,下一个块以abab开始,重叠将是bababa。你或者你不想把ABAB算在中间吗?也就是说,abab在abababab中发生2次还是3次?上面的代码是3

但是,对于没有前缀等于后缀的目标(如时间数据),这种模糊性不会出现。例如,对于带有后缀和前缀的ATIME_数据,可能会出现这种情况:这种情况在ATIME_DATATIME_数据中出现一次还是两次?上面的代码可以说两次,如果它被分割成块,例如,…ATIME_DATATI和ME_DATA


如果您关心这一点,可以通过进行简短的搜索来解决,以确保粘贴在一起的段中的匹配不会与左块末尾或右块开头附近的匹配重叠。

日志文件是什么样子的?如果您能在文件中找到时间\数据的时间/地点中找到一个模式,可能会有所帮助。@JayMody肯定有一个模式。这会有什么帮助呢?这取决于格式/模式,但一个简单的例子是时间数据只出现在奇数行上,这会将搜索减少一半。很难说它有什么帮助,这完全取决于文件的实际外观。@JayMody我刚检查过,虽然有一个模式,但它是不规则的,因为这些日志来自工厂,有时数据不完整,取决于可用的项目的种类。这使得预测它将在何处发生变得奇怪甚至困难。事实上,这就是我正在编写的测试的要点,以便能够捕捉到时间数据不完整的情况。虽然我在远处看到的日志文件有很好的计数,但通过搜索这些文件,我可以看到,在time_数据行之前,前面的实体的长度或结尾不相同。如果根据操作系统运行grep-F-c string file或find/c string file,您是否看到任何加速?试图确定CPU使用率是这里的问题还是I/O。Grep和find是CPU使用率的下限。日志文件是什么样子的?如果您能在文件中找到时间\数据的时间/地点中找到一个模式,可能会有所帮助。@JayMody肯定有一个模式。这会有什么帮助呢?这取决于格式/模式,但一个简单的例子是时间数据只出现在奇数行上,这会将搜索减少一半。很难说它有什么帮助,这完全取决于文件的实际外观。@JayMody我刚检查过,虽然有一个模式,但它是不规则的,因为这些日志来自工厂,有时数据不完整,取决于可用的项目的种类。这使得预测它将在何处发生变得奇怪甚至困难。事实上,这就是我正在编写的测试的要点,以便能够捕捉到时间数据不完整的情况。虽然我在远处看到的日志文件有很好的计数,但通过搜索这些文件,我可以看到,在time_数据行之前,前面的实体的长度或结尾不相同。如果根据操作系统运行grep-F-c string file或find/c string file,您是否看到任何加速?试图确定CPU使用率是这里的问题还是I/O的问题。Grep和find是CPU使用率的下限。有趣的是,看看这篇文章的第一篇:他说,记住这是一个高级编程咒语,需要小心使用。映射到的文件与磁盘上显示的完全相同。在Windows上处理与Linux和Mac不同的行尾、Unicode字符编码等取决于您。您还必须小心在内存中映射字符串,因为这很容易导致内存使用量激增。想法@我的答案中的示例代码以二进制模式处理文件,因此不存在行尾问题。由于您的目标字符串完全是ASCII,所以即使日志文件中包含ASCII以外的UTF-8字符,您也不会遇到编码问题。因此,因为我们处理的是二进制,所以我们不会担心abotu编码格式和新行?像他警告的那样消耗太多的记忆怎么样@你为什么不试试呢?你不需要写一篇文章
很多代码。我打算。工作了12个小时后,我的大脑有点紧张,所以我想在明天早上实施之前提前问一下。我想在我还让你在线的时候也能得到同样多的建议:DinInteresting,但看看这篇quora上的第一篇帖子:他说,记住这是一个高级编程咒语,需要小心使用。映射到的文件与磁盘上显示的完全相同。在Windows上处理与Linux和Mac不同的行尾、Unicode字符编码等取决于您。您还必须小心在内存中映射字符串,因为这很容易导致内存使用量激增。想法@我的答案中的示例代码以二进制模式处理文件,因此不存在行尾问题。由于您的目标字符串完全是ASCII,所以即使日志文件中包含ASCII以外的UTF-8字符,您也不会遇到编码问题。因此,因为我们处理的是二进制,所以我们不会担心abotu编码格式和新行?像他警告的那样消耗太多的记忆怎么样@你为什么不试试呢?你不需要写太多的代码,我打算写。工作了12个小时后,我的大脑有点紧张,所以我想在明天早上实施之前提前问一下。我想在我还让你在线的时候也能得到同样多的建议:DComments不是用来进行长时间讨论的;此对话已结束。评论不用于扩展讨论;这个对话已经结束了。所以这个方法实际上是最快的。我不知道;我不知道为什么。我想使用它,但我需要了解发生了什么。我不理解这个语法:overlap=last[-windowsize:][memcap[:windowsize]的意思,我也不理解下面的其他逻辑。但我不知道你做了什么魔法。不管是什么,这是迄今为止最快的方法。甚至比内存映射更快,所以这个方法实际上是最快的。我不知道;我不知道为什么。我想使用它,但我需要了解发生了什么。我不理解这个语法:overlap=last[-windowsize:][memcap[:windowsize]的意思,我也不理解下面的其他逻辑。但我不知道你做了什么魔法。不管是什么,这是迄今为止最快的方法。甚至比内存映射示例更快
import mmap
import sys

with open(sys.argv[1], 'rb') as f:
    mm = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
    target = b'TIME_DATA'
    tl = len(target)
    idx = -tl
    counter = 0
    while True:
        idx = mm.find(target, idx + tl)
        if idx < 0:
            break
        counter += 1
    print(counter)
    mm.close()

target = b"TIME_DATA"
windowsize = len(target) - 1
last = b""
target_count = 0
with open(filename, "rb") as f:
    while True:
        memcap = f.read(102400)
        if not memcap:
            break
        overlap = last[-windowsize :] + memcap[: windowsize]
        if target in overlap:
            target_count += 1
        target_count += memcap.count(target)
        last = memcap