Optimizar el rendimiento del bloqueo de fuente

13

Quiero realizar una variante de coincidencia de bloqueo de fuente anclada. Tengo definiciones de funciones que comienzan con una lista de nombres, y quiero que esos nombres se resalten dentro del cuerpo de la función.

Creé una función que hace esto y la registré como una función de bloqueo de jit con jit-lock-register, sin embargo, el rendimiento es bastante pobre y el desplazamiento se desplaza en archivos más grandes.

  • ¿Cómo puedo medir el rendimiento? Si acabo de llamar a mi función en un archivo grande (con tiempo flotante antes y después o con elp) obtengo un rendimiento muy variable, toma de 0,65 a 12 segundos. ¿Hay alguna forma recomendada de evaluar el rendimiento del bloqueo de fuente?
  • ¿Hay alguna diferencia en el rendimiento entre un emparejador anclado definido en font-lock-keywords y agregar una función a través de jit-lock-register?

Editar: Parece que la variabilidad en el rendimiento está relacionada con la recolección de basura, las invocaciones de mi función jit-lock se vuelven sucesivamente más lentas con cada invocación hasta que se ejecuta la recolección de basura, en cuyo punto se vuelven a acelerar.

Joakim Hårsman
fuente
Para el primer elemento, prueba el generador de perfiles.
Malabarba
Puedo (y he usado) el generador de perfiles para ver qué partes de mi código llevan tiempo, pero como el rendimiento es tan inconsistente, es difícil saber si los cambios que realizo son una mejora o no.
Joakim Hårsman
¿Tienes algún código que podamos probar? Eso podría ayudarnos mucho.
PythonNut
1
Aunque no se trata de perfiles o micro optimizaciones, per se: he encontrado que el paquete font-lock-studio es otra herramienta útil para comprender el rendimiento del bloqueo de fuentes. Puede ayudar de la misma manera que cualquier otro depurador de pasos interactivo puede ayudar: puede descubrir que las rutas de ejecución no son lo que espera, y ese es el principal problema de rendimiento.
Greg Hendershott
Gracias por el consejo sobre font-lock-studio, ¡es increíble! Sin embargo, no ayuda con las funciones de bloqueo de jit, pero sí lo hace con todo lo demás.
Joakim Hårsman

Respuestas:

8

Resulta que el rendimiento muy variable estaba relacionado con la recolección de basura. Cada llamada a la función sería más lenta hasta que se ejecutara una recolección de basura. Con el stock emacs, gc se ejecutaba cada dos segundos, pero tenía una línea en mi init.el para mejorar el tiempo de inicio que establecía gc-cons-umbral en 20 MB, y eso significaba que gc se ejecutaba con mucha menos frecuencia, lo que causaba puntos de referencia para informe un tiempo cada vez más lento hasta que se ejecute un gc después de un par de minutos, luego los tiempos caerían en picado y volverían a ser rápidos.

Después de volver al valor predeterminado gc-cons-threshhold, la evaluación comparativa se hizo más fácil.

Luego perfilé la memoria con el generador de perfiles incorporado ( M-x profiler-start), y descubrí que las llamadas a syntax-ppss causaban la mayoría de las asignaciones, por lo que después de alguna optimización para llamar a syntax-ppss con menos frecuencia logré un rendimiento aceptable.

El uso de jit-lock-mode (agregar una función a través de jit-lock-register) parece ser la forma más fácil de lograr que el bloqueo de fuentes de varias líneas funcione de manera confiable, por lo que ese fue el método que elegí.

Editar: después de descubrir que el rendimiento aún no era lo suficientemente bueno en buffers muy grandes, pasé mucho tiempo optimizando el uso y la asignación de la CPU, midiendo las mejoras de rendimiento con el generador de perfiles Emacs incorporado ( M-x profiler-start). Sin embargo, Emacs todavía tartamudearía y se colgaría al desplazarse rápidamente a través de buffers muy grandes. La eliminación de la función de bloqueo de jit con la que me registré jit-lock-registereliminaría la tartamudez y los bloqueos, pero la creación de perfiles mostró que la función de bloqueo de jit se completa en alrededor de 8 ms, lo que debería ser lo suficientemente rápido para un desplazamiento suave. La eliminación de la llamada jit-lock-registery, en su lugar, el uso de un emparejador de palabras clave de bloqueo de fuente regular resolvió el problema.

TLDR: Hacer esto fue lento y tartamudearía:

(defun my-font-lock-function (start end)
"Set faces for font-lock between START and END.")

(jit-lock-register 'my-font-lock-function)

Hacer esto fue rápido y no tartamudearía:

(defun my-font-lock-function (start end)
"Set faces for font-lock between START and END.")

(defun my-font-lock-matcher (limit)
    (my-font-lock-function (point) limit)
   nil)

(setq font-lock-defaults
  (list 
     ...
    ;; Note that the face specified here doesn't matter since
    ;; my-font-lock-matcher always returns nil and sets the face on
    ;; its own.
    `(my-font-lock-matcher (1 font-lock-keyword-face nil))))
Joakim Hårsman
fuente
¿Podrías compartir el código que usaste? Su solución podría ayudar a otros que buscan lograr lo mismo.
Manuel Uberti
Realmente no utilicé ningún código específico, simplemente llamé a syntax-ppss less. Puede consultar el código en cuestión aquí: bitbucket.org/harsman/dyalog-mode/src/… Busque dyalog-fontify-locals.
Joakim Hårsman
Supongo que dyalog-fontify-locals-matcherdebería ser my-font-lock-matchery uno de los enddebería ser limit. De todos modos, ¡un descubrimiento realmente interesante!
Lindydancer
@Lindydancer: Sí, gracias. Fijo.
Joakim Hårsman
1
Re: gc-cons-thresholdsi estás jugando con valores internos únicamente para mejorar el tiempo de inicio, te sugiero que los uses emacs-startup-hookpara restaurarlos después.
phils