Как измерить производительность кода elisp?

26

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

В дополнение к общему времени, я могу видеть профиль, который показывает время, затраченное на функцию? Можно ли также профилировать использование памяти?

Уилфред Хьюз
источник
1
Вопрос слишком широкий. Какого рода производительность? Где? Когда? « Производительность Emacs » может означать что угодно и что угодно.
Дрю
@Drew Многие другие языки программирования имеют набор тестов (например, Python: speed.pypy.org , JS: Sunspider и т. Д.), И я надеялся, что для интерпретатора elisp был эквивалент.
Уилфред Хьюз
Сравнительный анализ, такой как предоставляемый функцией benchmarkи профилировщиком, не измеряет производительность Emacs . Он измеряет производительность, оценивая конкретные выражения. Это полезно при сравнении производительности в Emacs. Чтобы измерить производительность самого Emacs, вам нужно сравнить его с производительностью чего-то другого, чем Emacs. И именно здесь в игру вступает широта Emacs. Вы можете измерить Emacs против XYZ для того или иного, но для измерения производительности Emacs в целом вам понадобится множество таких сравнений.
Дрю
Может быть, вы имели в виду « Как измерить производительность в Emacs »?
Дрю
2
Хорошо, я открыл emacs.stackexchange.com/q/655/304, чтобы рассказать о тестировании Emacs, и перефразировал этот вопрос о тестировании и профилировании программ elisp.
Уилфред Хьюз

Ответы:

31

эталонный тест

Самым простым вариантом является встроенный benchmarkпакет. Его использование удивительно просто:

(benchmark 100 (form (to be evaluated)))

Он загружается автоматически, поэтому вам даже не нужно его запрашивать.

Профилирование

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

  1. Начни с M-x profiler-start.
  2. Выполните некоторые трудоемкие операции.
  3. Получите отчет с M-x profiler-report.

Вы должны перейти в буфер с навигационным деревом вызовов функций.
Скриншот профилировщика

Malabarba
источник
benchmarkфункция, кажется, не работает: когда я делаю внутри открытого .cфайла (benchmark 100 (c-font-lock-fontify-region 0 17355)), я продолжаю получать void-function jit-lock-bounds.
Привет-ангел
1
FTR: в качестве альтернативы benchmarkесть функции benchmark-runи benchmark-run-compiled. Для меня основным отличием было то, что обе функции действительно работают (см. Предыдущий комментарий) : Ь
Hi-Angel
14

В дополнение к ответу @ Malabara я обычно использую пользовательский with-timerмакрос, чтобы постоянно обрабатывать различные части моего кода (например, мой init.elфайл).

Разница заключается в том, что, хотя benchmarkпозволяет изучать производительность конкретного бита кода, который вы обрабатываете, with-timerвсегда дает вам время, затрачиваемое на каждую инструментальную часть кода (без больших затрат на достаточно большие части), что дает вам информацию, которую нужно знать какая часть должна быть исследована дальше.

(defmacro with-timer (title &rest forms)
  "Run the given FORMS, counting the elapsed time.
A message including the given TITLE and the corresponding elapsed
time is displayed."
  (declare (indent 1))
  (let ((nowvar (make-symbol "now"))
        (body   `(progn ,@forms)))
    `(let ((,nowvar (current-time)))
       (message "%s..." ,title)
       (prog1 ,body
         (let ((elapsed
                (float-time (time-subtract (current-time) ,nowvar))))
           (message "%s... done (%.3fs)" ,title elapsed))))))

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

(with-timer "Doing things"
  (form (to (be evaluated))))

получая следующий вывод в *Messages*буфере:

Doing things... done (0.047s)

Я должен отметить, что это в значительной степени вдохновлено макросом Джона Уигли use-package-with-elapsed-timerв его превосходном use-packageрасширении.

ffevotte
источник
Если вы измеряете init.el, вам, вероятно, будет интересен профилировщик запуска emacs .
Уилфред Хьюз
Макросы потрясающие. Это заслуживает большего количества голосов.
Малабарба
2
Emacs записывает общее время инициализации. Вы можете показать это с помощью команды emacs-init-time.
Джо
1
@WilfredHughes да, я использую, esupи мне это нравится. Но опять же, интерес к такой вещи, как with-timerне столько к профилированию чего-либо. Реальный интерес заключается в том, что у вас всегда есть информация профилирования. Всякий раз, когда я запускаю emacs, в моем *Messages*буфере есть куча строк, которые говорят мне, какая часть заняла сколько времени. Если я обнаруживаю что-то ненормальное, я могу использовать любой из более подходящих инструментов для профилирования и оптимизации.
ffevotte
@JoeS Да, emacs-init-timeдействительно производит интересную информацию. Тем не менее, он дает только включенное время, без возможности разбить отдельные части инициализации.
ffevotte
3

В дополнении к @ ответу Malabarba, в обратите внимание , что вы можете измерить скомпилированное время выполнения кода с benchmark-run-compiled. Этот показатель часто намного важнее интерпретированного времени выполнения, которое M-x benchmarkдает вам:

ELISP> (benchmark-run (cl-loop for i below (* 1000 1000) sum i))
(0.79330082 6 0.2081620540000002)

ELISP> (benchmark-run-compiled (cl-loop for i below (* 1000 1000) sum i))
(0.047896284 0 0.0)

Три числа - это общее прошедшее время, количество прогонов ГХ и время, проведенное в ГХ.

Клеман
источник
1

Бенчмаркинг - это не только получение цифр, но и принятие решений на основе анализа результатов.

На MELPA есть пакет benchstat.el, который вы можете использовать для получения функций, которые предоставляет программа benchstat .

Он реализует сравнительный анализ, основанный на сравнении, в котором сравниваются Xсвойства производительности Y.

Функции Benchstat можно рассматривать как benchmark-run-compiledоболочку, которая не только собирает информацию, но и возвращает ее в удобном для чтения формате. Оно включает:

  • Дельта прошедшего времени между XиY
  • Среднее среднее время
  • Сумма отчислений

Очень простой пример использования:

(require 'benchstat)

;; Decide how much repetitions is needed.
;; This is the same as `benchmark-run-compiled` REPETITIONS argument.
(defconst repetitions 1000000)

;; Collect old code profile.
(benchstat-run :old repetitions (list 1 2))
;; Collect new code profile.
(benchstat-run :new repetitions (cons 1 2))

;; Display the results.
;; Can be run interactively by `M-x benchstat-compare'.
(benchstat-compare)

Результаты benchstat-compareбудут отображаться во временном буфере:

name   old time/op    new time/op    delta
Emacs    44.2ms ± 6%    25.0ms ±15%  -43.38%  (p=0.000 n=10+10)

name   old allocs/op  new allocs/op  delta
Emacs      23.0 ± 0%      11.4 ± 5%  -50.43%  (p=0.000 n=10+10)

Тебе понадобится benchstat двоичный файл программы. Если вы использовали язык программирования Go, скорее всего, он уже есть в вашей системе. В противном случае есть возможность скомпилировать его из исходников.

Предварительно скомпилированный бинарный файл для linux / amd64 можно найти на странице релиза github .

Искандер Шарипов
источник