Python 使用内存分析器分析代码以增加执行时间

Python 使用内存分析器分析代码以增加执行时间,python,memory-profiling,Python,Memory Profiling,我正在编写一个简单的应用程序,它将一个大的文本文件拆分成更小的文件,我已经编写了它的两个版本,一个使用列表,另一个使用生成器。我使用memory_profiler模块分析了这两个版本,它清楚地显示了generators版本更好的内存效率,但奇怪的是,当分析使用Generator的版本时,它增加了执行时间。下面的演示解释了我的意思 使用列表的版本 from memory_profiler import profile @profile() def main(): file_name =

我正在编写一个简单的应用程序,它将一个大的文本文件拆分成更小的文件,我已经编写了它的两个版本,一个使用列表,另一个使用生成器。我使用memory_profiler模块分析了这两个版本,它清楚地显示了generators版本更好的内存效率,但奇怪的是,当分析使用Generator的版本时,它增加了执行时间。下面的演示解释了我的意思

使用列表的版本

from memory_profiler import profile


@profile()
def main():
    file_name = input("Enter the full path of file you want to split into smaller inputFiles: ")
    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: "))
    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()
使用探查器运行时

$ time py36 Splitting\ text\ files_BAD_usingLists.py                                                                                                               

Enter the full path of file you want to split into smaller inputFiles: /apps/nttech/rbhanot/Downloads/test.txt
Enter the number of parts you want to split in: 3
Filename: Splitting text files_BAD_usingLists.py

Line #    Mem usage    Increment   Line Contents
================================================
     6     47.8 MiB      0.0 MiB   @profile()
     7                             def main():
     8     47.8 MiB      0.0 MiB       file_name = input("Enter the full path of file you want to split into smaller inputFiles: ")
     9    107.3 MiB     59.5 MiB       input_file = open(file_name).readlines()
    10    107.3 MiB      0.0 MiB       num_lines_orig = len(input_file)
    11    107.3 MiB      0.0 MiB       parts = int(input("Enter the number of parts you want to split in: "))
    12    107.3 MiB      0.0 MiB       output_files = [(file_name + str(i)) for i in range(1, parts + 1)]
    13    107.3 MiB      0.0 MiB       st = 0
    14    107.3 MiB      0.0 MiB       p = int(num_lines_orig / parts)
    15    107.3 MiB      0.0 MiB       ed = p
    16    108.1 MiB      0.7 MiB       for i in range(parts-1):
    17    107.6 MiB     -0.5 MiB           with open(output_files[i], "w") as OF:
    18    108.1 MiB      0.5 MiB               OF.writelines(input_file[st:ed])
    19    108.1 MiB      0.0 MiB           st = ed
    20    108.1 MiB      0.0 MiB           ed = st + p
    21                             
    22    108.1 MiB      0.0 MiB       with open(output_files[-1], "w") as OF:
    23    108.1 MiB      0.0 MiB           OF.writelines(input_file[st:])



real    0m6.115s
user    0m0.764s
sys     0m0.052s
在没有分析器的情况下运行时

$ time py36 Splitting\ text\ files_BAD_usingLists.py 
Enter the full path of file you want to split into smaller inputFiles: /apps/nttech/rbhanot/Downloads/test.txt
Enter the number of parts you want to split in: 3

real    0m5.916s
user    0m0.696s
sys     0m0.080s
现在是使用发电机的那个

@profile()
def main():
    file_name = input("Enter the full path of file you want to split into smaller inputFiles: ")
    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: "))
    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()

那么,为什么分析首先会让我的代码变慢呢?其次,如果at评测影响了执行速度,那么为什么这个效果没有显示在使用列表的代码版本上。

我cpu使用line\u profiler评测了代码,这次我得到了答案,generator版本花费更多时间的原因是因为下面的行

19         2      11126.0   5563.0      0.2          with open(file, "w") as OF:
    20    379886     200418.0      0.5      3.0              for _ in range(st, ed):
    21    379884    2348653.0      6.2     35.1                  OF.writelines(input_file.readline())
而对于列表版本来说,它之所以没有减慢速度,是因为

   19         2       9419.0   4709.5      0.4          with open(output_files[i], "w") as OF:
    20         2    1654165.0 827082.5     65.1              OF.writelines(input_file[st:ed])

对于列表,只需通过切片获取列表的副本就可以编写新文件,这实际上是一条语句。但是,对于generators版本,通过逐行读取输入文件来填充新文件,这使得每行的内存探查器配置文件都增加了cpu时间。

任何时候添加探查器(内存或执行)都会增加开销。特定于内存的探查器很可能是在没有考虑执行速度的情况下编写的,而是专注于最准确地计算内存使用量。在使用专门为测量计算速度而设计的低开销探查器时,不能考虑速度。即使使用低开销的探查器,您的程序也永远不会像没有它时运行得那么快,因为您仍在要求处理器执行更多指令。我理解这一点,但问题是为什么它只影响程序的生成器版本,而不影响使用列表的生成器版本。您必须深入研究源代码才能获得准确答案,但是生成器版本可能会导致内存计数更频繁地触发(许多较小的分配,而不是单个较大的分配)。实际上,我有点好奇,您是否可以堆叠探查器并运行cpu时间配置文件,以查看
内存\u探查器的哪个部分。配置文件
使用生成器函数需要更多的时间(你会看到它被调用了多少次,平均需要多长时间才能返回)我已经通过将line_profiler的profile decorator堆叠在memory_profiler的profile decorator上实现了这一点。但是无法获得非常描述性的信息,我会再试一次
19         2      11126.0   5563.0      0.2          with open(file, "w") as OF:
    20    379886     200418.0      0.5      3.0              for _ in range(st, ed):
    21    379884    2348653.0      6.2     35.1                  OF.writelines(input_file.readline())
   19         2       9419.0   4709.5      0.4          with open(output_files[i], "w") as OF:
    20         2    1654165.0 827082.5     65.1              OF.writelines(input_file[st:ed])