Рассмотрим следующий цикл:
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)
Команда "команда 2" выполняется систематически дольше, когда "команда 1" выполняется перед ней. На следующем графике показано время выполнения 1+1
в зависимости от индекса i
цикла, усредненного по 100 циклам.
Выполнение 1+1
в 30 раз медленнее, когда ему предшествует subprocess.Popen
. subprocess.Popen
.
Это становится еще более странным. Можно подумать, что затрагивается только первая команда, выполняемая после 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 запусков:
Больше замечаний:
- Мы получаем тот же эффект при замене
subprocess.Popen()
("command 1") наtime.sleep()
или rawkits libraw C++ для инициализации привязок (libraw.bindings.LibRaw()
). Однако использование других библиотек с привязками C++, таких как libraw.py или OpenCVscv2.warpAffine()
, не влияет на время выполнения. Открытие файлов тоже не происходит. - Эффект не вызван
time.time()
, потому что он виден с помощьюtimeit.timeit()
и даже измерением вручную, когда появляется результатprint()
. - Это также происходит без цикла for.
- Это происходит даже тогда, когда между "командой 1" (
subprocess.Popen
) и "командой 2" выполняется много разных (возможно, CPU- и занимающих много памяти) операций. - С массивами Numpy замедление оказывается пропорциональным размеру массива. При относительно больших массивах (~ 60 M точек) простая операция
arr += 1
может занять до 300 мс!
Вопрос: Что может вызвать этот эффект и почему он влияет только на текущую итерацию цикла?
Я подозреваю, что это может быть связано с переключением контекста, но это, похоже, не объясняет, почему повлияла бы итерация всего цикла. Если переключение контекста действительно является причиной, почему некоторые команды запускают его, а другие нет?