Existe um ganho de desempenho no uso de aspas simples versus aspas duplas em ruby?


126

Você sabe se o uso de aspas duplas em vez de aspas simples no ruby ​​diminui o desempenho de alguma maneira significativa no ruby ​​1.8 e 1.9.

então se eu digitar

question = 'my question'

é mais rápido que

question = "my question"

Imagino que o ruby ​​tente descobrir se algo precisa ser avaliado quando encontrar aspas duplas e provavelmente gasta alguns ciclos fazendo exatamente isso.


17
Execute meio milhão de vezes e veja. Provavelmente, seu site não recebe tráfego suficiente para importar. A otimização prematura geralmente não vale a pena.
ceejayoz

60
por que tantas pessoas esperam que o ruby ​​seja usado apenas para programação na web?
Johannes

17
Eu não consideraria essa otimização prematura. Mais uma "prática recomendada", uma vez que voltar após a conclusão do seu aplicativo e otimizar para um ou outro, seria uma dor de cabeça enorme.
Omar

7
Para mim, é apenas estilo: uso aspas simples para strings 'estáticos' e qoutes duplos (ou outras strings interpoladas) em outros casos.
tig

3
@Baddie: É otimização prematura se você estiver otimizando um problema que não existe.
Andy Lester

Respostas:


86
$ ruby -v
ruby 1.9.3p0 (2011-10-30 revision 33570) [x86_64-darwin11.0.0]

$ cat benchmark_quotes.rb
# As of Ruby 1.9 Benchmark must be required
require 'benchmark'

n = 1000000
Benchmark.bm(15) do |x|
  x.report("assign single") { n.times do; c = 'a string'; end}
  x.report("assign double") { n.times do; c = "a string"; end}
  x.report("concat single") { n.times do; 'a string ' + 'b string'; end}
  x.report("concat double") { n.times do; "a string " + "b string"; end}
end

$ ruby benchmark_quotes.rb 

                      user     system      total        real
assign single     0.110000   0.000000   0.110000 (  0.116867)
assign double     0.120000   0.000000   0.120000 (  0.116761)
concat single     0.280000   0.000000   0.280000 (  0.276964)
concat double     0.270000   0.000000   0.270000 (  0.278146)

Nota: Eu atualizei isso para fazê-lo funcionar com as versões mais recentes do Ruby, limpei o cabeçalho e execute a referência em um sistema mais rápido.

Esta resposta omite alguns pontos-chave. Veja especialmente essas outras respostas sobre interpolação e a razão pela qual não há diferença significativa no desempenho ao usar aspas simples e duplas.


Estou interpretando os resultados corretamente? A atribuição usando aspas duplas é realmente mais rápida que simples? Como isso pode ser?
Aleatório

Aparentemente sim, embora a diferença seja pequena. Quanto ao porquê - me bate.
Zetetic

Essa referência seria muito mais atraente se levasse em consideração o tempo de compilação e o tempo de execução.
nohat

9
As diferenças medidas não são significativas. Apenas o pedido (devido à coleta de lixo) pode fazer uma diferença importante. Não há diferença de tempo de execução entre 'e "como eles são analisados ​​para a mesma coisa.
Marc-André Lafortune

104

Resumo: sem diferença de velocidade; este ótimo guia colaborativo de estilo Ruby recomenda ser consistente. Agora uso a 'string'menos que seja necessária interpolação (opção A no guia) e goste, mas você normalmente verá mais código com "string".

Detalhes:

Teoricamente, pode fazer a diferença quando seu código é analisado , mas você não deve se preocupar apenas com o tempo de análise em geral (desprezível em comparação com o tempo de execução), como também não poderá encontrar uma diferença significativa nesse caso.

O importante é que, quando for executado , será exatamente o mesmo .

O benchmarking mostra apenas uma falta de entendimento de como o Ruby funciona. Nos dois casos, as seqüências serão analisadas para a tSTRING_CONTENT(consulte a fonte emparse.y ). Em outras palavras, a CPU passará exatamente pelas mesmas operações ao criar 'string'ou "string". Os mesmos bits exatos mudarão exatamente da mesma maneira. O benchmarking mostra apenas diferenças que não são significativas e são devidas a outros fatores (entrada do GC, etc.) lembre-se, não pode haver nenhuma diferença neste caso! Micro benchmarks como esses são difíceis de acertar. Veja minha joia fruitypara uma ferramenta decente para isso.

