Измерение времени работы скриптов 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 делает это автоматически.