Предотвращение регрессии производительности в R - PullRequest
32 голосов
/ 11 декабря 2011

Каков хороший рабочий процесс для обнаружения регрессий производительности в пакетах R? В идеале я ищу что-то, что интегрируется с R CMD check, которое предупреждает меня, когда я ввел существенный спад производительности в моем коде.

Что такое хороший рабочий процесс в целом? Какие другие языки предоставляют хорошие инструменты? Это что-то, что может быть построено на тестировании верхнего модуля, или это обычно делается отдельно?

Ответы [ 3 ]

17 голосов
/ 31 декабря 2011

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

Каков хороший рабочий процесс для обнаружения регрессий производительности в пакетах R?

В моем случае, я склонен использовать очень конкретные сценарии использования, которые я пытаюсь ускорить, с разными фиксированными наборами данных. Как писал Spacedman, важно иметь фиксированную вычислительную систему, но это практически невозможно: иногда на общем компьютере могут быть другие процессы, которые замедляют работу на 10-20%, даже если он выглядит довольно простаевшим.

Мои шаги:

  1. Стандартизация платформы (например, одна или несколько машин, конкретная виртуальная машина или виртуальная машина + определенная инфраструктура, например, типы экземпляров Amazon EC2).
  2. Стандартизировать набор данных, который будет использоваться для тестирования скорости.
  3. Создание сценариев и фиксированный вывод промежуточных данных (т.е. сохраняются в файлы .rdat), которые включают в себя очень минимальные преобразования данных. Я сосредоточен на моделировании, а не на манипулировании данными или преобразовании. Это означает, что я хочу дать точно такой же блок данных для функций моделирования. Однако, если целью является преобразование данных, убедитесь, что предварительно преобразованные / обработанные данные максимально приближены к стандартным в тестах различных версий пакета. (См. в этом вопросе для примеров запоминания, кэширования и т. Д., Которые можно использовать для стандартизации или ускорения нефокусных вычислений. Он ссылается на несколько пакетов с помощью OP.)
  4. Повторить тесты несколько раз.
  5. Масштабировать результаты относительно фиксированных ориентиров, например, время для выполнения линейной регрессии, сортировки матрицы и т. д. Это может учитывать «локальные» или временные изменения в инфраструктуре, например, из-за ввода-вывода, системы памяти, зависимых пакетов и т. д.
  6. Изучите результаты профилирования настолько энергично, насколько это возможно (см. в этом вопросе для получения некоторых сведений, также ссылающихся на инструменты из OP).

    В идеале я ищу что-то, что интегрируется с проверкой CMD R, которая предупреждает меня, когда я ввел в свой код существенную регрессию производительности.

    К сожалению, у меня нет ответа на этот вопрос.

    Что такое хороший рабочий процесс в целом?

    Для меня это очень похоже на общее динамическое тестирование кода: вывод (в данном случае время выполнения) воспроизводим, оптимален и прозрачен? Прозрачность приходит от понимания того, что влияет на общее время. Именно здесь важны предложения Майка Данлавей, но я предпочитаю идти дальше с профилировщиком.

    Что касается линейного профилировщика, см. мой предыдущий вопрос , который ссылается на опции в Python и Matlab для других примеров. Наиболее важно изучить время часов, но также очень важно отслеживать распределение памяти, количество раз выполнения строки и глубину стека вызовов.

    Какие другие языки предоставляют хорошие инструменты?

    Почти все другие языки имеют лучшие инструменты. :) В интерпретируемых языках, таких как Python и Matlab, есть хорошие и, возможно, знакомые примеры инструментов, которые можно адаптировать для этой цели. Хотя динамический анализ очень важен, статический анализ может помочь определить, где могут возникнуть серьезные проблемы. Matlab имеет отличный статический анализатор, который может сообщать, когда объекты (например, векторы, матрицы) растут, например, внутри циклов. Страшно найти это только с помощью динамического анализа - вы уже потратили время выполнения, чтобы обнаружить что-то подобное, и это не всегда заметно, если ваш контекст выполнения довольно прост (например, всего несколько итераций или небольшие объекты).

    Что касается методов, не зависящих от языка, вы можете посмотреть:

    1. Valgrind и cachegrind
    2. Мониторинг дискового ввода-вывода, грязных буферов и т. Д.
    3. Мониторинг ОЗУ (Cachegrind полезен, но вы можете просто отслеживать распределение ОЗУ, имного подробностей об использовании ОЗУ)
    4. Использование нескольких ядер

    Это что-то, что может быть построено на тестировании верхнего модуля, или это обычно делается отдельно?

    Сложно ответить.Для статического анализа это может произойти до модульного тестирования.Для динамического анализа можно добавить больше тестов.Думайте об этом как о последовательном проектировании (то есть из экспериментальной структуры проектирования): если затраты на выполнение, кажется, в пределах некоторых статистических допусков для вариации одинаковы, то дальнейшие тесты не требуются.Однако если метод В, кажется, имеет среднюю стоимость выполнения больше, чем метод А., то следует выполнить более интенсивные тесты.