Observe que, se houver interpolação do formulário "...#{...}...", isso será analisado para a tSTRING_DBEG, um monte de tSTRING_DVARpara cada expressão em #{...}e uma final tSTRING_DEND. Isso é apenas se houver interpolação, o que não é o objetivo do OP.

Eu costumava sugerir que você use aspas duplas em todos os lugares (torna mais fácil adicioná-las #{some_var}posteriormente), mas agora uso aspas simples, a menos que precise de interpolação \n, etc ... Eu gosto visualmente e é um pouco mais explícito, pois não há precisa analisar a string para ver se ela contém alguma expressão.


3
Parece muito mais importante que a pequena diferença de desempenho. Aspas duplas é!
Venkat D.

Obrigado por me indicar sua resposta. Você poderia esclarecer por que está dizendo que o benchmarking é enganoso? Concordo que as diferenças são provavelmente insignificantes, mas o benchmark de alguma forma está errado? (Alguém já destacou #{n}que estaria fazendo a conversão de números). Não está mostrando as diferenças na análise ?.
PhilT

1
Obrigado por vincular ao guia de estilo. Não posso acreditar que não me deparei com isso antes.
PhilT

1
O guia de estilo mencionado em sua resposta foi atualizado para sugerir a adoção de um estilo consistente, seja com aspas simples ou duplas, e indica que seqüências de caracteres entre aspas duplas são mais prevalentes na comunidade Ruby.
philtr

Use aspas duplas. A programação é difícil. A sintaxe é inerentemente complexa. Aspas duplas significam nunca cometer um erro ou perder tempo com um erro ao dinamizar uma string. Com aspas duplas, você tem menos uma coisa em que pensar.
Kelsey Hannan

35

Porém, ninguém mediu concatenação x interpolação:

$ ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [i686-darwin9.6.2]
$ cat benchmark_quotes.rb
require 'benchmark'
n = 1000000
Benchmark.bm do |x|
  x.report("assign single") { n.times do; c = 'a string'; end}
  x.report("assign double") { n.times do; c = "a string"; end}
  x.report("assign interp") { n.times do; c = "a string #{'b string'}"; end}
  x.report("concat single") { n.times do; 'a string ' + 'b string'; end}
  x.report("concat double") { n.times do; "a string " + "b string"; end}
end

$ ruby -w benchmark_quotes.rb 
      user     system      total        real
assign single  2.600000   1.060000   3.660000 (  3.720909)
assign double  2.590000   1.050000   3.640000 (  3.675082)
assign interp  2.620000   1.050000   3.670000 (  3.704218)
concat single  3.760000   1.080000   4.840000 (  4.888394)
concat double  3.700000   1.070000   4.770000 (  4.818794)

Especificamente, observe assign interp = 2.62vs concat single = 3.76. Como cereja no topo do bolo, também acho a interpolação mais legível do que 'a' + var + 'b'especialmente no que diz respeito aos espaços.


+1. Este é o único benchmark de interpolação que compara maçãs com maçãs.
Mark Thomas

1
O benchmarking pode ser enganoso; veja minha resposta para o porquê. Quanto à comparação entre concatenação e interpolação, deve ser óbvio que a interpolação não pode ser mais lenta que a concatenação. De qualquer forma, isso não é realmente parte da questão!
Marc-André Lafortune

Você pode adicionar << a este teste?
28413 Nick

16

Não há diferença - a menos que você esteja usando a #{some_var}interpolação de string de estilo. Mas você só obtém o desempenho atingido se realmente fizer isso.

Modificado a partir do exemplo da Zetetic :

require 'benchmark'
n = 1000000
Benchmark.bm do |x|
  x.report("assign single") { n.times do; c = 'a string'; end}
  x.report("assign double") { n.times do; c = "a string"; end}
  x.report("assign interp") { n.times do; c = "a #{n} string"; end}  
  x.report("concat single") { n.times do; 'a string ' + 'b string'; end}
  x.report("concat double") { n.times do; "a string " + "b string"; end}
  x.report("concat interp") { n.times do; "a #{n} string " + "b #{n} string"; end}
end

resultado

               user       system     total    real
assign single  0.370000   0.000000   0.370000 (  0.374599)
assign double  0.360000   0.000000   0.360000 (  0.366636)
assign interp  1.540000   0.010000   1.550000 (  1.577638)
concat single  1.100000   0.010000   1.110000 (  1.119720)
concat double  1.090000   0.000000   1.090000 (  1.116240)
concat interp  3.460000   0.020000   3.480000 (  3.535724)

Interessante. A interpolação parece um pouco mais cara. Isso foi 1.8? Seria bom ver se 1.9 muda alguma coisa.
zetetic

zetético - sim. Isso foi contra o Ruby 1.8.7 #
madlep 04/12/2009

1
A versão interp interpola e concatena, além de converter um número em uma string duas vezes. A interpolação vence se você fizer os mesmos resultados. Consulte gist.github.com/810463 . O verdadeiro argumento é se preocupar mais com to_s do que com aspas simples ou duplas.
Brian Deterling

O benchmarking apenas pode ser enganoso e mostra um mal-entendido de como o Ruby funciona. Veja minha resposta.
Marc-André Lafortune

13

As aspas simples podem ser muito mais rápidas que as aspas duplas, porque o lexer não precisa procurar #{}marcadores de interpolação. Dependendo da implementação, etc. Observe que esse é um custo de tempo de análise, não um custo de tempo de execução.

Dito isso, a questão real era se o uso de strings com aspas duplas "diminui o desempenho de maneira significativa", para o qual a resposta é um "não" decisivo. A diferença de desempenho é tão incrivelmente pequena que é completamente insignificante em comparação com quaisquer preocupações reais de desempenho. Não perca seu tempo.

A interpolação real é uma história diferente, é claro. 'foo'será quase exatamente 1 segundo mais rápido que "#{sleep 1; nil}foo".


4
+1 por observar que o custo está no tempo de compilação e não no tempo de execução; portanto, as respostas baseadas em benchmarks altamente votadas acima são enganosas.
nohat

"este é um custo de tempo de análise, não um custo de tempo de execução." é a frase chave.
the Tin Man

9

As aspas duplas levam o dobro do número de pressionamentos de tecla para digitar do que aspas simples. Estou sempre com pressa. Eu uso aspas simples. :) E sim, considero isso um "ganho de desempenho". :)


