¿Cuándo usar símbolos en lugar de cadenas en Ruby?

98

Si hay al menos dos instancias de la misma cadena en mi secuencia de comandos, ¿debería usar un símbolo?

Alan Coromano
fuente

Respuestas:

175

TL; DR

Una regla general simple es usar símbolos cada vez que necesite identificadores internos. Para Ruby <2.2 solo use símbolos cuando no se generen dinámicamente, para evitar pérdidas de memoria.

Respuesta completa

La única razón para no usarlos para identificadores que se generan dinámicamente es por problemas de memoria.

Esta pregunta es muy común porque muchos lenguajes de programación no tienen símbolos, solo cadenas y, por lo tanto, las cadenas también se utilizan como identificadores en su código. Usted debe ser preocuparse por lo que los símbolos están destinados a ser , no sólo cuando se debe utilizar símbolos . Los símbolos están destinados a ser identificadores. Si sigue esta filosofía, lo más probable es que haga las cosas bien.

Existen varias diferencias entre la implementación de símbolos y cadenas. Lo más importante de los símbolos es que son inmutables . Esto significa que nunca cambiará su valor. Debido a esto, los símbolos se instancian más rápido que las cadenas y algunas operaciones como comparar dos símbolos también son más rápidas.

El hecho de que un símbolo sea inmutable le permite a Ruby usar el mismo objeto cada vez que hace referencia al símbolo, ahorrando memoria. Por lo tanto, cada vez que el intérprete lo lee :my_key, puede tomarlo de la memoria en lugar de crear una instancia nuevamente. Esto es menos costoso que inicializar una nueva cadena cada vez.

Puede obtener una lista de todos los símbolos que ya están instanciados con el comando Symbol.all_symbols:

symbols_count = Symbol.all_symbols.count # all_symbols is an array with all 
                                         # instantiated symbols. 
a = :one
puts a.object_id
# prints 167778 

a = :two
puts a.object_id
# prints 167858

a = :one
puts a.object_id
# prints 167778 again - the same object_id from the first time!

puts Symbol.all_symbols.count - symbols_count
# prints 2, the two objects we created.

Para las versiones de Ruby anteriores a 2.2, una vez que se crea una instancia de un símbolo, esta memoria nunca volverá a estar libre . La única forma de liberar memoria es reiniciando la aplicación. Por lo tanto, los símbolos también son una causa importante de pérdidas de memoria cuando se usan incorrectamente. La forma más sencilla de generar una pérdida de memoria es utilizar el método to_symen los datos de entrada del usuario, ya que estos datos siempre cambiarán, una nueva porción de la memoria se utilizará para siempre en la instancia del software. Ruby 2.2 introdujo el recolector de basura de símbolos , que libera símbolos generados dinámicamente, por lo que las pérdidas de memoria generadas al crear símbolos dinámicamente ya no son una preocupación.

Respondiendo tu pregunta:

¿Es cierto que tengo que usar un símbolo en lugar de una cadena si hay al menos dos cadenas iguales en mi aplicación o script?

Si lo que está buscando es un identificador para usarlo internamente en su código, debe usar símbolos. Si está imprimiendo una salida, debe ir con cadenas, incluso si aparece más de una vez, incluso asignando dos objetos diferentes en la memoria.

Este es el razonamiento:

  1. Imprimir los símbolos será más lento que imprimir cadenas porque se convierten en cadenas.
  2. Tener muchos símbolos diferentes aumentará el uso de memoria general de su aplicación, ya que nunca se desasignan. Y nunca está utilizando todas las cadenas de su código al mismo tiempo.

Caso de uso de @AlanDert

@AlanDert: si utilizo muchas veces algo como% input {type:: checkbox} en el código haml, ¿qué debo usar como casilla de verificación?

Yo: si.

@AlanDert: Pero para imprimir un símbolo en una página html, debería convertirse en una cadena, ¿no? ¿Cuál es el punto de usarlo entonces?

¿Cuál es el tipo de entrada? ¿Un identificador del tipo de entrada que desea utilizar o algo que desea mostrar al usuario?

Es cierto que se convertirá en código HTML en algún momento, pero en el momento en que está escribiendo esa línea de su código, significa que es un identificador: identifica qué tipo de campo de entrada necesita. Por lo tanto, se usa una y otra vez en su código, y siempre tiene la misma "cadena" de caracteres que el identificador y no generará una pérdida de memoria.

Dicho esto, ¿por qué no evaluamos los datos para ver si las cadenas son más rápidas?

Este es un punto de referencia simple que creé para esto:

require 'benchmark'
require 'haml'

str = Benchmark.measure do
  10_000.times do
    Haml::Engine.new('%input{type: "checkbox"}').render
  end
end.total

sym = Benchmark.measure do
  10_000.times do
    Haml::Engine.new('%input{type: :checkbox}').render
  end
end.total

puts "String: " + str.to_s
puts "Symbol: " + sym.to_s

Tres salidas:

# first time
String: 5.14
Symbol: 5.07
#second
String: 5.29
Symbol: 5.050000000000001
#third
String: 4.7700000000000005
Symbol: 4.68

