Quand utiliser des symboles au lieu de chaînes dans Ruby?

98

S'il y a au moins deux instances de la même chaîne dans mon script, dois-je utiliser à la place un symbole?

Alan Coromano
la source

Réponses:

175

TL; DR

Une règle empirique simple consiste à utiliser des symboles chaque fois que vous avez besoin d'identifiants internes. Pour Ruby <2.2, n'utilisez les symboles que lorsqu'ils ne sont pas générés dynamiquement, pour éviter les fuites de mémoire.

Réponse complète

La seule raison de ne pas les utiliser pour les identifiants générés dynamiquement est à cause de problèmes de mémoire.

Cette question est très courante car de nombreux langages de programmation n'ont pas de symboles, seulement des chaînes, et donc des chaînes sont également utilisées comme identificateurs dans votre code. Vous devriez vous soucier de ce que les symboles sont censés être , pas seulement quand vous devez utiliser des symboles . Les symboles sont censés être des identificateurs. Si vous suivez cette philosophie, il y a de fortes chances que vous fassiez les choses correctement.

Il existe plusieurs différences entre l'implémentation des symboles et des chaînes. La chose la plus importante à propos des symboles est qu'ils sont immuables . Cela signifie qu'ils ne verront jamais leur valeur modifiée. Pour cette raison, les symboles sont instanciés plus rapidement que les chaînes et certaines opérations telles que la comparaison de deux symboles sont également plus rapides.

Le fait qu'un symbole soit immuable permet à Ruby d'utiliser le même objet à chaque fois que vous faites référence au symbole, ce qui économise de la mémoire. Donc à chaque fois que l'interprète lit:my_key il peut le prendre de la mémoire au lieu de l'instancier à nouveau. C'est moins coûteux que d'initialiser une nouvelle chaîne à chaque fois.

Vous pouvez obtenir une liste de tous les symboles déjà instanciés avec la commande 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.

Pour les versions Ruby antérieures à la 2.2, une fois qu'un symbole est instancié, cette mémoire ne sera plus jamais libre . Le seul moyen de libérer de la mémoire est de redémarrer l'application. Les symboles sont donc également une cause majeure de fuites de mémoire lorsqu'ils sont utilisés de manière incorrecte. Le moyen le plus simple de générer une fuite de mémoire est d'utiliser la méthode to_symsur les données d'entrée utilisateur, puisque ces données changeront toujours, une nouvelle partie de la mémoire sera utilisée pour toujours dans l'instance du logiciel. Ruby 2.2 a introduit le ramasse-miettes de symboles , qui libère les symboles générés dynamiquement, donc les fuites de mémoire générées par la création dynamique de symboles ne sont plus un problème.

Répondre à votre question:

Est-il vrai que je dois utiliser un symbole au lieu d'une chaîne s'il y a au moins deux chaînes identiques dans mon application ou mon script?

Si vous recherchez un identifiant à utiliser en interne dans votre code, vous devez utiliser des symboles. Si vous imprimez la sortie, vous devriez utiliser des chaînes, même si elle apparaît plus d'une fois, même en allouant deux objets différents en mémoire.

Voici le raisonnement:

  1. L'impression des symboles sera plus lente que l'impression des chaînes car elles sont converties en chaînes.
  2. Avoir beaucoup de symboles différents augmentera l'utilisation globale de la mémoire de votre application car ils ne sont jamais désalloués. Et vous n'utilisez jamais toutes les chaînes de votre code en même temps.

Cas d'utilisation par @AlanDert

@AlanDert: si j'utilise plusieurs fois quelque chose comme% input {type:: checkbox} dans le code haml, que dois-je utiliser comme case à cocher?

Moi oui.

@AlanDert: Mais pour imprimer un symbole sur une page html, il doit être converti en chaîne, n'est-ce pas? quel est l'intérêt de l'utiliser alors?

Quel est le type d'une entrée? Un identifiant du type d'entrée que vous souhaitez utiliser ou quelque chose que vous souhaitez montrer à l'utilisateur?

Il est vrai que cela deviendra du code HTML à un moment donné, mais au moment où vous écrivez cette ligne de votre code, c'est censé être un identifiant - il identifie le type de champ de saisie dont vous avez besoin. Ainsi, il est utilisé maintes et maintes fois dans votre code, et a toujours la même "chaîne" de caractères que l'identifiant et ne générera pas de fuite mémoire.

Cela dit, pourquoi n'évaluons-nous pas les données pour voir si les chaînes sont plus rapides?

C'est un simple benchmark que j'ai créé pour cela:

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

Trois sorties:

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

Donc, utiliser smbols est en fait un peu plus rapide que les chaînes. Pourquoi donc? Cela dépend de la manière dont HAML est implémenté. J'aurais besoin de pirater un peu le code HAML pour voir, mais si vous continuez à utiliser des symboles dans le concept d'identifiant, votre application sera plus rapide et fiable. Lorsque des questions surgissent, comparez-les et obtenez vos réponses.

fotanus
la source
@andrewcockerham Votre lien fourni ne fonctionne pas (Erreur 404). Vous devez supprimer le dernier /(après strings) du lien. Le voici: www.reactive.io/tips/2009/01/11/the-difference-between-ruby-‌ symboles-and-strings
Atul Khanduri
14

En termes simples, un symbole est un nom, composé de caractères, mais immuable. Une chaîne, au contraire, est un conteneur ordonné pour les caractères, dont le contenu est autorisé à changer.

Boris Stitnicky
la source
4
+1. Les symboles et les chaînes sont des choses complètement différentes. Il n'y a vraiment aucune confusion quant à savoir lequel utiliser, à moins qu'ils n'aient été mal enseignés (c'est-à-dire l'erreur "un symbole est juste une chaîne immuable").
Jörg W Mittag
@ JörgWMittag: Exactement.
Boris Stitnicky
5
vous avez un point, mais ne répondez pas à la question qui a été posée. L'OP confond les chaînes avec les symboles, il ne suffit pas de dire que ce sont des choses différentes - vous devriez l'aider à comprendre ce qu'elles se ressemblent et en quoi elles sont différentes
fotanus
1
@ JörgWMittag, ce qui se passe partout sur le Web semble-t-il, à moins que vous ne regardiez dans la documentation ou que vous ayez la chance de trouver des personnes qui se soucient d'expliquer les choses telles qu'elles sont vraiment.
sargas
5

Voici un joli benchmark de chaînes vs symboles que j'ai trouvé à la 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 sortie est:

String time: 0.21983 seconds.
Symbol time: 0.087873 seconds.
Yurii
la source
2
Ne perdons pas de vue qu'il s'agit d'un dixième de seconde.
Casey
Tout est relatif. Parfois, le centième compte.
Yurii
2
Un centième de seconde sur un million d'itérations? Si c'est la meilleure optimisation dont vous disposez, votre programme est déjà assez bien optimisé, je pense.
Casey
0
  • utiliser des symboles comme identificateurs de clé de hachage

    {key: "value"}

  • les symboles vous permettent d'appeler la méthode dans un ordre différent

     def write (fichier :, data :, mode: "ascii")
          # supprimé par souci de concision
     fin
     écrire (données: 123, fichier: "test.txt")
  • freeze pour conserver comme une chaîne et économiser de la mémoire

    label = 'My Label'.freeze

Oshan Wisumperuma
la source