Quando usar símbolos em vez de strings em Ruby?

98

Se houver pelo menos duas instâncias da mesma string em meu script, devo usar um símbolo?

Alan Coromano
fonte

Respostas:

175

TL; DR

Uma regra simples é usar símbolos sempre que precisar de identificadores internos. Para Ruby <2.2, use apenas símbolos quando não forem gerados dinamicamente, para evitar vazamentos de memória.

Resposta completa

A única razão para não usá-los para identificadores que são gerados dinamicamente é devido a questões de memória.

Essa pergunta é muito comum porque muitas linguagens de programação não têm símbolos, apenas strings e, portanto, as strings também são usadas como identificadores em seu código. Você deve se preocupar com o que os símbolos devem ser , não apenas quando você deve usar símbolos . Os símbolos são identificadores. Se você seguir essa filosofia, é provável que você faça as coisas certas.

Existem várias diferenças entre a implementação de símbolos e strings. O mais importante sobre os símbolos é que eles são imutáveis . Isso significa que eles nunca terão seu valor alterado. Por causa disso, os símbolos são instanciados mais rápido do que strings e algumas operações, como comparar dois símbolos, também são mais rápidas.

O fato de um símbolo ser imutável permite que Ruby use o mesmo objeto toda vez que você fizer referência ao símbolo, economizando memória. Portanto, toda vez que o interpretador lê, :my_keyele pode retirá-lo da memória em vez de instanciá-lo novamente. Isso é menos caro do que inicializar uma nova string todas as vezes.

Você pode obter uma lista de todos os símbolos já instanciados com o 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 versões Ruby anteriores a 2.2, uma vez que um símbolo é instanciado, essa memória nunca estará livre novamente . A única maneira de liberar memória é reiniciando o aplicativo. Portanto, os símbolos também são uma das principais causas de vazamentos de memória quando usados ​​incorretamente. A maneira mais simples de gerar um vazamento de memória é usando o método to_symnos dados de entrada do usuário, uma vez que esses dados sempre serão alterados, uma nova parte da memória será usada para sempre na instância do software. Ruby 2.2 introduziu o coletor de lixo de símbolo , que libera símbolos gerados dinamicamente, de modo que os vazamentos de memória gerados pela criação de símbolos dinamicamente não são mais uma preocupação.

Respondendo sua pergunta:

É verdade que devo usar um símbolo em vez de uma string se houver pelo menos duas strings iguais em meu aplicativo ou script?

Se o que você está procurando é um identificador para ser usado internamente em seu código, você deve usar símbolos. Se estiver imprimindo saída, você deve ir com strings, mesmo que apareça mais de uma vez, mesmo alocando dois objetos diferentes na memória.

Aqui está o raciocínio:

  1. A impressão dos símbolos será mais lenta do que a impressão de strings porque eles são convertidos em strings.
  2. Ter muitos símbolos diferentes aumentará o uso geral da memória de seu aplicativo, pois eles nunca são desalocados. E você nunca está usando todas as strings do seu código ao mesmo tempo.

Caso de uso por @AlanDert

@AlanDert: se eu usar muitas vezes algo como% input {type:: checkbox} no código haml, o que devo usar como caixa de seleção?

Eu sim.

@AlanDert: Mas para imprimir um símbolo em uma página html, ele deve ser convertido em string, não é? qual é o ponto de usá-lo então?

Qual é o tipo de entrada? Um identificador do tipo de entrada que você deseja usar ou algo que deseja mostrar ao usuário?

É verdade que em algum momento ele se tornará um código HTML, mas no momento em que você está escrevendo essa linha do código, ela deve ser um identificador - identifica o tipo de campo de entrada de que você precisa. Portanto, ele é usado repetidamente em seu código e tem sempre a mesma "sequência" de caracteres que o identificador e não gera um vazamento de memória.

Dito isso, por que não avaliamos os dados para ver se as strings são mais rápidas?

Este é um benchmark simples que criei para isso:

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

Três saídas:

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

