web-dev-qa-db-fra.com

L'utilisation de guillemets simples et de guillemets doubles en ruby ​​présente-t-elle un gain de performance?

Savez-vous que l'utilisation de guillemets doubles au lieu de guillemets simples dans Ruby diminue les performances de manière significative dans Ruby 1.8 et 1.9.

donc si je tape 

question = 'my question'

est-ce plus rapide que 

question = "my question"

J'imagine que Ruby essaie de déterminer si quelque chose doit être évalué lorsqu'elle rencontre des guillemets et passe probablement quelques cycles à le faire.

121
dimus
$ 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)

Remarque: j'ai mis à jour cette fonctionnalité pour la faire fonctionner avec les nouvelles versions de Ruby. J'ai également nettoyé l'en-tête et exécuté le test de performance sur un système plus rapide. 

Cette réponse omet certains points clés. Voir en particulier ces autres réponses concernant l'interpolation et la raison il n'y a pas de différence significative de performances lorsque vous utilisez des guillemets simples ou doubles.

85
zetetic

Résumé: pas de différence de vitesse; this excellent guide de style collaboratif Ruby recommande d’être cohérent. J'utilise maintenant 'string' sauf si une interpolation est nécessaire (option A dans le guide) et je l'aime bien, mais vous verrez généralement plus de code avec "string".

Détails:

Théoriquement, cela peut faire une différence lorsque votre code est analysé, mais pas seulement si vous ne vous souciez pas du temps d'analyse en général (négligeable par rapport au temps d'exécution), vous ne pourrez pas trouver de différence significative dans ce cas.

La chose importante est que quand est obtenu exécuté ce sera exactement le même.

L'analyse comparative montre seulement un manque de compréhension du fonctionnement de Ruby. Dans les deux cas, les chaînes seront analysées avec un tSTRING_CONTENT (voir la source dans parse.y ). En d'autres termes, la CPU effectuera exactement les mêmes opérations lors de la création de 'string' ou "string". Les mêmes bits inverseront exactement la même chose. L'analyse comparative ne montrera que les différences non significatives et dues à d'autres facteurs (entrée en jeu du GC, etc.); Rappelez-vous, il ne peut y avoir aucune différence dans ce cas! Il est difficile d’obtenir des résultats tels que les micro-critères. Voir ma gem fruity pour un outil décent pour cela.

Notez que s'il existe une interpolation de la forme "...#{...}...", elle est analysée en un tSTRING_DBEG, un groupe de tSTRING_DVAR pour chaque expression dans #{...} et une finale tSTRING_DEND. Ce n’est que s’il ya interpolation, ce qui n’est pas l’objet du PO.

J'avais l'habitude de suggérer d'utiliser des guillemets partout (ce qui facilite l'ajout de ce #{some_var} plus tard), mais j'utilise maintenant des guillemets simples sauf si j'ai besoin d'interpolation, de \n, etc. il n'est pas nécessaire d'analyser la chaîne pour voir si elle contient une expression.

102

Cependant, personne ne mesurait la concaténation vs l'interpolation:

$ 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)

Plus précisément, notez assign interp = 2.62 vs concat single = 3.76. Cerise sur le gâteau, je trouve également que l’interpolation est plus lisible que 'a' + var + 'b', en particulier en ce qui concerne les espaces.

35
Tim Snowhite

Aucune différence - sauf si vous utilisez une interpolation de chaîne de style #{some_var}. Mais vous n'obtenez les performances que si vous le faites réellement.

Modifié à partir de de Zetetic exemple:

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

sortie

               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)
16
madlep

Les guillemets simples peuvent être très légèrement plus rapides que les guillemets doubles car le lexer n'a pas à vérifier les marqueurs d'interpolation #{}. En fonction de l'implémentation, etc. Notez qu'il s'agit d'un coût d'analyse, et non d'un coût d'exécution.

