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.
$ 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.
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.
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.
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)
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"
.
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)
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". :)
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.
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.
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 :)
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.
~ > 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)
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)
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?