CProfile и pstats: найдите доминирующую операцию в профиле Python

Напишите решение на Python и проверьте его на странице.

Другие упражнения по этой теме: Профилирование Python: задачи на cProfile, pstats и байт-код dis.

Доминирующая операция профиля

Модуль мониторинга профилирует вызов функции с помощью cProfile.Profile и разбирает собранные замеры в словаре stats.stats объекта pstats.Stats. Чтобы выявить узкие места, анализатор ищет потенциальные листовые операции — записи профиля, у которых кумулятивное время cumtime численно равно собственному времени tottime, включая случаи с нулевыми значениями.

Для каждого кандидата детектор вычисляет отношение его tottime к общему суммарному времени всех вызовов total_tt. Лидер определяется только тогда, когда величина total_tt строго больше нуля, а максимальная найденная доля строго превышает заданный порог threshold. Если наибольшую долю делят сразу несколько операций, предпочтение отдаётся претенденту с меньшим количеством вызовов ncalls, а при повторном равенстве — кандидату с лексикографически меньшим кортежем (filename, line, func_name).

Если подходящих записей не нашлось или общее время total_tt равно нулю, результат считается неопределённым. Выясните, какая листовая операция доминирует в предоставленной статистике pstats.Stats, или убедитесь, что ни один кандидат не удовлетворяет условиям отбора.

Что уже дано

В редакторе объявлена функция dominant_profile_operation(stats, threshold), которую вам предстоит реализовать.

Вспомогательная функция make_profile_case(config) запускает профилирование тестовых функций с управляемым таймером и доступна при выполнении кода; реализовывать генераторы или эмуляторы профилей самостоятельно не требуется.

Что нужно сделать

Реализуйте функцию dominant_profile_operation(stats, threshold).

Функция принимает два аргумента:

  • stats — объект pstats.Stats, содержащий от 1 до 500 записей профиля в словаре stats.stats и атрибут stats.total_tt (неотрицательное конечное число до 1 000 000);
  • threshold — конечное число типа int или float из отрезка [0, 1].

Ключами словаря stats.stats являются стандартные кортежи идентификации (filename, line, func_name), где filename и func_name — строки, а line — целое число. Значениями являются кортежи вида (primitive_calls, total_calls, tottime, cumtime, callers), где total_calls отражает суммарное число вызовов ncalls (целое число от 1 до 10 000), а tottime и cumtime — конечные неотрицательные числа до 1 000 000.

Правила отбора доминирующей операции:

  • Кандидатами признаются только те записи, у которых кумулятивное время численно равно собственному: cumtime == tottime (включая равенство нулю). Наличие или отсутствие реальных дочерних вызовов не проверяется.
  • Анализируйте только переданный объект статистики. Запрещено повторно запускать функции, модифицировать ключи, счётчики или времена в stats.stats.
  • Записи встроенных функций и стандартных функций Python равноправны.
  • Если stats.total_tt == 0, функция должна вернуть None.
  • Доля кандидата вычисляется как tottime / stats.total_tt.
  • Среди всех кандидатов находится максимальная доля. Если она не превышает threshold строго (максимальная доля > threshold), операция считается недоминирующей, и функция возвращает None.
  • Если максимальную долю имеют несколько кандидатов, побеждает кандидат с наименьшим общим значением ncalls (total_calls).
  • При равенстве долей и количества вызовов выбирается кандидат с лексикографически меньшим полным кортежем ключа (filename, line, func_name).
  • При успешном выборе функция возвращает точный кортеж (filename, line, func_name) победившей операции. Если ни одного подходящего кандидата нет, возвращается None.

Ввод и вывод

Стандартные потоки ввода stdin и вывода stdout в решении не используются: считывать ввод и вызывать print() не нужно. После выполнения кода учащегося проверяющая система считывает из пространства имён модуля функцию верхнего уровня dominant_profile_operation и вызывает её с проверочными аргументами.

В приведённом ниже примере профилируются функции с дискретным таймером. Из четырёх зарегистрированных операций равенство cumtime == tottime выполняется только у двух листовых: a_leaf (tottime = 3.0, cumtime = 3.0) и z_leaf (tottime = 1.0, cumtime = 1.0). При общем времени total_tt = 4.0 доля a_leaf составляет 3.0 / 4.0 = 0.75, а доля z_leaf — 1.0 / 4.0 = 0.25. Поскольку максимальная доля 0.75 строго превышает порог 0.4, побеждает операция a_leaf, и функция возвращает полный кортеж её идентификатора.

Пример использования

probe = make_profile_case({
    'z_calls': 1, 'z_cost': 1, 'z_file': 'example_ops.py', 'z_line': 10,
    'a_calls': 1, 'a_cost': 3, 'a_file': 'example_ops.py', 'a_line': 20,
})
stats = probe.stats
for key, row in sorted(stats.stats.items(), key=lambda item: item[0][2]):
    primitive_calls, total_calls, tottime, cumtime, callers = row
    print(key[2], total_calls, tottime, cumtime)
print(stats.total_tt)
print(dominant_profile_operation(stats, 0.4))

Вывод:

a_leaf 1 3.0 3.0
dispatcher 1 0.0 4.0
driver 1 0.0 4.0
z_leaf 1 1.0 1.0
4.0
('example_ops.py', 20, 'a_leaf')
РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Связанные задачи