为什么Python操作是30×;调用time.sleep或subprocess.Popen后速度变慢?

为什么Python操作是30×;调用time.sleep或subprocess.Popen后速度变慢?,python,performance,subprocess,python-performance,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) 当“命令1”在“命令2”之前运行时,“命令2”命令系统性地运行时间更长。下图显示了1+1的执行时间,作为循环索引i的函

考虑以下循环:

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)
当“命令1”在“命令2”之前运行时,“命令2”命令系统性地运行时间更长。下图显示了
1+1
的执行时间,作为循环索引
i
的函数,平均运行100次

前面有
子进程.Popen
时,执行
1+1
的速度要慢30倍


更奇怪的是。有人可能认为只有在
subprocess.Popen()
之后运行的第一个命令会受到影响,但事实并非如此。以下循环显示当前循环迭代中的所有命令都受到影响。但随后的循环迭代似乎基本上是正常的

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)
下面是此循环的执行时间曲线图,平均超过100次运行:


更多备注:

  • 在使用<代码> >时间>睡眠()>代码>或C++绑定初始化(<代码> Labo.Buang.SabangWar()/代码>)时,我们得到了相同的效果。但是,使用其他的带有C++绑定的库,例如OpenCV的代码> CV2..WalPAffIn()/<代码>不会影响执行时间。打开文件也不起作用
  • 这种影响不是由
    time.time()
    引起的,因为它可以通过
    timeit.timeit()
    看到,甚至可以在出现
    print()
    结果时手动测量
  • 在没有for循环的情况下也会发生这种情况
  • 即使在“command 1”(
    subprocess.Popen
    )和“command 2”之间执行了大量不同的(可能是CPU和内存消耗)操作,也会发生这种情况
  • 对于Numpy阵列,速度减慢似乎与阵列的大小成正比。具有相对较大的阵列(~60 M点),一个简单的
    arr+=1
    操作可能需要300毫秒

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


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

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

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

在调用
Popen()
后,我得到~5倍的
LIBPERF\u COUNT\u HW\u CACHE\u MISSES
计数器:

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()                                                
给我:

 2605,  2185,  2127,  2099,  2407,  2120,
5481210,
16499, 10694, 10398, 10301, 10206, 10166

(在非标准位置断开表示代码流的行)

为了记录在案,我已经能够用Python 3.7重现这一点,使用
time.perf_counter_ns()
而不是
time.time()
,使用
1+1
甚至在
time.perf_counter_ns()的计数器之间什么都不做
…为了确保我理解,你的意思是因为新进程接管了处理器的缓存,对吗?(当然,粗略地说。)@jpmc26有点……做任何事情都会给缓存带来压力,你做的工作越多,移动的东西就越多。这很可能会导致旧的东西从缓存中被逐出,当这些旧的东西再次被需要时,它们需要重新填充。启动一个进程需要消耗几MB的RAM,创建一个大的
numpy
阵列等等。