Jak zmierzyć wydajność kodu elisp?

26

Jak zmierzyć wydajność mojego kodu elisp? Jakie narzędzia / pakiety zewnętrzne są dostępne do pomiaru czasu?

Czy oprócz całkowitego czasu mogę zobaczyć profil pokazujący czas poświęcony na funkcję? Czy mogę również profilować użycie pamięci?

Wilfred Hughes
źródło
1
Pytanie jest zbyt ogólne. Jakiego rodzaju wydajność? Gdzie? Kiedy? „ Wydajność Emacsa ” może oznaczać wszystko i wszystko.
Drew
@Drew Wiele innych języków programowania ma zestaw testów porównawczych (np. Python: speed.pypy.org , JS: Sunspider itp.), I miałem nadzieję, że istnieje odpowiednik interpretera elisp.
Wilfred Hughes
Benchmarking, taki jak ten zapewniany przez funkcję benchmarki profiler, nie mierzy wydajności Emacsa . Mierzy wydajność oceniając poszczególne wyrażenia. Jest to pomocne w porównywaniu wydajności w Emacsie. Aby zmierzyć wydajność samego Emacsa, musisz porównać go z wydajnością czegoś innego niż Emacs. I tu właśnie zaczyna się gra Emacsa. Możesz zmierzyć Emacsa względem XYZ dla tego lub tamtego, ale aby zmierzyć wydajność Emacsa jako całości, potrzebujesz kilku takich porównań.
Drew
Może miałeś na myśli „ Jak mierzyć wydajność w Emacsie ”?
Drew
2
OK, otworzyłem emacs.stackexchange.com/q/655/304, aby zajmować się testowaniem Emacsa, i przeredagowałem to pytanie na temat testów porównawczych / profilowania programów elisp.
Wilfred Hughes

Odpowiedzi:

31

Reper

Najprostszą opcją jest wbudowany benchmarkpakiet. Jego użycie jest niezwykle proste:

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

Jest ładowany automatycznie, więc nie musisz go nawet wymagać.

Profilowy

Benchmark jest dobry w ogólnych testach, ale jeśli masz problemy z wydajnością, nie mówi ci, które funkcje powodują problem. Do tego masz (również wbudowany) profiler .

  1. Zacznij od M-x profiler-start.
  2. Wykonaj trochę czasochłonnych operacji.
  3. Pobierz raport z M-x profiler-report.

Powinieneś zostać przeniesiony do bufora z nawigowalnym drzewem wywołań funkcji.
Zrzut ekranu programu Profiler

Malabarba
źródło
benchmarkfunkcja nie działa: kiedy robię wewnątrz otwartego .cpliku (benchmark 100 (c-font-lock-fontify-region 0 17355)), ciągle się pojawia void-function jit-lock-bounds.
Hi-Angel
1
FTR: jako alternatywa dla benchmarkistnieją funkcje benchmark-runi benchmark-run-compiled. Dla mnie główna różnica polegała na tym, że obie funkcje faktycznie działają (patrz poprzedni komentarz) : Ь
Hi-Angel
14

Oprócz odpowiedzi @ Malabara, mam tendencję do używania niestandardowego with-timermakra, aby trwale instrumentować różne części mojego kodu (np. Mój init.elplik).

Różnica polega na tym, że chociaż benchmarkpozwala badać wydajność określonego fragmentu kodu, który instrumentujesz, with-timerzawsze daje ci czas spędzony w każdej instrumentowanej części kodu (bez dużego obciążenia dla wystarczająco dużych części), co daje wkład do poznania która część powinna być dalej badana.

(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))))))

Przykładowe zastosowanie:

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

uzyskując następujące dane wyjściowe w *Messages*buforze:

Doing things... done (0.047s)

Powinienem wspomnieć, że jest to w dużej mierze inspirowane makrem Jona Wiegleya use-package-with-elapsed-timerw jego doskonałym use-packagerozszerzeniu.

ffevotte
źródło
Jeśli mierzysz init.el, prawdopodobnie zainteresuje Cię profil startowy emacsa .
Wilfred Hughes
Makra są niesamowite. To zasługuje na więcej głosów.
Malabarba
2
Emacs rejestruje całkowity czas inicjacji. Możesz to pokazać za pomocą polecenia emacs-init-time.
Joe
1
@WilfredHughes tak, używam esupi lubię to. Ale po raz kolejny interesuje się czymś takim, jak with-timernie tyle dokładnym profilowaniem czegoś. Prawdziwe zainteresowanie polega na tym, że zawsze masz informacje profilujące. Ilekroć uruchamiam emacsa, mam w *Messages*buforze wiele linii, które mówią mi, która część trwała. Jeśli wykryję coś nienormalnego, mogę użyć dowolnego z bardziej odpowiednich narzędzi do profilowania i optymalizacji rzeczy.
ffevotte
@JoeS Tak, emacs-init-timeprodukuje ciekawe informacje. Daje to jednak tylko czas, który upłynął, bez możliwości rozbicia poszczególnych części inicjalizacji.
ffevotte
3

Oprócz odpowiedzi @ Malabarba, pamiętaj, że możesz zmierzyć skompilowany czas wykonania swojego kodu benchmark-run-compiled. Ta metryka jest często znacznie bardziej odpowiednia niż interpretowany czas wykonania, który M-x benchmarkzapewnia:

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)

Te trzy liczby to całkowity czas, który upłynął, liczba przebiegów GC i czas spędzony w GC.

Łaskawy
źródło
1

Benchmarking to nie tylko uzyskiwanie liczb, ale także podejmowanie decyzji w oparciu o analizę wyników.

Na MELPA znajduje się pakiet benchstat.el , za pomocą którego można uzyskać funkcje oferowane przez program benchstat .

Implementuje testy porównawcze oparte na porównaniu, w których porównuje się Xwłaściwości wydajności Y.

Funkcje Benchstat można postrzegać jako benchmark-run-compiledopakowanie, które nie tylko zbiera informacje, ale daje je w łatwym do odczytania formacie interpretacji. Obejmuje:

  • Upływ czasu między XiY
  • Średni średni czas
  • Kwota przydziałów

Bardzo prosty przykład użycia:

(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-compareOdda wyniki w tymczasowym buforze:

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)

Będziesz jednak potrzebował benchstatprogramu binarnego. Jeśli używałeś języka programowania Go, najprawdopodobniej już go masz. W przeciwnym razie istnieje możliwość skompilowania go ze źródeł.

Wstępnie skompilowane pliki binarne dla systemu Linux / amd64 można znaleźć na stronie wydania github .

Iskander Sharipov
źródło