Por que aspas duplas receberiam o dobro das batidas de tecla? Ambos são representados por uma única chave. Além disso, muitos IDEs adicionam as cotações de fechamento automaticamente.
precisa

3
Mesmo se o IDE fechar automaticamente a cotação, as aspas duplas ainda exigirão 100% mais pressionamentos de tecla. ;-)
Clint Pachl

Matt Dressel: aspas duplas requer o dobro do número de pressionamentos de tecla porque você também precisa pressionar a tecla Shift. Oh: :) apenas no caso de você ter perdido no meu comentário original. :) As teclas com fio requerem mais esforço e, sem dúvida, mais tempo para serem executadas. :)
aqn 30/08/13

1
Às vezes, sigo esse conselho por preguiça. Mas, infelizmente, em alguns outros idiomas, é o contrário (por exemplo, aspas simples precisam de Shift + algo, enquanto aspas duplas são um único pressionamento de tecla). Lamentável, porque, se duas pessoas com diferentes layouts de teclado trabalhar no mesmo projeto, um deles terá que sacrificar algumas teclas digitadas :)
Halil Özgür

"Sou um homem com pressa" - A menos que você pressione Shift e 2 (ou seja qual for a outra tecla), uma após a outra, você não economiza tempo usando aspas simples.
Machisuji

8

Pensei em adicionar uma comparação entre 1.8.7 e 1.9.2. Eu os corri algumas vezes. A variação foi de cerca de + -0,01.

require 'benchmark'
n = 1000000
Benchmark.bm do |x|
  x.report("assign single") { n.times do; c = 'a string'; end}
  x.report("assign double") { n.times do; c = "a string"; end}
  x.report("assign interp") { n.times do; c = "a #{n} string"; end}
  x.report("concat single") { n.times do; 'a string ' + 'b string'; end}
  x.report("concat double") { n.times do; "a string " + "b string"; end}
  x.report("concat interp") { n.times do; "a #{n} string " + "b #{n} string"; end}
