Como posso melhorar o tempo de inicialização, apesar de muitos pacotes?

19

TL; DR Eu tenho uma quantidade tão grande de pacotes que está prejudicando meu tempo de inicialização. Se você não acredita que possa ser esse o caso, continue lendo.


O tempo de inicialização do meu Emacs é bem pequeno. Eu não uso use-package, apenas ajustei toneladas de ganchos autoloades para que quase todo o código seja adiado. Na realidade, a coisa toda é carregada em menos de meio segundo, apesar de parecer uma bagunça louca.

No entanto, com o tempo, notei que meu tempo de inicialização fica minuciosamente mais lento, inexplicavelmente. Eventualmente, chegou ao ponto em que o tempo de inicialização é ≥ 1 segundo. Eu finalmente tive o suficiente e descobri a raiz do problema. Finalmente, comentei meu ~/.emacsarquivo inteiro e descobri que o tempo de inicialização ainda era ≥ 1 segundo. Na verdade, ele só tinha raspado ~ 0.2segundos, às vezes até menos. Então eu tentei emacs -qe descobri que o tempo de inicialização era ~ 0.1segundos.

Ao examinar esta seção do manual Elisp, descobri por que emacs -qestava reduzindo tanto o tempo de inicialização. Aparentemente, o emacs -qEmacs impede três ações na inicialização:

  1. carregando seu arquivo init
  2. carregando seu default.elarquivo
  3. chamando package-initialize

Já descartamos meu arquivo init, pois comentar todo o processo ~/.emacsnão faz quase nada. Eu não uso um default.elarquivo, então isso também é descartado. O que deixa package-initializecomo o culpado pelo desempenho atingido.

Por que package-initializeconsumir tanto tempo de inicialização? Essa foi a primeira pergunta que me fiz. Não estou carregando automaticamente tudo? Bem, sim. Mas esse é precisamente o problema.

Eu encontrei este post que explica que "ativar" pacotes consiste em ler arquivos de carregamento automático e definir caminhos de carregamento. Obviamente, isso implica em uma penalidade de E / S quando você possui muitos pacotes, porque possui muitos arquivos de carregamento automático para ler e muitos caminhos para definir. Infelizmente, sem isso, a tarefa de gerenciar carregamentos automáticos fica nas mãos do usuário. Em outras palavras, sem deixar package.elrastrear o sistema de arquivos para carregar arquivos e caminhos automaticamente, eu mesmo precisaria gerenciar isso, o que poderia ser um processo tedioso e propenso a erros.

Eu preferiria não seguir esse caminho. Atualmente, tenho 116 pacotes, dos quais 107 do ELPA e 25 dos quais são dependências. Estou certo de que esse número enorme é o que está degradando tanto meu desempenho. Mas estou com um dilema porque não quero remover nenhum dos meus pacotes.

Existe algum remédio em tal situação para recuperar meu tempo de inicialização do raio?


Atualizar:

Iniciamos um novo tópico na emacs-devellista de discussão sobre alguns patches de Stefan Monnier (uma descrição desses patches está aqui ) para resolver esse problema. Qualquer pessoa é bem-vinda para testar seus patches e dar feedback.

Outra atualização:

Parece que Stefan Monnier não está mais interessado nesta questão ou não está recebendo minhas mensagens. Estou inclinado a acreditar no primeiro, o que é bom, embora eu apreciasse algum tipo de resposta dele, se esse for o caso. De qualquer forma, o código que ele produziu para esse problema até agora funciona muito bem. Os patches mais recentes dele podem ser encontrados aqui (para o Emacs 25.3) e aqui (para o ramo principal do Emacs).Eu vi boas melhorias no meu tempo de inicialização graças aos patches dele e estou em um ponto em que me sinto confortável com o meu tempo de inicialização, pois é o mais otimizado possível, sem cortar os recursos da minha personalização. Eu esperava que esses patches chegassem à linha principal do Emacs em algum momento, mas acho que eu (ou outra pessoa) teria que pegar a tocha agora, em vez de Stefan. Tivemos um pouco de dificuldade na lista de discussão sobre atribuição e licenciamento de direitos autorais. Inicialmente, fiquei desconfortável com isso, mas devido a alguns comentários de Richard Stallman e outros, a atribuição de direitos autorais pode não ser tão restritiva quanto eu pensava inicialmente. Além disso, pode ser possível eu comprometer meus trabalhos ao domínio público como alternativa à atribuição de direitos autorais.

De qualquer forma, obrigado Stefan pelos patches até agora! Espero que você continue desenvolvendo essas mudanças, mas se não, tudo bem e que eu possa continuar desenvolvendo em algum momento. Agradeço também a todos que ofereceram insights e contribuições para resolver esse problema.

Mais uma atualização:

Uau, parece que esse recurso finalmente chegou e estará no Emacs 27. Graças a Stefan Monnier!

PIB2
fonte
Ótima pergunta.
Tirou
use-packageé o caminho a percorrer para isso.
Dodgie
Não tentando subestimar o problema (a latência de inicialização é importante!), Mas considere executar o emacs como um daemon / servidor para que você pague apenas pelo custo de inicialização uma vez.
GManNickG 26/01
1
@GManNickG Eu corro o Emacs como um servidor. Infelizmente, de vez em quando eu empurro demais o Emacs ou mexo muito nele, e um reinício é a melhor maneira de limpar as coisas. Quando isso ocorre, eu gosto do meu tempo de inicialização ideal.
GDP2 27/01

Respostas:

13

Uma das opções de design no package.el foi tentar tornar as coisas "simples". Parte disso é que package-initializeprocura todos os pacotes que estão instalados, depois tenta descobrir quais deles devem ser ativados (de acordo com a fixação e a evolução das versões no caso de várias versões do mesmo pacote) e carrega cada <pkg>-autoloads.elarquivo do pacote de ativação .

