Wie messe ich die Leistung von Elisp-Code?

26

Wie messe ich die Leistung meines Elisp-Codes? Welche Tools / externen Pakete stehen mir zur Verfügung, um die benötigte Zeit zu messen?

Kann ich zusätzlich zur Gesamtzeit ein Profil sehen, das die pro Funktion benötigte Zeit anzeigt? Kann ich auch die Speichernutzung profilieren?

Wilfred Hughes
quelle
1
Die Frage ist zu weit gefasst. Was für eine Aufführung? Woher? Wann? " Emacs Performance " kann alles und jeden bedeuten.
Drew
@Drew Viele andere Programmiersprachen haben eine Reihe von Benchmarks (z. B. Python: speed.pypy.org , JS: Sunspider usw.), und ich hatte gehofft, dass es ein Äquivalent für den Elisp-Interpreter gibt.
Wilfred Hughes
Ein Benchmarking wie das von function benchmarkund dem Profiler bereitgestellte misst nicht die Emacs- Leistung. Es misst die Leistung bei der Bewertung bestimmter Ausdrücke. Es ist hilfreich, um die Leistungen innerhalb von Emacs zu vergleichen. Um die Leistung von Emacs selbst zu messen, müssten Sie sie mit der Leistung von etwas anderem als Emacs vergleichen. Und hier kommt die Breite von Emacs ins Spiel. Sie könnten Emacs gegen XYZ für dieses oder jenes messen, aber um die Leistung von Emacs als Ganzes zu messen, würden Sie unzählige solcher Vergleiche benötigen.
Drew
Vielleicht meinten Sie " Wie messe ich die Leistung in Emacs "?
Drew
2
OK, ich habe emacs.stackexchange.com/q/655/304 geöffnet, um mich mit dem Benchmarking von Emacs zu befassen , und diese Frage mit dem Benchmarking / Profiling von elisp-Programmen umformuliert.
Wilfred Hughes

Antworten:

31

Benchmark

Die einfachste Option ist das eingebaute benchmarkPaket. Die Bedienung ist denkbar einfach:

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

Es wird automatisch geladen, sodass Sie es nicht einmal benötigen.

Profiling

Benchmark ist gut für allgemeine Tests, aber wenn Sie Leistungsprobleme haben, können Sie nicht feststellen, welche Funktionen das Problem verursachen. Dafür haben Sie den (auch eingebauten) Profiler .

  1. Beginnen Sie mit M-x profiler-start.
  2. Führen Sie einige zeitaufwändige Vorgänge durch.
  3. Holen Sie sich den Bericht mit M-x profiler-report.

Sie sollten zu einem Puffer mit einem navigierbaren Baum von Funktionsaufrufen geführt werden.
Profiler-Screenshot

Malabarba
quelle
benchmarkFunktion scheint nicht zu funktionieren: Wenn ich in einer geöffneten .cDatei mache, bekomme (benchmark 100 (c-font-lock-fontify-region 0 17355))ich immer void-function jit-lock-bounds.
Hi-Angel
1
FTR: alternativ benchmarkgibt es funktionen benchmark-runund benchmark-run-compiled. Für mich war der Hauptunterschied, dass beide Funktionen tatsächlich funktionieren (siehe vorheriger Kommentar) : Ь
Hi-Angel
14

Zusätzlich zu der Antwort von @ Malabara verwende ich normalerweise ein benutzerdefiniertes with-timerMakro, um verschiedene Teile meines Codes (z init.el. B. meine Datei) permanent zu instrumentieren .

Der Unterschied besteht darin, dass Sie zwar benchmarkdie Leistung eines bestimmten von Ihnen instrumentierten Codebits untersuchen können, jedoch with-timerimmer die Zeit für jeden instrumentierten Teil des Codes haben (ohne großen Aufwand für ausreichend große Teile), wodurch Sie die Eingabe erhalten, die Sie wissen müssen welcher Teil soll weiter untersucht werden.

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

Beispiel Verwendung:

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

ergibt folgende Ausgabe im *Messages*Puffer:

Doing things... done (0.047s)

Ich sollte erwähnen, dass dies stark von Jon Wiegleys use-package-with-elapsed-timerMakro in seiner exzellenten use-packageErweiterung inspiriert ist .

ffevotte
quelle
Wenn Sie init.el messen, werden Sie wahrscheinlich am emacs-Startprofiler interessiert sein .
Wilfred Hughes
Makros sind fantastisch. Dies verdient mehr Stimmen.
Malabarba
2
Emacs zeichnet die gesamte Init-Zeit auf. Sie können es mit dem Befehl anzeigen emacs-init-time.
Joe
1
@WilfredHughes ja, ich benutze esupund ich mag es. Aber noch einmal, das Interesse an so etwas with-timerist nicht so sehr, etwas gründliches zu profilieren. Das wirkliche Interesse ist, dass Sie immer Profilinformationen haben. Wann immer ich Emacs starte, habe ich ein Buch mit Zeilen in meinem *Messages*Puffer, die mir sagen, welcher Teil wie lange gedauert hat . Wenn ich etwas Ungewöhnliches feststelle, kann ich eines der besser geeigneten Tools zum Profilieren und Optimieren verwenden.
ffevotte
@JoeS Ja, emacs-init-timeliefert interessante Informationen. Es gibt jedoch nur eine inklusiv verstrichene Zeit, ohne die Möglichkeit, einzelne Teile der Initialisierung zu zerlegen.
ffevotte
3

Beachten Sie, dass Sie neben der Antwort von @ Malabarba auch die kompilierte Ausführungszeit Ihres Codes mit messen können benchmark-run-compiled. Diese Metrik ist häufig viel relevanter als die interpretierte Ausführungszeit, M-x benchmarkdie Ihnen Folgendes bietet:

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)

Die drei Zahlen sind die insgesamt verstrichene Zeit, die Anzahl der GC-Läufe und die Zeit, die im GC verbracht wurde.

Clément
quelle
1

Beim Benchmarking geht es nicht nur darum, die Zahlen zu ermitteln, sondern auch darum, Entscheidungen auf der Grundlage von Ergebnisanalysen zu treffen.

Auf MELPA gibt es das Paket benchstat.el, mit dem Sie die Funktionen des Programms benchstat abrufen können .

Es implementiert ein vergleichsbasiertes Benchmarking, mit dem Sie XLeistungseigenschaften vergleichen Y.

Benchstat-Funktionen können als benchmark-run-compiledWrapper betrachtet werden, der die Informationen nicht nur sammelt, sondern sie in einem leicht lesbaren Interpretationsformat zurückgibt. Es enthält:

  • Zeitdelta zwischen XundY
  • Mittlere durchschnittliche Zeit
  • Zuteilungsbetrag

Sehr einfaches Anwendungsbeispiel:

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

Das benchstat-compareErgebnis wird in einem temporären Puffer dargestellt:

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)

Sie benötigen jedoch benchstatProgramm-Binärdateien. Wenn Sie die Programmiersprache Go verwendet haben, haben Sie höchstwahrscheinlich bereits eine in Ihrem System. Ansonsten besteht die Möglichkeit, es aus den Quellen zu kompilieren.

Vorkompilierte Binärdateien für Linux / AMD64 finden Sie auf der Github-Release-Seite .

Iskander Sharipov
quelle