end

ruby 1.8.7 (nível de patch 302 2010-08) [x86_64-linux]

assign single  0.180000   0.000000   0.180000 (  0.187233)
assign double  0.180000   0.000000   0.180000 (  0.187566)
assign interp  0.880000   0.000000   0.880000 (  0.877584)
concat single  0.550000   0.020000   0.570000 (  0.567285)
concat double  0.570000   0.000000   0.570000 (  0.570644)
concat interp  1.800000   0.010000   1.810000 (  1.816955)

ruby 1.9.2p0 (18/08/2010 revisão 29036) [x86_64-linux]

  user          system      total      real
assign single  0.140000   0.000000   0.140000 (  0.144076)
assign double  0.130000   0.000000   0.130000 (  0.142316)
assign interp  0.650000   0.000000   0.650000 (  0.656088)
concat single  0.370000   0.000000   0.370000 (  0.370663)
concat double  0.370000   0.000000   0.370000 (  0.370076)
concat interp  1.420000   0.000000   1.420000 (  1.412210)

Interp está tendo que fazer número para converter conversões. Consulte gist.github.com/810463 .
Brian Deterling

Veja minha resposta sobre por que você recebe esses números.
Marc-André Lafortune

Bom ponto sobre o Interp. Acabei de copiar a resposta anterior como base para a minha. Isso vai me ensinar.
PhilT

3

Não há diferença significativa em nenhuma direção. Teria que ser enorme para que isso importasse.

Exceto nos momentos em que você tem certeza de que há um problema real com o tempo, otimize a manutenção do programador.

Os custos do tempo da máquina são muito muito pequenos. Os custos do tempo do programador para escrever e mantê-lo são enormes.

Qual a utilidade de uma otimização para economizar segundos e até minutos de execução em milhares de execuções, se isso significa que o código é mais difícil de manter?

Escolha um estilo e mantenha-o, mas não escolha esse estilo com base em milissegundos de tempo de execução estatisticamente insignificantes.


1

Eu também pensei que seqüências de caracteres entre aspas simples poderiam ser mais rápidas para analisar Ruby. Não parece ser o caso.

De qualquer forma, acho que o benchmark acima está medindo a coisa errada. É lógico que ambas as versões serão analisadas nas mesmas representações internas de cadeia de caracteres. Para obter a resposta mais rápida, não devemos medir o desempenho com variáveis ​​de cadeia, mas a velocidade de Ruby de analisar cadeias.

generate.rb: 
10000.times do
  ('a'..'z').to_a.each {|v| print "#{v}='This is a test string.'\n" }
end

#Generate sample ruby code with lots of strings to parse
$ ruby generate.rb > single_q.rb
#Get the double quote version
$ tr \' \" < single_q.rb > double_q.rb

#Compare execution times
$ time ruby single_q.rb 

real    0m0.978s
user    0m0.920s
sys     0m0.048s
$ time ruby double_q.rb 

real    0m0.994s
user    0m0.940s
sys     0m0.044s

Execuções repetidas não parecem fazer muita diferença. Ainda leva praticamente o mesmo tempo para analisar qualquer versão da string.


0

Certamente é possível, dependendo da implementação, mas a parte de varredura do intérprete deve olhar apenas para cada caractere uma vez. Será necessário apenas um estado adicional (ou um conjunto possível de estados) e transições para manipular # {} blocos.

Em um scanner baseado em tabela, haverá uma única pesquisa para determinar a transição e, de qualquer maneira, acontecerá para cada personagem.

Quando o analisador obtém a saída do scanner, já é sabido que ele terá que avaliar o código no bloco. Portanto, a sobrecarga é apenas a sobrecarga de memória no scanner / analisador para manipular o bloco # {}, pelo qual você paga de qualquer maneira.

A menos que esteja faltando alguma coisa (ou lembrando os detalhes da construção do compilador), o que também é certamente possível :)


0
~ > ruby -v   
jruby 1.6.7 (ruby-1.8.7-p357) (2012-02-22 3e82bc8) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_37) [darwin-x86_64-java]
~ > cat qu.rb 
require 'benchmark'

