Résoudre un message coûte une remontée de chaîne — que le cache amortit.Resolving a message costs a chain walk — that the cache amortizes.
Le dernier volume boucle sur le premier. Si tout est un message (Vol 1), et qu'envoyer un message, c'est remonter la chaîne d'ancêtres jusqu'au premier qui répond (Vol 5), alors chaque appel a, en théorie, un coût : celui du parcours. Mais Ruby ne paie ce coût qu'une fois. Un cache mémorise, pour chaque site d'appel, la méthode trouvée ; les appels suivants la touchent directement, sans reparcourir la chaîne. C'est pourquoi des hiérarchies profondes ou des mixins multiples ne ruinent pas la performance en régime normal : la résolution est amortie. Le coût existe, mais il est presque toujours payé une seule fois — et le savoir, c'est savoir où il redevient visible.The last volume loops back to the first. If everything is a message (Vol 1), and sending a message means walking the ancestor chain to the first that answers (Vol 5), then each call has, in theory, a cost: the walk. But Ruby pays that cost only once. A cache memorizes, per call site, the method found; subsequent calls hit it directly, without re-walking the chain. That's why deep hierarchies or multiple mixins don't ruin performance in normal operation: resolution is amortized. The cost exists, but it's almost always paid once — and knowing that is knowing where it becomes visible again.
# chaque appel RÉSOUT le message en parcourant la chaîne d'ancêtres (Vol 5) obj.value # → singleton class → classe → modules inclus → superclasse → … → BasicObject # jusqu'au premier ancêtre qui définit value # mais Ruby n'arpente pas la chaîne à CHAQUE appel : il met en cache obj.value # 1er appel : résolution complète, puis mise en cache obj.value # appels suivants : cache touché, O(1) — la chaîne n'est pas reparcourue String.instance_method(:upcase).owner # => String (résolu une fois, mémorisé)
La profondeur de la chaîne ne se paie qu'au premier appel d'un site donné. Ensuite, le cache rend la résolution constante. D'où une vérité contre-intuitive : une chaîne d'ancêtres longue coûte rarement en régime établi — sauf si quelque chose invalide le cache.The chain's depth is paid only on a call site's first call. After that, the cache makes resolution constant. Hence a counter-intuitive truth: a long ancestor chain rarely costs in steady state — unless something invalidates the cache.
« Ai-je mesuré ce coût, ou je l'imagine ? » C'est la règle de tout le Vol 9 : on n'entre dans la performance qu'avec un profil — benchmark, benchmark/ips — jamais avec une intuition. La résolution de méthode est le premier endroit où l'on croit « savoir » que c'est lent, alors que le cache a déjà réglé la question."Have I measured this cost, or am I imagining it?" It's the rule of all of Vol 9: enter performance only with a profile — benchmark, benchmark/ips — never with an intuition. Method resolution is the first place you think you "know" it's slow, when the cache already settled the question.
Le Vol 9 vient en dernier, pas en premier, et ce n'est pas un hasard. On a passé huit volumes à goûter la liberté de Ruby — messages, mixins, métaprog, blocs paresseux. Chacune de ces libertés a un prix, et ici on le nomme. Mais la discipline est stricte : le prix se mesure, il ne s'imagine pas. La plupart des « optimisations » qu'on dégaine par réflexe corrigent un coût que le cache, le GC ou l'interpréteur avaient déjà absorbé.Vol 9 comes last, not first, and that's no accident. We spent eight volumes savoring Ruby's freedom — messages, mixins, metaprogramming, lazy blocks. Each of these freedoms has a price, and here we name it. But the discipline is strict: the price is measured, not imagined. Most "optimizations" you reach for by reflex fix a cost the cache, the GC or the interpreter had already absorbed.