Cela dit, la véritable question était de savoir si l’utilisation de chaînes entre guillemets "diminuait la performance de manière significative", à laquelle la réponse est un "non" décisif. La différence de performance est tellement infime qu’elle est totalement insignifiante par rapport à de véritables problèmes de performance. Ne perdez pas votre temps.

L'interpolation réelle est une histoire différente, bien sûr. 'foo' sera presque exactement 1 seconde plus rapide que "#{sleep 1; nil}foo".

13
Rein Henrichs

Je pensais ajouter une comparaison entre 1.8.7 et 1.9.2. Je les ai courus quelques fois. La variance était d'environ + -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 (2010-08-16 patchlevel 302) [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 (révision 2010-08-18 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)
8
PhilT

Les guillemets doubles nécessitent deux fois plus de combinaisons de touches que les guillemets simples. Je suis toujours pressé. J'utilise des guillemets simples. :) Et oui, je considère cela comme un "gain de performance". :)

8
aqn

Il n'y a pas de différence significative dans les deux sens. Il faudrait que ce soit énorme pour que cela compte.

Sauf dans les cas où vous êtes sûr qu'il existe un problème réel de synchronisation, optimisez la maintenabilité du programmeur.

Les coûts de temps machine sont très très faibles. Les coûts de temps de programmation pour écrire du code et le maintenir sont énormes.

A quoi sert une optimisation pour économiser des secondes, voire des minutes d’exécution sur des milliers d’exécution, si cela signifie que le code est plus difficile à maintenir?

Choisissez un style et respectez-le, mais not choisissez ce style en fonction de millisecondes non significatives sur le plan statistique.

3
Andy Lester

Je pensais aussi que les chaînes entre guillemets pourraient être plus rapides à analyser pour Ruby. Cela ne semble pas être le cas. 

Quoi qu'il en soit, je pense que les références ci-dessus mesurent la mauvaise chose, cependant… .. Il va de soi que l'une ou l'autre version sera analysée dans les mêmes représentations de chaînes internes, de manière à obtenir la réponse quant à celle qui est la plus rapide à analyser, nous ne devrions pas. ne pas mesurer les performances avec des variables de chaîne, mais plutôt la vitesse d’analyse des chaînes de Ruby.

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

Les courses répétées ne semblent pas faire beaucoup de différence. Il faut encore à peu près le même temps pour analyser l'une ou l'autre version de la chaîne.

1
PSkocik

Cela est certainement possible en fonction de l'implémentation, mais la partie numérisation de l'interprète ne doit examiner chaque caractère qu'une seule fois. Il faudra simplement un état supplémentaire (ou un ensemble d'états possible) et des transitions pour gérer les blocs # {}. 

Dans un scanner basé sur une table, ce sera une simple recherche pour déterminer la transition, et se produira de toute façon pour chaque personnage.

Lorsque l'analyseur obtient la sortie du scanner, il est déjà connu qu'il devra évaluer le code dans le bloc. Ainsi, la surcharge est uniquement la surcharge de mémoire du scanner/analyseur qui gère le bloc # {}, que vous payez de toute façon.

À moins que quelque chose ne me manque (ou que je me souvienne mal des détails de la construction du compilateur), ce qui est également certainement possible :)

0
µBio

J'ai essayé ce qui suit:

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

Et ce sont les sorties:

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

dix.

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

Si je ne me trompe pas, il me semble que les deux prennent à peu près le même temps, même si un seul cité est légèrement plus rapide dans la plupart des cas.

0
Marcelo Xavier
~ > 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
grilix

J'ai modifié la réponse 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

Résultats:

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
Nick

Il y en a un que vous avez tous manqué.

ICI doc

essaye ça

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

Cela m'a donné

`asign here doc  0.141000   0.000000   0.141000 (  0.140625)`

et

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

donc c'est certainement mieux que concat et écrire toutes ces options.

J'aimerais que Ruby enseigne davantage comme un langage de manipulation de documents.

Après tout, ne faisons-nous pas cela dans Rails, Sinatra et lors de tests?

0
Douglas G. Allen