n = 1000000
Benchmark.bm do |x|
  x.report("assign single") { n.times do; c = 'a string'; end}
  x.report("assign double") { n.times do; c = "a string"; end}
  x.report("concat single") { n.times do; 'a string ' + 'b string'; end}
  x.report("concat double") { n.times do; "a string " + "b string"; end}
end
~ > ruby qu.rb
      user     system      total        real
assign single  0.186000   0.000000   0.186000 (  0.151000)
assign double  0.062000   0.000000   0.062000 (  0.062000)
concat single  0.156000   0.000000   0.156000 (  0.156000)
concat double  0.124000   0.000000   0.124000 (  0.124000)

0

Todos vocês sentiram falta de um.

AQUI doc

tente isso

require 'benchmark'
mark = <<EOS
a string
EOS
n = 1000000
Benchmark.bm do |x|
  x.report("assign here doc") {n.times do;  mark; end}
end

Deu-me

`asign here doc  0.141000   0.000000   0.141000 (  0.140625)`

e

'concat single quotes  1.813000   0.000000   1.813000 (  1.843750)'
'concat double quotes  1.812000   0.000000   1.812000 (  1.828125)'

por isso é certamente melhor do que concat e escrever todas essas opções.

Eu gostaria de ver Ruby ensinado mais ao longo das linhas de uma linguagem de manipulação de documentos.

Afinal, nós realmente não fazemos isso no Rails, Sinatra e na execução de testes?


0

Modifiquei a resposta de Tim Snowhite.

require 'benchmark'
n = 1000000
attr_accessor = :a_str_single, :b_str_single, :a_str_double, :b_str_double
@a_str_single = 'a string'
@b_str_single = 'b string'
@a_str_double = "a string"
@b_str_double = "b string"
@did_print = false
def reset!
    @a_str_single = 'a string'
    @b_str_single = 'b string'
    @a_str_double = "a string"
    @b_str_double = "b string"
end
Benchmark.bm do |x|
    x.report('assign single       ') { n.times do; c = 'a string'; end}
    x.report('assign via << single') { c =''; n.times do; c << 'a string'; end}
    x.report('assign double       ') { n.times do; c = "a string"; end}
    x.report('assing interp       ') { n.times do; c = "a string #{'b string'}"; end}
    x.report('concat single       ') { n.times do; 'a string ' + 'b string'; end}
    x.report('concat double       ') { n.times do; "a string " + "b string"; end}
    x.report('concat single interp') { n.times do; "#{@a_str_single}#{@b_str_single}"; end}
    x.report('concat single <<    ') { n.times do; @a_str_single << @b_str_single; end}
    reset!
    # unless @did_print
    #   @did_print = true
    #   puts @a_str_single.length 
    #   puts " a_str_single: #{@a_str_single} , b_str_single: #{@b_str_single} !!"
    # end
    x.report('concat double interp') { n.times do; "#{@a_str_double}#{@b_str_double}"; end}
    x.report('concat double <<    ') { n.times do; @a_str_double << @b_str_double; end}
end

Resultados:

jruby 1.7.4 (1.9.3p392) 2013-05-16 2390d3b on Java HotSpot(TM) 64-Bit Server VM 1.7.0_10-b18 [darwin-x86_64]
       user     system      total        real
assign single         0.220000   0.010000   0.230000 (  0.108000)
assign via << single  0.280000   0.010000   0.290000 (  0.138000)
assign double         0.050000   0.000000   0.050000 (  0.047000)
assing interp         0.100000   0.010000   0.110000 (  0.056000)
concat single         0.230000   0.010000   0.240000 (  0.159000)
concat double         0.150000   0.010000   0.160000 (  0.101000)
concat single interp  0.170000   0.000000   0.170000 (  0.121000)
concat single <<      0.100000   0.000000   0.100000 (  0.076000)
concat double interp  0.160000   0.000000   0.160000 (  0.108000)
concat double <<      0.100000   0.000000   0.100000 (  0.074000)

ruby 1.9.3p429 (2013-05-15 revision 40747) [x86_64-darwin12.4.0]
       user     system      total        real
