Why compose multiple generator shows different time for same process

Viewed 97

I have a child function that will iterate over 1000000 times. Now when I call that child function on two different functions. It's showing different time is taken for every execution. Why this is happening?

import timeit

def child():
    for i in range(1_000_000):
        yield i

def slow():
    yield from child()

def fast():
    yield from child()

baseline = timeit.timeit(
    stmt='for _ in slow(): pass',
    globals=globals(),
    number=50
)
comparision = timeit.timeit(
    stmt='for _ in fast(): pass',
    globals=globals(),
    number=50
)
print(f'Manual nesting {baseline:.2f}s')
print(f'Composed nesting {comparision:.2f}s')

Output

# First execution
Manual nesting 9.94s
Composed nesting 9.35s

# Second execution
Manual nesting 9.20s
Composed nesting 9.77s

As you have already seen the above output. It's showing different time intervals. Why does it happen for the same process?

1 Answers

Any Python function will take different amount of time on each run, close to each other but a bit different. There are many reasons:

  1. Your operating system (Windows, Linux, etc...) has many processes and threads. Each process and thread is scheduled to be run only for a small amount of continuous time, usually several milliseconds, then process/thread switches to another one. It means that your execution process may suspend in the middle, then continue after some time when operating system reschedules it. So running program is not continuous process.
  2. Evey modern CPU executes each instruction also at different amount of time each time. There are different prediction algorithms and caching algorithms and out of order execution and look ahead buffers, etc. All of these result in slightly different time of execution of each small instruction.
  3. CPU cores may change their running speed, frequency. Sometimes they slow down (when overheated) or boost for short time (on turbo frequency). This means on each run CPU core speed may change at random points of time.
  4. Also when each run of function is done Python has a bit different execution environment. For example memory manager may have a different list of free memory chunks (another length, other sizes of chunks, etc), so allocating each Python object will take different amount of time for each single Python instruction that does memory allocation, and Python does a lot.
  5. Also Python may run extra service threads in background, they will also occupy different part of execution time of your main process. Because of GIL, which blocks all other threads while any single Python thread is running.
  6. Also if your function is not deterministic on inputs (and your function is deterministic) then it may run different amount of time on each run because it basically executes different sequence of opcodes.

All these reasons and many more make whole Python execution system unpredictable time-wise, meaning that each run of function will have slightly (and sometimes even huge) difference in time.

Even not only for Python but for any program running on computer most of these reasons are applicable and any program will vary in speed on each execution. I suppose there might exist some special programming languages that may synchronize execution of each instruction/opcode to some high-resolution clock, for example on Mars landing spacecraft execution of each function may be needed to be exact up to nanoseconds. Then for programs written in such languages timings will be almost precise. But not for Python, it doesn't do any time-synchronization when running opcodes.

Related