Diferenças de desempenho na comparação de símbolos e seqüências de caracteres

7

Em sx.el, temos um caso em que precisamos verificar se passamos GETou POSTcomo argumento.

Atualmente, temos o argumento passando como uma string e estamos usando (string= "GET" url-method)para compará-lo "GET".

Existe alguma vantagem de compilação elisp / byte para alterá-lo para um símbolo (equal url-method 'GET)?

Jonathan Leech-Pepin
fonte
Independentemente da velocidade, os símbolos são idiomáticos para esse fim no Lisp. Além disso, use eqpara comparar com um símbolo.
eqirá converter objetos do Emacs inte compará-los. Comparar ints é muito mais rápido que comparar strings.
abo-abo
11
string=funcionará como esperado, seja url-methodum símbolo ou uma string. OTOH se você usar eqou equal, precisará garantir que os argumentos sejam do mesmo tipo.
YoungFrog

Respostas:

11

Os novos usuários do Lisp que vêm de outros idiomas às vezes usam cadeias para tais fins por hábito.

Os símbolos podem ser comparados ao eqinvés de apenas equal. string=usa código semelhante a equal, para testar cada caractere até encontrar uma incompatibilidade. Então, sim, a comparação de símbolos pode ser um pouco mais rápida. Se você estiver usando a comparação em um loop, por exemplo, a diferença pode ser importante.

Além disso, dependendo do código específico, muitas vezes pode tornar o código mais simples ou mais legível para usar símbolos em vez de cadeias. E há funções (e macros etc.) que esperam símbolos ou que comparam coisas usando eq(ou eql) por padrão. Para usar aqueles com strings, você precisa converter em símbolos.

Desenhou
fonte
7

Observe que o lisp reader estende símbolos, de forma que as referências independentes a um determinado símbolo fornecem exatamente o mesmo objeto lisp e, consequentemente, você é capaz de compará-los eq(que só precisa comparar os endereços dos objetos).

Por outro lado, strings independentes são sempre objetos lisp diferentes e, portanto, você deve comparar o conteúdo deles.

Portanto, você esperaria que a eqcomparação ganhasse pelo desempenho.

Curiosamente (estou certamente muito surpreso), alguns testes triviais benchmark-runestão dando string=a vitória por uma margem bastante. Isso me parece muito estranho. YMMV?


Edit: Então, eu apenas notei essa resposta (e seu comentário) novamente e me senti inspirado para ver se eu poderia recriar e explicar o resultado.

Nota: nada é compilado em bytes inicialmente.

A primeira constatação foi que meus símbolos eram quoted e as seqüências não eram, e imediatamente descobri que quoteera o responsável pela maior parte da diferença de velocidade:

(benchmark-run 10000000
  (string= "foo" "foo"))

é, para cadeias pequenas, consistentemente mais rápido que:

(benchmark-run 10000000
  (eq 'foo 'foo))

no entanto, se também citarmos a string:

(benchmark-run 10000000
  (string= '"foo" '"foo"))

que quase equilibra completamente as coisas.

No entanto, em média, a menos que a corda seja muito grande, a comparação de cordas ainda parece ganhar por um fio de cabelo.

Uma abordagem alternativa é vincular os objetos a variáveis:

(let ((foo 'test)
      (bar 'test))
  (benchmark-run 10000000
    (eq foo bar)))

(let ((foo "test")
      (bar "test"))
  (benchmark-run 10000000
    (string= foo bar)))

Mais uma vez, estou vendo a comparação das cordas mais rapidamente, em média.

Após a compilação de bytes, vejo apenas um resultado consistente para os casos de variáveis ​​vinculadas, onde eqé consistentemente mais rápido do que string=(demorando cerca de 2/3 do tempo).

Para os outros exemplos, estou obtendo resultados sem sentido (para mim), como as seqüências de caracteres citadas, sendo mais rápidas do que as seqüências de caracteres não citadas, o que só posso supor é efetivamente ruído de outros aspectos da execução e / ou de benchmark-runsi próprio. Havia variedade suficiente entre execuções diferentes da mesma função para ocultar completamente quaisquer diferenças entre execuções de funções diferentes.


Minhas conclusões são:

(a) eqcomparações com um símbolo podem (de maneira um pouco contra-intuitiva) ser mais lenta que uma comparação de cadeias, se o símbolo for citado.

(b) A menos que as strings sejam bastante grandes, as diferenças práticas são totalmente desprezíveis e, portanto, eu não me incomodaria em converter uma na outra por razões puramente de desempenho.

phils
fonte
3
Possivelmente porque do jeito que você tenha configurado seu teste faz com que cada símbolo a ser gerado de novo em cada execução do loop, assim que você está colocando intern+ eqcontra string=. Ou possivelmente por algum motivo completamente diferente: é impossível saber sem ver seu código. Você deve postar seu código de teste como uma pergunta neste site.
Gilles 'SO- stop be evil'
Tentei usar benchmark-run-compiled(com a ideia de que isso reduziria o custo de intern), mas várias vezes obtive tempos de resultados negativos. Eu acho que as duas operações são muito rápidas para medir com segurança.
N