crystal - ¿Hay un aumento en el rendimiento en el uso de comillas simples frente a comillas dobles en ruby?
language crystal (14)
Ciertamente es posible dependiendo de la implementación, pero la parte de escaneo del intérprete solo debe mirar a cada carácter una vez. Solo necesitará un estado adicional (o un posible conjunto de estados) y transiciones para manejar bloques # {}.
En un escáner basado en tablas, esa será una única búsqueda para determinar la transición, y de todas maneras ocurrirá para cada personaje.
Cuando el analizador obtiene la salida del escáner, ya se sabe que tendrá que evaluar el código en el bloque. Entonces, la sobrecarga solo es realmente la sobrecarga de memoria en el escáner / analizador para manejar el bloque # {}, que se paga de cualquier manera.
A menos que me falta algo (o recordar mal los detalles de construcción del compilador), que también es ciertamente posible :)
¿Sabes si el uso de comillas dobles en lugar de comillas simples en rubí reduce el rendimiento de manera significativa en ruby 1.8 y 1.9?
entonces si escribo
question = ''my question''
es mas rapido que
question = "my question"
Me imagino que Ruby intenta averiguar si algo necesita ser evaluado cuando encuentra comillas dobles y probablemente gasta algunos ciclos haciendo exactamente eso.
Hay uno que todos ustedes se perdieron.
AQUÍ doc
prueba esto
require ''benchmark''
mark = <<EOS
a string
EOS
n = 1000000
Benchmark.bm do |x|
x.report("assign here doc") {n.times do; mark; end}
end
Me dio
`asign here doc 0.141000 0.000000 0.141000 ( 0.140625)`
y
''concat single quotes 1.813000 0.000000 1.813000 ( 1.843750)''
''concat double quotes 1.812000 0.000000 1.812000 ( 1.828125)''
por lo que es ciertamente mejor que concat y escribir todas esas puts.
Me gustaría ver a Ruby enseñar más sobre la línea de un lenguaje de manipulación de documentos.
Después de todo, ¿realmente no hacemos eso en Rails, Sinatra y pruebas de funcionamiento?
Intenté lo siguiente:
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
Y estos son los resultados:
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
Si no cometí ningún error, me parece que ambos tardan aproximadamente el mismo tiempo, a pesar de que las citas individuales son ligeramente más rápidas en la mayoría de los casos.
Las comillas dobles toman el doble de ataques de tecla para escribir que las comillas simples. Siempre estoy apurado. Yo uso comillas simples :) Y sí, considero que es una "ganancia de rendimiento". :)
Las comillas simples pueden ser muy ligeramente más rápidas que las comillas dobles porque el lexer no tiene que verificar los marcadores de interpolación #{}
. Dependiendo de la implementación, etc. Tenga en cuenta que esto es un costo de tiempo de análisis, no un costo de tiempo de ejecución.
Dicho esto, la verdadera pregunta era si el uso de cadenas de comillas dobles "disminuye el rendimiento de una manera significativa", a lo que la respuesta es un "no" decisivo. La diferencia en el rendimiento es tan increíblemente pequeña que es completamente insignificante en comparación con cualquier problema de rendimiento real. No pierdas tu tiempo.
La interpolación real es una historia diferente, por supuesto. ''foo''
será casi exactamente 1 segundo más rápido que "#{sleep 1; nil}foo"
.
Modifiqué la respuesta 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)
Nadie pasó a medir concatenación vs interpolación:
$ 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)
Específicamente, note assign interp = 2.62
vs concat single = 3.76
. Como guinda del pastel, también encuentro que la interpolación es más legible que ''a'' + var + ''b''
especialmente con respecto a los espacios.
No hay diferencia, a menos que esté utilizando #{some_var}
cadena de estilo de interpolación. Pero solo obtienes el rendimiento si realmente haces eso.
Modificado Zetetic''s ejemplo de Zetetic''s :
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
salida
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)
No hay una diferencia significativa en ninguna dirección. Tendría que ser enorme para que importara.
Excepto en los momentos en los que esté seguro de que existe un problema real con el tiempo, optimice la capacidad de mantenimiento del programador.
Los costos del tiempo de la máquina son muy muy pequeños. El costo del tiempo del programador para escribir el código y mantenerlo es enorme.
¿De qué sirve una optimización para ahorrar segundos, incluso minutos de tiempo de ejecución en miles de ejecuciones si eso significa que el código es más difícil de mantener?
Elija un estilo y quédese con él, pero no elija ese estilo en función de los milisegundos de tiempo de ejecución estadísticamente insignificantes.
Pensé que agregaría una comparación de 1.8.7 y 1.9.2. Los corrí varias veces. La varianza fue 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 (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)
rubí 1.9.2p0 (revisión 18-08-08 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)
Yo también pensé que las cadenas citadas podrían ser más rápidas de analizar para Ruby. No parece ser el caso.
De todos modos, creo que el punto de referencia anterior está midiendo lo incorrecto, sin embargo. Es lógico que cualquiera de las versiones se analizará en las mismas representaciones internas de cadena, por lo que para obtener una respuesta más rápida de analizar, no deberíamos medir el rendimiento con variables de cadena, sino más bien la velocidad de Ruby de análisis de cadenas.
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
Las carreras repetidas no parecen hacer mucha diferencia. Todavía toma más o menos el mismo tiempo para analizar cualquier versión de la cadena.
Resumen: sin diferencia de velocidad; esta gran guía colaborativa de estilo Ruby recomienda ser consistente. Ahora uso ''string''
menos que sea necesaria la interpolación (opción A en la guía) y me gusta, pero normalmente verá más código con "string"
.
Detalles:
Teóricamente, puede marcar una diferencia cuando se analiza el código, pero no solo no le importa el tiempo de análisis en general (insignificante en comparación con el tiempo de ejecución), no podrá encontrar una diferencia significativa en este caso.
Lo importante es que cuando se ejecuta se hará exactamente lo mismo .
Hacer una evaluación comparativa de esto solo muestra una falta de comprensión de cómo funciona Ruby. En ambos casos, las cadenas se analizarán en tSTRING_CONTENT
(consulte la fuente en parse.y
). En otras palabras, la CPU realizará exactamente las mismas operaciones cuando cree ''string''
o "string"
. Los mismos bits exactamente se voltearán de la misma manera. La evaluación comparativa de esto solo mostrará diferencias que no son significativas y se deben a otros factores (inicio de sesión de la GC, etc.); recuerde, no puede haber ninguna diferencia en este caso! Los micro benchmarks como estos son difíciles de acertar. Ver mi gema fruity
por una herramienta decente para esto.
Tenga en cuenta que si hay interpolación del formulario "...#{...}..."
, esto se analiza en un tSTRING_DBEG
, un grupo de tSTRING_DVAR
para cada expresión en #{...}
y un tSTRING_DEND
final . Sin embargo, eso es solo si hay interpolación, que no es de lo que se trata el OP.
Solía sugerirle que use comillas dobles en todas partes (hace que sea más fácil agregar que #{some_var}
más adelante), pero ahora uso comillas simples a menos que necesite interpolación, /n
, etc ... Me gusta visualmente y es un poco más explícito, ya que no hay necesidad de analizar la cadena para ver si contiene alguna expresión.
$ 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: He actualizado esto para que funcione con las versiones más nuevas de Ruby, y limpié el encabezado, y ejecuté el punto de referencia en un sistema más rápido.
Esta respuesta omite algunos puntos clave. Consulte especialmente estas otras respuestas sobre la interpolation y la razón por la cual no hay una diferencia significativa en el rendimiento al usar comillas simples vs. dobles.
~ > 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)