【问题标题】:Negative process execution time in Python. How do I correctly measure this?Python 中的负进程执行时间。我如何正确测量这个?
【发布时间】:2021-02-07 07:02:51
【问题描述】:

所以我正在做一个任务,我需要编写两个使用线程\多处理的程序。虽然它的线程部分很顺利,但我在测量进程的执行时间时遇到了一些麻烦。代码如下:

import multiprocessing as mp
import time 

start = time.perf_counter()
txtsmpl = open('C:\\dev\\OS\\dummy.txt', 'r').read()
processes_num = 5
to_replace = 'az'
replace_with = '{[]}'


def process_txt(start_time, inp_text):
    txt = list(inp_text)
    for i, letter in enumerate(txt):
        if letter in to_replace:
            txt[i] = replace_with
    txt = ''.join(txt)
    print(txt + '\n')
    print('Ran for ' + str(round(time.perf_counter() - start_time, 4)) + ' second(s)...\n')

def main():
    processes = []
    for _ in range(processes_num):
        p = mp.Process(target=process_txt, args = [time.perf_counter(), txtsmpl])
        p.start()
        processes.append(p)

    for process in processes:
        process.join()

    finish = time.perf_counter()
    print(f'Finished in {round(finish-start, 4)} second(s)')

if __name__ == "__main__":
    main()

这是测量它的推荐方法(将开始时间作为参数传递给线程\进程)。但是对于流程,我会得到负的执行时间。当然,我理解当它们都并行运行时会发生这种情况,但我不知道如何防止它。感谢您的帮助!

【问题讨论】:

    标签: python python-3.x time multiprocessing python-multiprocessing


    【解决方案1】:

    如果你想分析 python 代码,你可以使用 cProfile。

    import cProfile
    
    def _a():
        a = 1
        while a < 1000:
            a+= 1
    
    def _b():
        a = 1
        while a < 100:
            a+= 1
    
    cProfile.run("_a()")
    cProfile.run("_b()")
    

    输出:

             4 function calls in 0.000 seconds
    
       Ordered by: standard name
    
       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
            1    0.000    0.000    0.000    0.000 <string>:1(<module>)
            1    0.000    0.000    0.000    0.000 __init__.py:4(_a)
            1    0.000    0.000    0.000    0.000 {built-in method builtins.exec}
            1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
    
    
             4 function calls in 0.000 seconds
    
       Ordered by: standard name
    
       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
            1    0.000    0.000    0.000    0.000 <string>:1(<module>)
            1    0.000    0.000    0.000    0.000 __init__.py:9(_b)
            1    0.000    0.000    0.000    0.000 {built-in method builtins.exec}
            1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
    
    
    
    Process finished with exit code 0
    

    【讨论】:

      【解决方案2】:

      首先,当您在 Windows 下运行时,您注意到您必须保护创建子进程的代码,方法是确保它在由if __name__ == '__main__': 控制的块内运行,正如您所做的那样。原因是当子流程执行时,整个文件会重新执行,您将进入创建新子流程的无限递归循环。但这里的重点是,您的代码if __name__ == '__main__': 块控制,而您确实不需要需要也不想被每个子进程执行,即打开和读取文件。这段代码应该被移动。

      现在揭开你的神秘面纱。根据time.perf_counter()上的手册:

      返回性能计数器的值(以秒为单位),即具有最高可用分辨率的时钟以测量短持续时间。它确实包括睡眠期间经过的时间,并且是系统范围的。返回值的参考点是未定义的,因此只有连续调用的结果之间的差异才有效。

      注意上面的最后一句话。来自多个进程的并行调用。您认为在计算经过时间时为开始时间和结束时间分配值的此函数的调用是连续调用,但不能保证它们是连续调用。你真的只需要使用time.time()

      import multiprocessing as mp
      import time
      
      to_replace = 'az'
      replace_with = '{[]}'
      
      
      def process_txt(start_time, inp_text):
          txt = list(inp_text)
          for i, letter in enumerate(txt):
              if letter in to_replace:
                  txt[i] = replace_with
          txt = ''.join(txt)
          print(txt + '\n')
          print('Ran for ' + str(round(time.time() - start_time, 4)) + ' second(s)...\n')
      
      def main():
          start = time.time()
          txtsmpl = open('C:\\dev\\OS\\dummy.txt', 'r').read()
          processes_num = 5
          processes = []
          for _ in range(processes_num):
              p = mp.Process(target=process_txt, args = [time.time(), txtsmpl])
              p.start()
              processes.append(p)
      
          for process in processes:
              process.join()
      
          finish = time.time()
          print(f'Finished in {round(finish-start, 4)} second(s)')
      
      if __name__ == "__main__":
          main()
      

      更新

      为了更好地了解每个子流程处理文本所需的时间,process_txt 应该使用自己对 time.time() 的调用来初始化 start_text,并更好地了解您需要多少时间正在通过使用子流程进行保存,请将分配 start = time.start_time() 移动到您创建子流程之前的主流程中:

      import multiprocessing as mp
      import time
      
      to_replace = 'az'
      replace_with = '{[]}'
      
      
      def process_txt(inp_text):
          start_time = time.time()
          txt = list(inp_text)
          for i, letter in enumerate(txt):
              if letter in to_replace:
                  txt[i] = replace_with
          txt = ''.join(txt)
          print(txt + '\n')
          print('Ran for ' + str(round(time.time() - start_time, 4)) + ' second(s)...\n')
      
      def main():
          txtsmpl = open('C:\\dev\\OS\\dummy.txt', 'r').read()
          processes_num = 5
          processes = []
          start = time.time()
          for _ in range(processes_num):
              p = mp.Process(target=process_txt, args = [txtsmpl])
              p.start()
              processes.append(p)
      
          for process in processes:
              process.join()
      
          finish = time.time()
          print(f'Finished in {round(finish-start, 4)} second(s)')
      
      if __name__ == "__main__":
          main()
      

      您可能会发现为此类短期运行任务创建子流程的开销远远超过使用并行性的任何好处。只需在循环中调用process_txt 5 次而不创建子进程,这段代码就会运行得更快。

      【讨论】:

      • 这正是我需要学习的,非常感谢!
      • 现在研究使用带有来自concurrent.futures 模块的ProcessPoolExecutor 类或来自multiprocessing 模块的Pool 类的进程池来执行此操作。
      • 是的,谢谢!我知道在这里使用流程并不是最佳选择,但这只是让学生熟悉线程和流程的大学作业,我将尝试超过 5 次迭代。感谢分享这一切,我学到了很多:)
      猜你喜欢
      • 2019-09-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-31
      • 2020-11-28
      • 2011-11-14
      • 2021-05-05
      • 1970-01-01
      相关资源
      最近更新 更多