Измерение времени работы скриптов Python: инструменты и рекомендации

Раздел: Производительность -> Измерение времени

Измерение времени выполнения кода Python

Наиболее точным и удобным решением для замера времени выполнения небольших участков кода является использование модуля timeit. Он автоматически учитывает фоновые процессы, отключает сборщик мусора и выполняет код многократно, что даёт статистически значимый результат.

Пример использования timeit.timeit():

import timeit

code_to_test = """
sum(range(100))
"""
time = timeit.timeit(code_to_test, number=10000)
print(f"Среднее время выполнения: {time/10000:.6f} сек")
    

Python время выполнения кода (время выполнения кода python)

Среднее время выполнения: 0.000023 сек

Параметр number задаёт количество повторений. Чем больше повторений, тем точнее результат, но дольше измерение. Рекомендуется выбирать такое значение, чтобы общее время замера было не менее 0.2 секунды.

Как измерить время выполнения с помощью time.perf_counter()?

Функция time.perf_counter() возвращает максимально точное монотонное время (в секундах) для измерения интервалов. Она не зависит от системных часов, поэтому подходит для однократных замеров.

import time

start = time.perf_counter()
result = sum(range(1000000))
end = time.perf_counter()
print(f"Время выполнения: {end - start:.6f} сек")
    
Время выполнения: 0.043125 сек

Типичная ошибка: использование time.time() для коротких интервалов. time.time() может быть скорректирована системными часами, что даёт отрицательные или неточные результаты.

Решение: всегда использовать time.perf_counter() для измерения интервалов.

Как измерить время выполнения функции с помощью декоратора?

Декоратор позволяет легко добавлять замеры к любым функциям без изменения их кода.

import time
from functools import wraps

def timer_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        print(f"Функция '{func.__name__}' выполнена за {end - start:.6f} сек")
        return result
    return wrapper

@timer_decorator
def slow_function():
    time.sleep(0.1)

slow_function()
    
Функция 'slow_function' выполнена за 0.100023 сек

Как замерить время выполнения фрагмента кода с помощью time.process_time()?

time.process_time() возвращает сумму времени пользовательского и системного процессора, исключая ожидание ввода/вывода. Полезно для анализа вычислительной сложности.

import time

start = time.process_time()
for _ in range(1000000):
    _ = 2**60
end = time.process_time()
print(f"Затрачено процессорного времени: {end - start:.6f} сек")
    
Затрачено процессорного времени: 0.312500 сек

Проблема: process_time не учитывает время ожидания, так что если код содержит блокировки или сон, измерение будет занижено.

Как профилировать программу с помощью cProfile?

Для анализа производительности целых программ или длительных вызовов используется модуль cProfile. Он показывает количество вызовов и время в каждой функции.

import cProfile

def expensive():
    sum(range(100000))

def main():
    for _ in range(10):
        expensive()

cProfile.run('main()', sort='time')
    
         22 function calls in 0.279 seconds
   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       10    0.279    0.028    0.279    0.028 <stdin>:1(expensive)
        1    0.000    0.000    0.279    0.279 <stdin>:1(main)
        ...

Как измерить время с помощью datetime.datetime.now()?

Этот метод подходит только для грубых замеров (с точностью до миллисекунд) и не рекомендуется для высокоточных измерений из-за возможных скачков системного времени.

from datetime import datetime

start = datetime.now()
result = [x*x for x in range(1000000)]
end = datetime.now()
print(f"Время: {(end - start).total_seconds():.4f} сек")
    
Время: 0.0890 сек

Типичная ошибка: использование datetime.now() до и после вызова может дать отрицательную разницу, если время было скорректировано.

Решение: применять time.perf_counter() или timeit для точных замеров.

Расширенные примеры измерения времени

Пример 1. Многократные замеры с timeit.repeat() для анализа вариабельности

Пример
import timeit

code = "sum(range(100))"
times = timeit.repeat(code, repeat=5, number=10000)
print("5 замеров по 10000 повторений:")
for i, t in enumerate(times, 1):
    print(f"  Замер {i}: {t/10000:.8f} сек на вызов")
print(f"Лучшее: {min(times)/10000:.8f}")
print(f"Худшее: {max(times)/10000:.8f}")
5 замеров по 10000 повторений:
  Замер 1: 0.00002345 сек на вызов
  Замер 2: 0.00002298 сек на вызов
  Замер 3: 0.00002401 сек на вызов
  Замер 4: 0.00002311 сек на вызов
  Замер 5: 0.00002287 сек на вызов
Лучшее: 0.00002287
Худшее: 0.00002401

Пример 2. Декоратор с сохранением статистики

Пример
import time
from functools import wraps

class TimerStats:
    def __init__(self):
        self.times = []

    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time.perf_counter()
            result = func(*args, **kwargs)
            elapsed = time.perf_counter() - start
            self.times.append(elapsed)
            return result
        wrapper.timer_stats = self
        return wrapper

@TimerStats()
def compute(n):
    return [i**2 for i in range(n)]

for n in [1000, 10000, 100000]:
    compute(n)

print("Собранные времена:", compute.timer_stats.times)
print("Среднее:", sum(compute.timer_stats.times)/len(compute.timer_stats.times))
Собранные времена: [0.000305, 0.001890, 0.017234]
Среднее: 0.006476

Пример 3. Сравнение двух реализаций с помощью timeit

Пример
import timeit

setup = "import math"
code_list = "[math.sqrt(x) for x in range(1000)]"
code_map = "list(map(math.sqrt, range(1000)))"

t_list = timeit.timeit(code_list, setup, number=10000)
t_map = timeit.timeit(code_map, setup, number=10000)

print(f"List comprehension: {t_list:.4f} сек")
print(f"map + list:         {t_map:.4f} сек")
print(f"Разница в {t_list/t_map:.2f} раза")
List comprehension: 0.1234 сек
map + list:         0.0987 сек
Разница в 1.25 раза

Пример 4. Профилирование вызовов с помощью line_profiler (внешняя библиотека)

Пример
# Установка: pip install line_profiler
# Создаём файл script.py:
def heavy():
    total = 0
    for i in range(100000):
        total += i*i
    return total

# Запуск: kernprof -l -v script.py
# Результат будет выведен в консоль с пошаговым временем
Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     1                                           def heavy():
     2         1          2.0      2.0      0.0      total = 0
     3    100001     145000.0      1.4     99.6      for i in range(100000):
     4    100000        600.0      0.0      0.4          total += i*i
     5         1          1.0      1.0      0.0      return total

Пример 5. Измерение времени с отключением сборщика мусора

Пример
import gc
import time

gc.disable()  # отключаем сборку мусора для чистоты замера
start = time.perf_counter()
data = [i for i in range(100000)]
end = time.perf_counter()
gc.enable()

print(f"Время без GC: {end - start:.6f} сек")
Время без GC: 0.005234 сек

Важно: после отключения сборщика мусора нужно не забыть включить его обратно, иначе в долгоживущих программах возможна утечка памяти. Обычно модуль timeit делает это автоматически.

Время выполнения кода Python - comments

En
Python время выполнения кода (python)