Недавно мы решали сложную задачу Эйнштейна с помощью кода на Python, а потом оптимизировали его, чтобы сократить время выполнения. Там всё было просто: с четырёх часов мы оптимизировали время выполнения до долей секунды, и это было явно заметно. Но бывает так, что даже полсекунды оптимизации — это очень хорошо, когда речь идёт о высоконагруженных сервисах. Например, в соцсетях, которыми пользуются сотни тысяч пользователей в минуту. Сегодня мы покажем целых 6 простых способов измерения времени работы кода, которые может использовать каждый.
Как измерять время выполнения кода
В большинстве случаев измерить время работы кода можно так:
- Зафиксировать время начала работы.
- Зафиксировать время окончания работы.
- Вычесть первое значение из второго.
Ещё важно измерять время выполнения кода при одних и тех же условиях:
- конфигурация и мощность компьютера должны совпадать для всех замеров;
- загрузка процессора должна быть одинаковой;
- программа для работы с кодом должна быть одной и той же с одинаковой версией.
Но даже если эти условия выполняются, результаты нескольких замеров могут немного различаться для одного и того же кода. На это могут влиять фоновые процессы, поэтому для точных измерений проводят несколько замеров в чистой среде, когда кроме кода и системных процессов ничего нет.
Если будете заниматься оптимизацией, вот вам на вырост: нужно различать понятия wall time («время на стене») и процессорное время. Первое показывает прошедшее время от начала до конца работы, второе — время, которое процессор затратил на выполнение кода. Их значения могут различаться, если программа ожидает высвобождение ресурсов для выполнения. Но сейчас можно без этих тонкостей.
Модуль datetime
С помощью такого способа можно измерить время выполнения кода в формате часы:минуты:секунды:микросекунды. Мы использовали модуль datetime, когда оптимизировали код для решения загадки Эйнштейна и ускоряли работу программы более чем в 200 тысяч раз.
# подключаем модуль datetime
import datetime
# фиксируем и выводим время старта работы кода
start = datetime.datetime.now()
print('Время старта: ' + str(start))
# код, время работы которого измеряем
#фиксируем и выводим время окончания работы кода
finish = datetime.datetime.now()
print('Время окончания: ' + str(finish))
# вычитаем время старта из времени окончания
print('Время работы: ' + str(finish - start))
Результат — 51 тысячная секунды. Неплохо, но что покажут другие способы?
Модуль time
Модуль time предоставляет разные возможности для измерения времени работы кода:
- time.time() поможет измерить время работы в секундах. Если нужно получить время в минутах, результат вычисления нужно разделить на 60, в миллисекундах — умножить на 1000.
- time.perf_counter() также можно использовать для измерения времени в секундах, но таймер не будет зависеть от системных часов. Функцию используют, чтобы избежать погрешностей. Функция time.perf_counter_ns() вернёт значение в наносекундах.
- time.monotonic() подходит для больших программ, поскольку эта функция не зависит от корректировки времени системы. Функция использует отдельный таймер, как и time.perf_counter(), но имеет более низкое разрешение. С помощью time.monotonic_ns() можно получить результат в наносекундах.
- time.process_time() поможет получить сумму системного и пользовательского процессорного времени в секундах, не включая время сна. Если процесс выполнения блокируется функцией time.sleep() или приостанавливается операционной системой, это время не включается в отчётное. Для наносекунд есть функция time.process_time_ns(), но её поддерживают не все платформы.
- time.thread_time() сообщит время выполнения текущего потока, а не процесса. Если в коде есть функция time.sleep(), время её выполнения не будет включено.
Time.time(). Давайте посчитаем время выполнения нашего кода с помощью функции time.time() в миллисекундах:
# подключаем модуль time
import time
# фиксируем время старта работы кода
start = time.time()
# код, время работы которого измеряем
#фиксируем время окончания работы кода
finish = time.time()
# вычитаем время старта из времени окончания и получаем результат в миллисекундах
res = finish - start
res_msec = res * 1000
print('Время работы в миллисекундах: ', res_msec)
Получаем результат: 61 тысячная секунды. Результат отличается от предыдущего, тут уже нужно было бы хорошо сделать серию тестов и посчитать среднее значение.
Time.perf_counter(). Посчитаем время выполнения нашего кода с помощью функции time.perf_counter() в секундах:
# подключаем модуль time
import time
# фиксируем время старта работы кода
start = time.perf_counter()
# код, время работы которого измеряем
#фиксируем время окончания работы кода
finish = time.perf_counter()
# вычитаем время старта из времени окончания и выводим результат
print('Время работы: ' + str(finish - start))
Мы получили 51 тысячную секунды — почти такой же результат, как и в самый первый раз. Кажется, что это точное время, но посмотрим, что будет дальше.
Time.monotonic_ns(). Посчитаем время выполнения нашего кода с помощью функции time.monotonic_ns() в наносекундах:
# подключаем модуль time
import time
# фиксируем время старта работы кода
start = time.monotonic_ns()
# код, время работы которого измеряем
#фиксируем время окончания работы кода
finish = time.monotonic_ns()
# вычитаем время старта из времени окончания и получаем результат в наносекундах
print('Время работы в наносекундах: ' + str(finish - start))
Результат примерно такой же — 52 тысячные секунды, но количество цифр в результате меньше, чем в предыдущем случае.
Time.process_time(). Посчитаем сумму системного и пользовательского процессорного времени в секундах:
# подключаем модуль time
import time
# фиксируем время старта работы кода
start = time.process_time()
# код, время работы которого измеряем
#фиксируем время окончания работы кода
finish = time.process_time()
# вычитаем время старта из времени окончания и выводим результат
print('Время работы: ' + str(finish - start))
Время работы снова выросло — с 51 до 62 тысячных секунды. Для одних программ такой разброс вообще некритичен, а для других это может означать, что нужно провести больше тестов.
Time.thread_time(). Наконец, посчитаем время выполнения кода с помощью time.thread_time():
# подключаем модуль time
import time
# фиксируем время старта работы кода
start = time.thread_time()
# код, время работы которого измеряем
#фиксируем время окончания работы кода
finish = time.thread_time()
# вычитаем время старта из времени окончания и выводим результат
print('Время работы: ' + str(finish - start))
Время работы снова выглядит правдоподобно в сравнении с предыдущим результатом.
Что дальше
В следующий раз продолжим оптимизировать наш код решения задачи Эйнштейна: отформатируем и избавимся от вложенных данных. Подпишитесь, чтобы не пропустить продолжение.