ruby symbols

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



symbols (4)

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


TL; DR

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

Respuesta completa

La única razón para no usarlos para los identificadores que se generan dinámicamente es debido a 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 usan como identificadores en su código. Debería preocuparse por los símbolos que deben ser , no solo cuando debe usar símbolos . Los símbolos están destinados a ser identificadores. Si sigues esta filosofía, es probable que hagas 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 tendrán su valor cambiado. 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, guardando la memoria. Entonces, cada vez que el intérprete lee :my_key puede tomarlo de la memoria en lugar de instanciarlo de nuevo. Esto es menos expansivo 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 al 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 la memoria es reiniciar 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 simple de generar una pérdida de memoria es usar el método to_sym en los datos de entrada del usuario, ya que estos datos siempre cambiarán, una nueva porción de la memoria se usará para siempre en la instancia del software. Ruby 2.2 introdujo el símbolo recolector de basura , 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 que se utilizará internamente en su código, debe usar símbolos. Si está imprimiendo salida, debe ir con cadenas, incluso si aparece más de una vez, incluso asignando dos objetos diferentes en la memoria.

Aquí está el razonamiento:

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

Caso de uso de @AlanDert

@AlanDert: si uso muchas veces algo como% input {type:: checkbox} en código haml, ¿qué debo usar como checkbox?

Yo: si.

@AlanDert: Pero para imprimir un símbolo en la página html, se debe convertir a cadena, ¿no es así? ¿De qué sirve usarlo entonces?

¿Cuál es el tipo de entrada? ¿Un identificador del tipo de entrada que desea usar 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 ser 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 he creado 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 en realidad un poco más rápido que usar cadenas. ¿Porqué es eso? Depende de la forma en que se implemente HAML. Tendría que hackear un poco el código HAML para ver, pero si sigues usando símbolos en el concepto de identificador, tu aplicación será más rápida y confiable. Cuando surgen preguntas, coteje esto y obtenga sus respuestas.


  1. Un símbolo de Ruby es un objeto con O (1) comparación

Para comparar dos cadenas, es posible que tengamos que ver cada personaje. Para dos cadenas de longitud N, esto requerirá comparaciones N + 1 (que los informáticos denominan "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 como todas las apariencias de: foo se refieren al mismo objeto, podemos comparar símbolos mirando los identificadores de objeto. Podemos hacer esto con una sola comparación (que los informáticos llaman "O (1) tiempo").

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 BugStatus, o por no perder de vista los valores legales. En cambio, representamos los valores de enumeración como símbolos:

original_status = :open current_status = :closed

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

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 palabra 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


Aquí hay un buen benchmark de cadenas versus 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."

El resultado es:

String time: 0.21983 seconds. Symbol time: 0.087873 seconds.


En pocas palabras, un símbolo es un nombre, compuesto de caracteres, pero inmutable. Una cadena, por el contrario, es un contenedor ordenado para los personajes, cuyos contenidos pueden cambiar.