Portanto, usar símbolos é um pouco mais rápido do que usar strings. Por que é que? Depende da maneira como o HAML é implementado. Eu precisaria hackear um pouco o código HAML para ver, mas se você continuar usando símbolos no conceito de identificador, seu aplicativo será mais rápido e confiável. Quando as perguntas surgirem, compare-as e obtenha suas respostas.

fotanus
fonte
@andrewcockerham O link fornecido não está funcionando (Erro-404). Você tem que remover o último /(depois strings) do link. Aqui está: www.reactive.io/tips/2009/01/11/the-difference-between-ruby-‌ symbols-and-strings
Atul Khanduri
14

Simplificando, um símbolo é um nome, composto de caracteres, mas imutável. Uma string, ao contrário, é um contêiner ordenado para caracteres, cujo conteúdo pode ser alterado.

Boris Stitnicky
fonte
4
+1. Símbolos e Strings são coisas completamente diferentes. Realmente não há confusão quanto a qual usar, a menos que tenham sido mal ensinados (ou seja, a falácia "um símbolo é apenas uma corda imutável").
Jörg W Mittag
@ JörgWMittag: Exatamente.
Boris Stitnicky
5
você tem razão, mas não responda à pergunta que foi feita. O OP confunde strings com símbolos, não basta dizer que são coisas diferentes - você deve ajudá-lo a entender o que são iguais e em que são diferentes
fotanus
1
@ JörgWMittag que está acontecendo em toda a web, ao que parece, a menos que você dê uma olhada na documentação ou tenha a sorte de encontrar pessoas que se importam em explicar as coisas como elas realmente são.
sargas
8
  1. Um símbolo Ruby é um objeto com comparação O (1)

Para comparar duas strings, precisamos potencialmente examinar cada caractere. Para duas sequências de comprimento N, isso exigirá comparações N + 1 (que os cientistas da computação chamam de "tempo 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"

Mas como cada aparência de: foo se refere ao mesmo objeto, podemos comparar símbolos observando os IDs de objeto. Podemos fazer isso com uma única comparação (que os cientistas da computação chamam de "tempo O (1)").

def symbol_comp sym1, sym2
  sym1.object_id == sym2.object_id
end
symbol_comp :foo, :foo
  1. Um símbolo Ruby é um rótulo em uma enumeração de forma livre

Em C ++, podemos usar "enumerações" para representar famílias de constantes relacionadas:

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

Mas como Ruby é uma linguagem dinâmica, não nos preocupamos em declarar um tipo BugStatus ou em manter o controle dos valores legais. Em vez disso, representamos os valores de enumeração como símbolos:

original_status = :open
current_status  = :closed

3. Um símbolo Ruby é um nome constante e único

Em Ruby, podemos alterar o conteúdo de uma string:

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

Mas não podemos alterar o conteúdo de um símbolo:

:foo[0]  = ?b # Raises an error
  1. Um símbolo Ruby é a palavra-chave para um argumento de palavra-chave

Ao passar argumentos de palavras-chave para uma função Ruby, especificamos as palavras-chave usando símbolos:

# Build a URL for 'bug' using Rails.
url_for :controller => 'bug',
        :action => 'show',
        :id => bug.id
  1. Um símbolo Ruby é uma excelente escolha para uma chave hash

Normalmente, usaremos símbolos para representar as chaves de uma tabela hash:

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

Aqui está um bom benchmark de strings vs símbolos que encontrei na 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."

O resultado é:

String time: 0.21983 seconds.
Symbol time: 0.087873 seconds.
Yurii
fonte
2
Não vamos perder de vista que isso é um décimo de segundo.
Casey
É tudo relativo. Às vezes, o centésimo importa.
Yurii
2
Um centésimo de segundo em um milhão de iterações? Se essa é a melhor otimização disponível para você, seu programa já está muito bem otimizado, eu acho.
Casey
0
  • usar símbolos como identificadores de chave hash

    {key: "value"}

  • os símbolos permitem que você chame o método em uma ordem diferente

     def write (arquivo :, dados :, modo: "ascii")
          # removido por questões de brevidade
     fim
     escrever (dados: 123, arquivo: "test.txt")
  • congelar para manter como uma string e economizar memória

    label = 'My Label'.freeze

Oshan Wisumperuma
fonte