Обновление 1: если я могу быть настолько смелыместь еще один вопрос, который я бы порекомендовал включить, а именно: «Какие уловки при сравнении времени выполнения двух версий пакета?»Это аналогично предположению, что две программы, реализующие один и тот же алгоритм, должны иметь одинаковые промежуточные объекты.Это не совсем верно (см. этот вопрос - не то, чтобы я продвигал свои собственные вопросы, здесь - это просто тяжелая работа, чтобы сделать вещи лучше и быстрее ... приводя к множеству SO вопросов по этой теме :)).Аналогичным образом, два выполнения одного и того же кода могут отличаться по времени, потребляемому из-за факторов, отличных от реализации.

Итак, некоторые ошибки, которые могут происходить, либо на одном языке, либо на разных языках, в одном и том жеЭкземпляр выполнения или «идентичные» экземпляры, которые могут повлиять на время выполнения:

  1. Сборка мусора - различные реализации или языки могут поразить сборку мусора при различных обстоятельствах.Это может привести к тому, что два исполнения будут выглядеть по-разному, хотя это может сильно зависеть от контекста, параметров, наборов данных и т. Д. Обязательное выполнение GC будет выглядеть медленнее.
  2. Кэширование на уровне диска, материнской платы (например,L1, L2, L3 кэши) или другие уровни (например, запоминание).Часто за первое выполнение платят штраф.
  3. Динамическое масштабирование напряжения - Это отстой.Когда есть проблема, это может быть одна из самых трудных проблем, потому что она может быстро исчезнуть.Это похоже на кеширование, но это не так.
  4. Любой менеджер приоритетов заданий, о котором вы не знаете.
  5. Один метод использует несколько ядер или делает некоторые умные вещи о том, как работа распределяется между ядрами или процессорами.Например, привязка процесса к ядру может быть полезна в некоторых сценариях.Одно выполнение пакета R может быть более удачным в этом отношении, другой пакет может быть очень умным ...
  6. Неиспользуемые переменные, чрезмерная передача данных, грязные кэши, незаполненные буферы, ... список можно продолжить.

Ключевой результат: в идеале, как мы должны проверять различия в ожидаемых значениях с учетом случайности, созданной из-за эффектов порядка?Ну, довольно просто: вернемся к экспериментальному дизайну.:)

Когда эмпирические различия во времени выполнения отличаются от «ожидаемых» различий, здорово включить дополнительный мониторинг системы и выполнения, чтобы нам не приходилось перезапускать эксперименты, пока мы несинее лицо.

10 голосов
/ 11 декабря 2011

Единственный способ сделать что-либо здесь - это сделать некоторые предположения. Итак, давайте предположим, что машина без изменений, или же потребуется «повторная калибровка».

Затем используйте систему модульного тестирования и рассматривайте «должно быть сделано в X единицах времени» как еще один критерий тестирования, который необходимо выполнить. Другими словами, сделайте что-то вроде

 stopifnot( timingOf( someExpression ) < savedValue plus fudge)

так что нам нужно было бы связать предшествующее время с данными выражениями. Можно также использовать сравнения на равенство из любого из трех существующих пакетов модульного тестирования.

Ничего, с чем Хэдли не мог справиться, так что я думаю, что мы почти можем ожидать новый пакет timr после следующего длительного академического перерыва :). Конечно, это должно быть либо необязательным, потому что на «неизвестной» машине (например, CRAN тестирует пакет) у нас нет контрольной точки, иначе коэффициент «выдумки» должен «перейти к 11», чтобы автоматически принять на новой машине .

4 голосов
/ 14 декабря 2011

Недавнее изменение, объявленное в канале R-devel, может дать приблизительную оценку этому.

ИЗМЕНЕНИЯ В ОБЛАСТИ R-devel

‘R CMD check’ может опционально сообщать о времени в различных частях проверки: это контролируется переменными среды, задокументированными в «Writing R Extensions».

См. http://developer.r -project.org / blosxom.cgi / R-devel / 2011/12/13 # n2011-12-13

Общее время, затраченное на выполнение тестов, можно проверить и сравнить с предыдущими значениями. Конечно, добавление новых тестов увеличит время, но по-прежнему можно наблюдать резкий спад производительности, хотя и вручную.

Это не так хорошо, как поддержка синхронизации в отдельных наборах тестов, но также не зависит ни от одного конкретного набора тестов.

...