Otimizando o desempenho do bloqueio de fonte

13

Quero executar uma variante da correspondência de bloqueio de fonte ancorada. Eu tenho definições de função que começam com uma lista de nomes e quero que esses nomes sejam destacados dentro do corpo da função.

Eu criei uma função que faz isso e a registrei como uma função jit-lock com jit-lock-register, no entanto, o desempenho é muito ruim e a rolagem fica lenta em arquivos maiores.

  • Como posso medir o desempenho? Se eu apenas chamar minha função em um arquivo grande (com tempo de flutuação antes e depois ou com elp), obtém um desempenho bastante variado, leva de 0,65 a 12 segundos. Existe uma maneira recomendada de comparar o desempenho do bloqueio de fonte?
  • Existe alguma diferença no desempenho entre um comparador ancorado definido em palavras-chave de bloqueio de fonte e a adição de uma função via jit-lock-register?

Edit: Parece que a variabilidade no desempenho está relacionada à coleta de lixo, as invocações da minha função jit-lock ficam sucessivamente mais lentas a cada invocação até que a coleta de lixo seja executada, e nesse ponto elas ficam mais rápidas novamente.

Joakim Hårsman
fonte
Para o primeiro item, tente o criador de perfil.
Malabarba
Também posso (e já usei) o criador de perfil para ver quais partes do meu código levam tempo, mas como o desempenho é tão inconsistente, é difícil saber se alguma alteração feita é uma melhoria ou não.
Joakim Hårsman
Você tem algum código que podemos testar? Isso pode nos ajudar muito.
PythonNut
1
Embora não seja sobre criação de perfil ou micro otimizações, por si só: eu achei o pacote font-lock-studio uma outra ferramenta útil para entender o desempenho do bloqueio de fonte. Ele pode ajudar da mesma maneira que qualquer outro depurador interativo interativo - você pode descobrir que os caminhos de execução não são o que você espera, e esse é o principal problema de desempenho.
Greg Hendershott
Obrigado pela dica sobre o font-lock-studio, é incrível! No entanto, não ajuda nas funções jit-lock, mas certamente ajuda em tudo o mais.
Joakim Hårsman

Respostas:

8

Acontece que o desempenho bastante variado estava relacionado à coleta de lixo. Cada chamada para a função ficaria mais lenta até que uma coleta de lixo fosse executada. Com o estoque emacs, o gc era executado a cada dois segundos, mas eu tinha uma linha no init.el para melhorar o tempo de inicialização que definia o limite de gc-cons-20 para 20 MB, e isso significava que o gc era executado com muito menos frequência, fazendo com que os benchmarks relatar um tempo cada vez mais lento até que um gc fosse executado depois de alguns minutos; os tempos despencariam e seriam rápidos novamente.

Após reverter para o padrão gc-cons-threshhold, o benchmarking ficou mais fácil.

Em seguida, criei um perfil de memória com o profiler interno ( M-x profiler-start) e descobri que as chamadas para syntax-ppss causavam mais alocações; portanto, após alguma otimização para chamar syntax-ppss com menos frequência, obtive um desempenho aceitável.

Usar o modo jit-lock (adicionar uma função via jit-lock-register) parece ser a maneira mais fácil de fazer com que o bloqueio de fontes de várias linhas funcione de maneira confiável, e esse foi o método que eu escolhi.

Edit: Depois de descobrir que o desempenho ainda não era bom o suficiente em buffers muito grandes, passei muito tempo otimizando o uso e a alocação da CPU, medindo as melhorias de desempenho com o criador de perfil Emacs ( M-x profiler-start). No entanto, o Emacs ainda gagueja e trava ao rolar rapidamente por buffers muito grandes. A remoção da função jit-lock com a qual eu me registrei jit-lock-registerremoveria a gagueira e o travamento, mas o perfil mostrou que a função jit-lock é concluída em cerca de 8 ms, o que deve ser rápido o suficiente para uma rolagem suave. A remoção da chamada jit-lock-registere, em vez disso, o uso de um correspondente regular de bloqueio de fonte-palavras-chave resolveu o problema.

TLDR: Fazer isso era lento e gaguejava:

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

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

Fazer isso era rápido e não gaguejava:

(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
fonte
Você poderia compartilhar o código que usou? Sua solução pode ajudar outras pessoas que desejam alcançar a mesma coisa.
Manuel Uberti
Eu realmente não usei nenhum código específico, apenas chamei de sintaxe-ppss menos. Você pode conferir o código em questão aqui: bitbucket.org/harsman/dyalog-mode/src/… Procure dyalog-fontify-locals.
Joakim Hårsman
Eu acho que dyalog-fontify-locals-matcherdeveria ser my-font-lock-matchere um dos enddeveria ser limit. Enfim, descoberta realmente interessante!
Lindydancer 31/12/16
@ Lindydancer: Sim, obrigado. Fixo.
Joakim Hårsman
1
Re: gc-cons-thresholdSe você estiver mexendo com valores internos apenas para melhorar o tempo de inicialização, sugiro que você os emacs-startup-hookrestaure posteriormente.
phils