优化生成器以获得更好的执行时间

时间:2018-06-12 11:52:47

标签: python

我有以下代码,我将大文本文件拆分为较小的文件,我使用生成器迭代文件,然后处理它。与我编写的列表版本相比,它具有高内存效率,但它在执行速度方面受到严重影响。下面是我的代码,我已经弄清楚为什么需要更多时间,但我没有办法优化它。

def main():
    # file_name = input("Enter the full path of file you want to split into smaller inputFiles: ")
    file_name = "/rbhanot/Downloads/newtest.txt"
    input_file = open(file_name)
    num_lines_orig = sum(1 for _ in input_file)
    input_file.seek(0)
    # parts = int(input("Enter the number of parts you want to split in: "))
    parts = 3
    output_files = ((file_name + str(i)) for i in range(1, parts + 1))
    st = 0
    p = int(num_lines_orig / parts)
    ed = p
    for i in range(parts - 1):
        file = next(output_files)
        with open(file, "w") as OF:
            for _ in range(st, ed):
                OF.writelines(input_file.readline())

            st = ed
            ed = st + p
            if num_lines_orig - ed < p:
                ed = st + (num_lines_orig - ed) + p
            else:
                ed = st + p

    file = next(output_files)
    with open(file, "w") as OF:
        for _ in range(st, ed):
            OF.writelines(input_file.readline())


if __name__ == "__main__":
    main()

大部分时间位于其遍历范围的部分,然后有两个函数调用来读取和写入文件。

    for _ in range(st, ed):
        OF.writelines(input_file.readline())

这是使用列表的相同代码的另一个版本,显然这可以更快地运行

def main():
    # file_name = input("Enter the full path of file you want to split into smaller inputFiles: ")
    file_name = "/rbhanot/Downloads/newtest.txt"
    input_file = open(file_name).readlines()
    num_lines_orig = len(input_file)
    # parts = int(input("Enter the number of parts you want to split in: "))
    parts = 3
    output_files = [(file_name + str(i)) for i in range(1, parts + 1)]
    st = 0
    p = int(num_lines_orig / parts)
    ed = p
    for i in range(parts - 1):
        with open(output_files[i], "w") as OF:
            OF.writelines(input_file[st:ed])
        st = ed
        ed = st + p

    with open(output_files[-1], "w") as OF:
        OF.writelines(input_file[st:])


if __name__ == "__main__":
    main()

我知道如果我将此代码设置为多线程,我可以将执行速度提高一些,因为这里的大部分内容都是IO,但我想知道是否有其他方法可以在不修改代码的情况下执行相同操作。

感谢。

1 个答案:

答案 0 :(得分:0)

您最大的瓶颈是文件I / O.读取和写入磁盘是

然而,通过将单行传递给file.writelines()方法,您会使事情变得更糟。后者期望可迭代的行(实现有效地迭代并为每个元素调用file.write())。由于字符串是一个可迭代的,也可以为您提供单个字符,因此您实际上是将单个字符写入文件缓冲区。与文件I / O相比, 的速度慢,但它也没有效率。不要使用file.writelines()来写一行,只需使用file.write()

接下来,您正在使用重复的file.readline()来电。不要为每一行使用方法调用;您可以在迭代器中使用文件对象,并使用itertools.islice()从中获取一系列行来限制写入的行数。如果将islice()对象传递给file.writelines(),那么该方法将进行迭代:

with open(file, "w") as OF:
    OF.writelines(islice(input_file, p))

以上将p行数写入OF文件对象。请注意,我们无需在此处跟踪所有的开始和结束编号!如果您需要将文件的“剩余”行添加到最后,您只需要读取输入文件的其余部分并将其中的任何内容复制到最后一个输出文件中。只需循环parts次并在循环中创建文件名,即可大大简化代码:

from itertools import islice
from shutil import copyfileobj

parts = int(input("Enter the number of parts you want to split in: "))

file_name = "/rbhanot/Downloads/newtest.txt"
with open(file_name) as input_file:
    num_lines_orig = sum(1 for _ in input_file)
    input_file.seek(0)

    chunk_size = num_lines_orig // parts

    for i in range(parts):
        output_file = f'{file_name}{i + 1}'
        with open(output_file, "w") as OF:
            OF.writelines(islice(input_file, chunk_size))

        if i == parts - 1:   # last iteration
            # copy across any remaining lines
            copyfileobj(input_file, OF)

我使用shutil.copyfileobj() function来处理剩余的复制;它将以块的形式读取和写入文件数据。