调用time.sleep或subprocess.Popen后,为什么Python操作会慢30倍?

Arc*_*s B 15 python performance subprocess python-performance

考虑以下循环:

for i in range(20):
    if i == 10:
        subprocess.Popen(["echo"]) # command 1
    t_start = time.time()
    1+1 # command 2
    t_stop = time.time()
    print(t_stop - t_start)
Run Code Online (Sandbox Code Playgroud)

当"命令1"在其之前运行时,"命令2"命令系统地运行更长时间.下图显示了1+1作为循环索引函数的执行时间i,平均超过100次运行.

执行1+1时比之前慢30倍subprocess.Popen.

作为循环索引的函数的<code>subprocess.Popen()</code>受到影响,但事实并非如此.以下循环显示<strong>当前循环迭代中的所有命令都受到影响</strong>.但随后的循环迭代似乎基本上没问题.</p>

<pre><code>var = 0
for i in range(20):
    if i == 10:
      # command 1
      subprocess.Popen(['echo'])
    # command 2a
    t_start = time.time()
    1 + 1
    t_stop = time.time()
    print(t_stop - t_start)
    # command 2b
    t_start = time.time()
    print(1)
    t_stop = time.time()
    print(t_stop - t_start)
    # command 2c
    t_start = time.time()
    var += 1
    t_stop = time.time()
    print(t_stop - t_start)
</code></pre><a target=Run Code Online (Sandbox Code Playgroud)

以下是此循环的执行时间图,平均超过100次运行:

作为循环索引的函数的<code>subprocess.Popen()</code>(time.sleep()或使用rawkit的libraw C++绑定初始化(libraw.bindings.LibRaw())时,我们获得相同的效果.但是,使用其他带有C++绑定的库(如libraw.py或OpenCV)cv2.warpAffine()不会影响执行时间.打开文件也不是.

  • 效果不是time.time()由于它是可见的timeit.timeit(),甚至是在print()结果出现时手动测量.
  • 它也没有for循环.
  • 即使在"命令1"(subprocess.Popen)和"命令2" 之间执行许多不同的(可能是CPU和内存消耗)操作时,也会发生这种情况.
  • 对于Numpy数组,减速似乎与数组的大小成比例.使用相对较大的阵列(约60 M点),简单的arr += 1操作可能需要300 ms!

  • 问题:什么可能导致这种影响,为什么它只影响当前的循环迭代?

    我怀疑它可能与上下文切换有关,但这似乎并不能解释为什么整个循环迭代会受到影响.如果上下文切换确实是原因,为什么有些命令会触发它而其他命令却没有?

    Sam*_*son 8

    我的猜测是,这是由于Python代码从CPU /内存系统中的各种缓存中逐出

    perflib程序包可用于提取有关缓存状态的更详细的CPU级别统计信息 - 即命中/未命中数.

    电话LIBPERF_COUNT_HW_CACHE_MISSES结束后,我得到了5倍的柜台 Popen():

    from subprocess import Popen, DEVNULL
    from perflib import PerfCounter
    import numpy as np
    
    arr = []
    p = PerfCounter('LIBPERF_COUNT_HW_CACHE_MISSES')                                                        
    
    for i in range(100):
      ti = []
      p.reset()
      p.start()
      ti.extend(p.getval() for _ in range(7))
      Popen(['echo'], stdout=DEVNULL)
      ti.extend(p.getval() for _ in range(7))
      p.stop()
      arr.append(ti)
    
    
    np.diff(np.array(arr), axis=1).mean(axis=0).astype(int).tolist()                                                
    
    Run Code Online (Sandbox Code Playgroud)

    给我:

     2605,  2185,  2127,  2099,  2407,  2120,
    5481210,
    16499, 10694, 10398, 10301, 10206, 10166
    
    Run Code Online (Sandbox Code Playgroud)

    (在非标准位置打破的行以指示代码流)