Chaque "…", [], {} alloue — et le GC paie pour libérer.Every "…", [], {} allocates — and the GC pays to free.
Après la résolution, l'autre coût du dynamisme : la mémoire. En Ruby, presque tout est un objet (Vol 1), et créer un objet l'alloue sur le tas. Chaque littéral de chaîne, de tableau, de hash que tu écris fabrique un nouvel objet — y compris les identiques, réalloués à chaque passage. Plus tard, le ramasse-miettes (GC) parcourt le tas pour libérer ce qui n'est plus référencé : c'est un travail, proportionnel au nombre d'objets créés. Une seule allocation ne coûte rien ; un million dans une boucle chaude déclenche des cycles de GC qui, eux, se voient. La règle reste celle du volume : on ne devine pas une allocation, on la compte.After resolution, the other cost of dynamism: memory. In Ruby, almost everything is an object (Vol 1), and creating an object allocates it on the heap. Every string, array, hash literal you write makes a new object — including identical ones, reallocated on each pass. Later, the garbage collector (GC) scans the heap to free what's no longer referenced: that's work, proportional to the number of objects created. A single allocation costs nothing; a million in a hot loop triggers GC cycles that do show. The rule stays the volume's: you don't guess an allocation, you count it.
# chaque LITTÉRAL crée un objet — invisible, mais réel "abc".object_id == "abc".object_id # => false deux String distincts [].object_id == [].object_id # => false deux Array {}.object_id == {}.object_id # => false deux Hash # … sauf ceux qui sont uniques par nature (Vol 1) : :abc.object_id == :abc.object_id # => true un seul Symbol 1.object_id == 1.object_id # => true un seul Integer # compter, plutôt que deviner : before = GC.stat(:total_allocated_objects) 1_000.times { "ligne".dup } GC.stat(:total_allocated_objects) - before # => ~2000 objets : le littéral ET sa copie, à chaque tour
L'allocation est bon marché, mais pas gratuite : plus tu crées d'objets, plus le GC doit travailler pour les libérer. Le coût n'est pas dans le littéral isolé — il est dans le nombre, et le nombre se compte avec GC.stat.Allocation is cheap, but not free: the more objects you create, the harder the GC works to free them. The cost isn't in the lone literal — it's in the count, and the count is measured with GC.stat.
« Cette boucle chaude alloue-t-elle un objet par tour que je pourrais hisser ou geler ? » Un littéral constant créé à chaque itération, une chaîne réallouée mille fois : ce sont les cibles. Mais on ne les traite qu'après les avoir comptées — GC.stat, memory_profiler, pas l'intuition."Does this hot loop allocate an object per turn that I could hoist or freeze?" A constant literal created each iteration, a string reallocated a thousand times: those are the targets. But you treat them only after counting — GC.stat, memory_profiler, not intuition.
Le Vol 4 l'a montré : # frozen_string_literal: true gèle et partage les littéraux de chaîne — un seul objet réutilisé, au lieu d'un par évaluation. C'est l'optimisation d'allocation la plus simple de Ruby, gratuite et sûre. Mais même elle obéit à la règle : son gain réel, dans ton code, se mesure. Elle n'est pas encore le défaut — Ruby 3.4 se contente de rendre les littéraux « chilled » et d'avertir (sous -W:deprecated) quand on les mute — mais posée en tête de fichier, elle évite des milliers d'allocations dans les boucles qui répètent des chaînes constantes.Vol 4 showed it: # frozen_string_literal: true freezes and shares string literals — one object reused, instead of one per evaluation. It's Ruby's simplest allocation optimization, free and safe. But even it obeys the rule: its real gain, in your code, is measured. It isn't the default yet — Ruby 3.4 only makes literals "chilled" and warns (under -W:deprecated) when you mutate them — but placed at the top of a file, it avoids thousands of allocations in loops that repeat constant strings.