Entonces, usar símbolos es un poco más rápido que usar cadenas. ¿Porqué es eso? Depende de la forma en que se implemente HAML. Necesitaría hackear un poco el código HAML para verlo, pero si sigues usando símbolos en el concepto de un identificador, tu aplicación será más rápida y confiable. Cuando surjan preguntas, compárelo y obtenga sus respuestas.

fotanus
fuente
@andrewcockerham El enlace proporcionado no funciona (Error-404). Tienes que eliminar el último /(después strings) del enlace. Aquí está: www.reactive.io/tips/2009/01/11/the-difference-between-ruby-‌ symbols-and-strings
Atul Khanduri
14

En pocas palabras, un símbolo es un nombre, compuesto de caracteres, pero inmutable. Una cadena, por el contrario, es un contenedor ordenado de caracteres, cuyo contenido puede cambiar.

Boris Stitnicky
fuente
4
+1. Los símbolos y las cadenas son cosas completamente diferentes. Realmente no hay ninguna confusión en cuanto a cuál usar, a menos que se les haya enseñado mal (es decir, la falacia de "un símbolo es solo una cadena inmutable").
Jörg W Mittag
@ JörgWMittag: Exactamente.
Boris Stitnicky
5
tiene un punto, sin embargo, no responda a la pregunta que se hizo. El OP confunde cadenas con símbolos, no es suficiente decir que son cosas diferentes; debe ayudarlo a comprender en qué se parecen y en qué se diferencian
fotanus
1
@ JörgWMittag, que parece estar sucediendo en toda la web, a menos que mires la documentación o tengas la suerte de encontrar personas que se preocupen por explicar las cosas como realmente son.
sargas
8
  1. Un símbolo de Ruby es un objeto con comparación O (1)

Para comparar dos cadenas, potencialmente necesitamos observar cada carácter. Para dos cadenas de longitud N, esto requerirá N + 1 comparaciones (a las que los científicos informáticos se refieren como "tiempo O (N)").

def string_comp str1, str2
  return false if str1.length != str2.length
  for i in 0...str1.length
    return false if str1[i] != str2[i]
  end
  return true
end
string_comp "foo", "foo"

Pero dado que cada aparición de: foo se refiere al mismo objeto, podemos comparar los símbolos mirando los ID de los objetos. Podemos hacer esto con una sola comparación (a la que los científicos informáticos se refieren como "tiempo O (1)").

def symbol_comp sym1, sym2
  sym1.object_id == sym2.object_id
end
symbol_comp :foo, :foo
  1. Un símbolo de Ruby es una etiqueta en una enumeración de forma libre

En C ++, podemos usar "enumeraciones" para representar familias de constantes relacionadas:

enum BugStatus { OPEN, CLOSED };
BugStatus original_status = OPEN;
BugStatus current_status  = CLOSED;

Pero debido a que Ruby es un lenguaje dinámico, no nos preocupamos por declarar un tipo de BugStatus o realizar un seguimiento de los valores legales. En cambio, representamos los valores de enumeración como símbolos:

original_status = :open
current_status  = :closed

3.Un símbolo Ruby es un nombre único y constante

En Ruby, podemos cambiar el contenido de una cadena:

"foo"[0] = ?b # "boo"

Pero no podemos cambiar el contenido de un símbolo:

:foo[0]  = ?b # Raises an error
  1. Un símbolo de Ruby es la palabra clave para un argumento de palabra clave

Al pasar argumentos de palabras clave a una función de Ruby, especificamos las palabras clave usando símbolos:

# Build a URL for 'bug' using Rails.
url_for :controller => 'bug',
        :action => 'show',
        :id => bug.id
  1. Un símbolo de Ruby es una excelente opción para una clave hash

Normalmente, usaremos símbolos para representar las claves de una tabla hash:

options = {}
options[:auto_save]     = true
options[:show_comments] = false
Arun Kumar M
fuente
5

Aquí hay un buen punto de referencia de cadenas vs símbolos que encontré en codecademy:

require 'benchmark'

string_AZ = Hash[("a".."z").to_a.zip((1..26).to_a)]
symbol_AZ = Hash[(:a..:z).to_a.zip((1..26).to_a)]

string_time = Benchmark.realtime do
  1000_000.times { string_AZ["r"] }
end

symbol_time = Benchmark.realtime do
  1000_000.times { symbol_AZ[:r] }
end

puts "String time: #{string_time} seconds."
puts "Symbol time: #{symbol_time} seconds."

La salida es:

String time: 0.21983 seconds.
Symbol time: 0.087873 seconds.
Yurii
fuente
2
No perdamos de vista el hecho de que se trata de una décima de segundo.
Casey
Todo es relativo. A veces la centésima materia.
Yurii
2
¿Una centésima de segundo más de un millón de iteraciones? Si esa es la mejor optimización disponible para usted, su programa ya está bastante bien optimizado, creo.
Casey
0
  • utilizar símbolos como identificadores de clave hash

    {key: "value"}

  • los símbolos le permiten llamar al método en un orden diferente

     def write (archivo :, datos :, modo: "ascii")
          # eliminado por brevedad
     final
     escribir (datos: 123, archivo: "test.txt")
  • congelar para mantener como una cadena y ahorrar memoria

    label = 'My Label'.freeze

Oshan Wisumperuma
fuente