Comment mesurer les performances du code elisp?

26

Comment mesurer les performances de mon code elisp? Quels outils / packages externes sont disponibles pour mesurer le temps nécessaire?

En plus du temps total, puis-je voir un profil qui montre le temps pris par fonction? Puis-je également profiler l'utilisation de la mémoire?

Wilfred Hughes
la source
1
La question est trop large. Quel genre de performance? Où? Quand? La « performance Emacs » peut signifier tout et n'importe quoi.
attiré
@Drew De nombreux autres langages de programmation ont un ensemble de benchmarks (par exemple Python: speed.pypy.org , JS: Sunspider, etc.), et j'espérais qu'il y avait un équivalent pour l'interprète elisp.
Wilfred Hughes, du
L'analyse comparative telle que celle fournie par la fonction benchmarket le profileur ne mesure pas les performances d' Emacs . Il mesure les performances évaluant des expressions particulières. Il est utile pour comparer les performances dans Emacs. Pour mesurer les performances d'Emacs lui-même, vous devez le comparer aux performances de quelque chose d'autre qu'Emacs. Et c'est là que l'étendue d'Emacs entre en jeu. Vous pouvez mesurer Emacs par rapport à XYZ pour ceci ou cela, mais pour mesurer les performances d'Emacs dans son ensemble, vous aurez besoin d'une multitude de telles comparaisons.
Drew
Peut-être que vous vouliez dire " Comment mesurer les performances dans Emacs "?
Drew
2
OK, j'ai ouvert emacs.stackexchange.com/q/655/304 pour être sur le benchmarking Emacs, et reformulé cette question sur le benchmarking / profilage des programmes elisp.
Wilfred Hughes

Réponses:

31

Référence

Les options les plus simples sont le benchmarkpackage intégré. Son utilisation est remarquablement simple:

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

Il est chargé automatiquement, vous n'avez donc même pas besoin de l'exiger.

Profilage

La référence est bonne pour les tests globaux, mais si vous rencontrez des problèmes de performances, elle ne vous indique pas quelles fonctions sont à l'origine du problème. Pour cela, vous avez le profileur (également intégré) .

  1. Commencez avec M-x profiler-start.
  2. Faites des opérations qui prennent du temps.
  3. Obtenez le rapport avec M-x profiler-report.

Vous devez être redirigé vers un tampon avec une arborescence navigable d'appels de fonction.
Capture d'écran du profileur

Malabarba
la source
benchmarkla fonction ne semble pas fonctionner: quand je fais à l'intérieur d'un .cfichier ouvert (benchmark 100 (c-font-lock-fontify-region 0 17355)), je continue à obtenir void-function jit-lock-bounds.
Hi-Angel
1
FTR: comme alternative à benchmarkil existe des fonctions benchmark-runet benchmark-run-compiled. Pour moi, la principale différence était que les deux fonctions fonctionnent réellement (voir le commentaire précédent ) : Ь
Hi-Angel
14

En plus de la réponse de @ Malabara, j'ai tendance à utiliser une with-timermacro sur mesure pour instrumenter en permanence diverses parties de mon code (par exemple mon init.elfichier).

La différence est que, tout en benchmarkpermettant d'étudier les performances d'un bit de code spécifique que vous instrumentez, with-timervous donne toujours le temps passé dans chaque partie instrumentée du code (sans trop de surcharge pour des parties suffisamment grandes), ce qui vous donne la possibilité de connaître quelle partie devrait être étudiée plus avant.

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

Exemple d'utilisation:

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

donnant la sortie suivante dans le *Messages*tampon:

Doing things... done (0.047s)

Je dois mentionner que cela est fortement inspiré par la use-package-with-elapsed-timermacro de Jon Wiegley dans son excellente use-packageextension.

ffevotte
la source
Si vous mesurez init.el, vous serez probablement intéressé par le profileur de démarrage emacs .
Wilfred Hughes
Les macros sont géniales. Cela mérite plus de votes.
Malabarba
2
Emacs enregistre la durée totale d'initialisation. Vous pouvez le montrer avec la commande emacs-init-time.
Joe
1
@WilfredHughes oui, j'utilise esupet j'aime ça. Mais encore une fois, l'intérêt d'une telle chose with-timern'est pas tant de profiler quelque chose à fond. Le véritable intérêt est que vous disposez toujours d'informations de profilage. Chaque fois que je démarre emacs, j'ai un tas de lignes dans mon *Messages*tampon qui me disent quelle partie a pris combien de temps. Si je détecte quelque chose d'anormal, je peux alors utiliser l'un des outils les plus adéquats pour profiler et optimiser les choses.
ffevotte
@JoeS Oui, emacs-init-timeproduit des informations intéressantes. Cependant, il ne donne qu'un temps écoulé inclusif, sans possibilité de décomposer des parties individuelles de l'initialisation.
ffevotte
3

En plus de la réponse de @ Malabarba, notez que vous pouvez mesurer le temps d'exécution compilé de votre code avec benchmark-run-compiled. Cette métrique est souvent beaucoup plus pertinente que le temps d'exécution interprété qui M-x benchmarkvous donne:

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)

Les trois nombres sont le temps total écoulé, le nombre d'exécutions de GC et le temps passé en GC.

Clément
la source
1

L'analyse comparative ne consiste pas seulement à obtenir les chiffres, mais aussi à prendre des décisions basées sur l'analyse des résultats.

Il existe un package benchstat.el sur MELPA que vous pouvez utiliser pour obtenir les fonctionnalités fournies par le programme benchstat .

Il implémente une analyse comparative basée sur la comparaison dans laquelle vous examinez Xles propriétés de performance Y.

Les fonctions Benchstat peuvent être vues comme un benchmark-run-compiledwrapper qui non seulement recueille les informations, mais les restitue dans un format d'interprétation facile à lire. Il comprend:

  • Delta de temps écoulé entre XetY
  • Temps moyen moyen
  • Montant des allocations

Exemple d'utilisation très simple:

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

Le benchstat-comparerendra les résultats dans un tampon temporaire:

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)

Vous aurez cependant besoin d'un benchstatprogramme binaire. Si vous avez utilisé le langage de programmation Go, vous en avez probablement déjà un dans votre système. Sinon, il est possible de le compiler à partir des sources.

Le binaire précompilé pour linux / amd64 peut être trouvé sur la page de publication de github .

Iskander Sharipov
la source