assign single         0.100000   0.000000   0.100000 (  0.103326)
assign via << single  0.160000   0.000000   0.160000 (  0.163442)
assign double         0.100000   0.000000   0.100000 (  0.102212)
assing interp         0.110000   0.000000   0.110000 (  0.104671)
concat single         0.240000   0.000000   0.240000 (  0.242592)
concat double         0.250000   0.000000   0.250000 (  0.244666)
concat single interp  0.180000   0.000000   0.180000 (  0.182263)
concat single <<      0.120000   0.000000   0.120000 (  0.126582)
concat double interp  0.180000   0.000000   0.180000 (  0.181035)
concat double <<      0.130000   0.010000   0.140000 (  0.128731)

0

Eu tentei o seguinte:

def measure(t)
  single_measures = []
  double_measures = []
  double_quoted_string = ""
  single_quoted_string = ''
  single_quoted = 0
  double_quoted = 0

  t.times do |i|
    t1 = Time.now
    single_quoted_string << 'a'
    t1 = Time.now - t1
    single_measures << t1

    t2 = Time.now
    double_quoted_string << "a"
    t2 = Time.now - t2
    double_measures << t2

    if t1 > t2 
      single_quoted += 1
    else
      double_quoted += 1
    end
  end
  puts "Single quoted did took longer in #{((single_quoted.to_f/t.to_f) * 100).round(2)} percent of the cases"
  puts "Double quoted did took longer in #{((double_quoted.to_f/t.to_f) * 100).round(2)} percent of the cases"

  single_measures_avg = single_measures.inject{ |sum, el| sum + el }.to_f / t
  double_measures_avg = double_measures.inject{ |sum, el| sum + el }.to_f / t
  puts "Single did took an average of #{single_measures_avg} seconds"
  puts "Double did took an average of #{double_measures_avg} seconds"
    puts "\n"
end
both = 10.times do |i|
  measure(1000000)
end

E estas são as saídas:

1

Single quoted did took longer in 32.33 percent of the cases
Double quoted did took longer in 67.67 percent of the cases
Single did took an average of 5.032084099982639e-07 seconds
Double did took an average of 5.171539549983464e-07 seconds

2)

Single quoted did took longer in 26.9 percent of the cases
Double quoted did took longer in 73.1 percent of the cases
Single did took an average of 4.998066229983696e-07 seconds
Double did took an average of 5.223457359986066e-07 seconds

3)

Single quoted did took longer in 26.44 percent of the cases
Double quoted did took longer in 73.56 percent of the cases
Single did took an average of 4.97640888998877e-07 seconds
Double did took an average of 5.132918459987151e-07 seconds

4)

Single quoted did took longer in 26.57 percent of the cases
Double quoted did took longer in 73.43 percent of the cases
Single did took an average of 5.017136069985988e-07 seconds
Double did took an average of 5.004514459988143e-07 seconds

5)

Single quoted did took longer in 26.03 percent of the cases
Double quoted did took longer in 73.97 percent of the cases
Single did took an average of 5.059069689983285e-07 seconds
Double did took an average of 5.028807639983705e-07 seconds

6

Single quoted did took longer in 25.78 percent of the cases
Double quoted did took longer in 74.22 percent of the cases
Single did took an average of 5.107472039991399e-07 seconds
Double did took an average of 5.216212339990241e-07 seconds

7)

Single quoted did took longer in 26.48 percent of the cases
Double quoted did took longer in 73.52 percent of the cases
Single did took an average of 5.082368429989468e-07 seconds
Double did took an average of 5.076817109989933e-07 seconds

8)

Single quoted did took longer in 25.97 percent of the cases
Double quoted did took longer in 74.03 percent of the cases
Single did took an average of 5.077162969990005e-07 seconds
Double did took an average of 5.108381859991112e-07 seconds

9

Single quoted did took longer in 26.28 percent of the cases
Double quoted did took longer in 73.72 percent of the cases
Single did took an average of 5.148080479983138e-07 seconds
Double did took an average of 5.165793929982176e-07 seconds

10)

Single quoted did took longer in 25.03 percent of the cases
Double quoted did took longer in 74.97 percent of the cases
Single did took an average of 5.227828659989748e-07 seconds
Double did took an average of 5.218296609988378e-07 seconds

Se não me enganei, parece-me que os dois demoram aproximadamente o mesmo tempo, embora o único citado seja um pouco mais rápido na maioria dos casos.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.