`_map_collection` の中間配列アロケーションを削減する最適化

rails/jbuilder

_map_collection の実装を map + 配列減算から map + delete に変更することで、中間オブジェクトのアロケーション数を3から1に削減し、スループットを約1.14倍向上させました。

背景

#610filter_map を使ったパフォーマンス改善が提案されたことが、本PRのきっかけです。filter_map 案の検証を通じて、_map_collection のアロケーション特性を詳しく分析する契機が生まれました。

既存実装では collection.map { ... } - [BLANK] という形で [BLANK] という一時配列を毎回生成し、さらに減算の結果として新たな配列を生成していました。この処理ではアロケーション数が3オブジェクト(120バイト)に上り、コレクション処理のホットパスで不要なGCプレッシャーとなっていました。

PR作者がまず検証した filter_map 案は、コードの意図は明確になるものの、ベンチマーク上では元実装より 1.61倍遅い という予想外の結果となりました。- [BLANK] を定数 BLANKS = [BLANK].freeze に置き換える案も試みられましたが、アロケーション数は2オブジェクト(80バイト)にとどまり、速度改善にはつながりませんでした。

技術的な変更

_map_collection の実装を、配列の減算から破壊的な delete 呼び出しに変更しました。

変更前:

def _map_collection(collection)
  collection.map do |element|
    _scope{ yield element }
  end - [BLANK]
end

変更後:

def _map_collection(collection)
  collection = collection.map do |element|
    _scope{ yield element }
  end
  collection.delete(BLANK)
  collection
end

変更の核心は2点です。まず -[BLANK] という配列減算を廃止することで、一時配列オブジェクト[BLANK])と減算結果の新配列という2つのアロケーションが消えます。次に Array#delete を使うことで map が返した配列をそのまま破壊的に編集するため、最終的なアロケーション数は1オブジェクト(40バイト)となり、元実装の3分の1になります。

PR内のベンチマーク結果によると、_map_collection 単体では 1.14倍のスループット向上(178.86 ns/i → 156.86 ns/i)が確認されています。array! を使ったエンドツーエンドの計測でも同様に 1.14倍の改善 と、メモリアロケーションの 半減(160バイト → 80バイト)が示されています。

設計判断

Array#delete による破壊的変更を採用したことが、本PRの中心的な設計判断です。

map が返した配列は _map_collection 内のローカル変数としてのみ参照されており、呼び出し元には map 完了後に渡されます。そのため、破壊的な delete を適用しても呼び出し元への副作用は発生しません。filter_map が遅かった理由についてPR内で明示的な言及はありませんが、delete は既存配列を直接編集するだけであり、filter_map のような新配列の逐次構築と条件評価のオーバーヘッドを回避できます。

また、PR本文では「複雑なテンプレートでは恩恵は薄まるが、変更が小さく局所的なため実施する価値がある」と述べられています。変更行数はわずか4行の追加・2行の削除であり、テスト変更も不要なほど動作仕様に変化はありません。

まとめ

本PRは _map_collection における不要な中間配列の生成を排除することで、コードの意味論を変えずにメモリアロケーションを削減した局所的な最適化です。filter_map という直感的な代替案のベンチマークが予想外の劣化を示したという経緯から、Ruby内部における配列操作のコスト特性を改めて確認できる変更といえます。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
757147b3

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)の3部構成が明確であり、必須要素をすべて含んでいます。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きのシンタックスハイライト(```ruby:lib/jbuilder.rb)やPR番号のリンク記法([#610](...))が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

アロケーション、GCプレッシャー、スループットといった用語を適切に用いており、専門知識を持つエンジニアという対象読者に適合しています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成になっており、各段落もトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られており、非常に読みやすいです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されている変更前後のコードは、提供されたDiff情報と完全に一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「アロケーション」「スループット」「破壊的変更」などの技術用語が、PRの文脈に沿って正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

「アロケーションが3から1に削減」「スループットが1.14倍向上」といった説明は、PR内のベンチマーク結果によって裏付けられており、技術的に正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内の主張はすべてPR Descriptionやベンチマーク結果で裏付けられており、ハルシネーション(創作)は見られません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

スループット向上率(1.14倍)、アロケーション数(3→1)、メモリ量(120バイト→40バイト)など、すべての数値がPR情報と正確に一致しています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルはPRの主題「Optimize `_map_collection`」をより具体的に、かつ正確に要約しており、内容と一致しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PRに記載のないバージョン情報やリリース予定などの外部知識は含まれておらず、PR情報に忠実です。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

PR情報に時間的な表現が少ないですが、記事内にも時間表現の歪曲や誤用は見られません。