Portanto, para N pacotes instalados, isso significa basicamente ler N <pkg>-pkg.elarquivos de descrição de pacotes e N <pkg>-autoloads.elarquivos. Para Ns grandes, isso pode se tornar um problema sério. Outro problema potencial de desempenho é que ele adiciona N elementos a load-path, assim, toda vez que o loadEmacs pesquisa nos diretórios N, cada um loadfica mais lento.

Existem várias maneiras de tentar acelerar isso:

  • Forneça uma maneira de pré-calcular um ~/.emacs.d/elpa/package-initialize.el(c)arquivo que seria o resultado da concatenação de todos os direitos <pkg>-autoloads.elna ordem certa. Depois, package-initializebasta carregar esse arquivo quando presente e pular todo o resto. Você precisaria de alguma forma para atualizar / liberar o package-initialize.el(c)arquivo quando os pacotes forem adicionados / atualizados / removidos ou quando você alterar o seu package-pinned-packagesou o seu package-load-list. Eu acho que isso pode ser feito com poucas alterações no sistema (a única coisa que realmente precisaria ser alterada, acho que é package-initializepara que possa ser dito para "somente ativar" sem carregar os metadados dos pacotes disponíveis).

  • Forneça uma maneira de criar / manipular super-pacotes, ou seja, pacote que combine vários pacotes em um (para que apenas um elemento seja adicionado load-path, um <pkg>-pkg.ele um <pkg>-autoloads.elcarregado). Isso pode ser mais difícil de fazer (porque você não pode ativar apenas parte dos pacotes contidos nesses super-pacotes, portanto, a análise de dependência / versão pode ser complicada).

A primeira opção acima deve ser bastante fácil de implementar e tornaria package-initialize muito mais rápida quando você tiver muitos pacotes instalados. Se você estiver interessado em experimentar, não hesite em me pedir ajuda.

FWIW, eu apenas tentei criar um arquivo de mega-carregamento automático "manualmente" na minha configuração de teste. Resultados: enquanto package-initializeleva cerca de 0,9s, o carregamento do mega-autoloads.elarquivo leva 0,3s, que eu posso reduzir para 0,2s, ligando load-source-file-functionpara zero e para 0,1s, compilando o arquivo com byte. Eu esperava uma velocidade melhor, para ser honesto, mas ainda vale a pena.

[EDIT] Esta abordagem de "mega carregamento automático" está agora disponível no ramo principal do Emacs (para se tornar o Emacs-27 em um futuro distante). É controlado pela nova package-quickstartvariável.

Stefan
fonte
Essas são algumas idéias muito interessantes. O primeiro parece mais realista e menos desafiador. O segundo é bastante interessante, mas isso parece um trabalho para os package.eldesenvolvedores. Que tipo de conselho você tem para começar com essa primeira opção? Eu gostaria de ver o que posso resolver com isso, pois parece muito mais viável.
GDP2 26/01
O el-get usa a abordagem de arquivo de carregamento automático único, basicamente funciona na maioria das vezes. Há problemas com alguns pacotes cujos carregamentos automáticos dependem da localização no sistema de arquivos em que são avaliados. Porém, não sigo o que você quer dizer com "na ordem certa", por que o pedido de carregamento automático é importante (não acho que foi até determinístico para o package.el atual)?
npostavs 27/01
@ Stefan, compartilhe a solução aqui, se possível.
Manuel Uberti 27/01
1
@npostavs: A maioria dos <pkg>-autoloads.elarquivos configura apenas carregamentos automáticos e, de fato, não se preocupa com pedidos, mas não há nada que os impeça de fazer outras coisas aleatoriamente, e o package.el garante que o pacote do qual <pkg>depende será ativado antes de <pkg>si.
Stefan
1
Outra atualização sobre esse problema: iniciamos um novo tópico na lista de discussão aqui e qualquer pessoa é livre para comentar ou testar as alterações de Stefan.
GDP2 15/02
6

O problema que você descreve sobre package-initializelevar tanto tempo para carregar é um problema bem conhecido. Esse também é um dos problemas que algumas estruturas do emacs tentam resolver carregando os carregamentos automáticos manualmente.

Eu vejo duas soluções para o seu problema.

  1. Escreva (ou extraia de uma estrutura) a funcionalidade para definir os caminhos e carregar os carregamentos automáticos dos pacotes nos quais você está interessado.
  2. Use uma estrutura que tenha como alvo explicitamente a velocidade. Eu, pessoalmente, recomendo DOOM emacs . Com essa estrutura, estou carregando mais de 200 pacotes em cerca de 1 segundo.

Uma das principais ressonâncias para recomendar o DOOM emacs é que a estrutura coloca o gerenciamento de pacotes fora do emacs. Não me interprete mal, ainda é o emacs que está fazendo o gerenciamento de pacotes, mas o gerenciamento de pacotes é feito fora de uma sessão de usuário padrão. A filosofia aqui é: ao iniciar o emacs normalmente, devemos assumir que todos os pacotes estão presentes e já podem ser carregados. Isso economiza muito tempo. O DOOM emacs fornece o equivalente a apt-getou pacmanpara o emacs. Depois que um pacote é instalado, sempre que o emacs inicia, presume-se que ele já esteja instalado; sem perguntas.

UndeadKernel
fonte
Ufa, fico feliz em saber que esse problema não está apenas me afetando. Obrigado por me indicar o DOOM Emacs. Vou ter que dar uma olhada mais de perto e ver o que posso adaptar à minha própria configuração.
GDP2 26/01
Estou brincando com o DOOM emacs (e contribuindo quando posso) há algum tempo. Se você estiver tendo problemas, não hesite em entrar em contato comigo.
UndeadKernel