お疲れ様です!
皆さんは普段パフォーマンスを意識してコードを書いていますか? ![]()
エンジニアなりたての頃は意識していなかったのですが、いろいろな経験を通して学ぶことができました!
今まで言語化してこなかったので自分の中で整理しつつ、メモリ管理について簡潔にまとめてみました!
興味ある方は読んでいただけると嬉しいです ![]()
そもそもメモリとは?
プログラムが動いている間、データを一時的に置いておく「作業スペース」です。よく「机の広さ」に例えられます ![]()
- メモリ(机の上) … 今すぐ使うものを広げる場所。速いが容量は小さい
- ディスク(引き出し/本棚) … たくさんしまえるが、出し入れは遅い
プログラムは変数・オブジェクト・読み込んだデータなどを、この「机の上(メモリ)」に置いて処理します。
机が広いほど一度にたくさん作業できますが、容量には限りがあるので、載せすぎると作業効率が落ちたり、最悪あふれて作業が止まったりします。
メモリってどこに何が置かれるの?
ざっくり3つの領域に分かれています。
| 領域 | 置かれるもの | 特徴 |
|---|---|---|
| 静的領域 | クラス・メソッドのコード、static(クラス変数)など |
プログラム起動中ずっと存在 |
| ヒープ |
new で作ったインスタンスデータ |
動的に確保される |
| スタック | 今呼び出しているメソッドの処理(ローカル変数・引数) | メソッドを抜けると自動で解放 |
今回の話で主に問題になるのは ヒープ です!
メモリを使いすぎるとどうなる?
「メモリに入れすぎない」と言われても何が困るのか? ざっくりこんな悪影響があります。
- 処理が遅くなる … メモリが足りなくなると GC(ガベージコレクション)が頻繁に走り、その間アプリの処理が止まる。「なんか重い」の原因がこれだったりします
- スワップで激遅に … 物理メモリが尽きると、OSがディスクをメモリ代わりに使い始める(disk swap)。ディスクはアクセスが遅いので一気に重くなります
- 最悪プロセスが落ちる(OOM) … メモリを使い切ると OS が「Out Of Memory」でプロセスを強制終了。バッチが途中で死ぬ・サーバーが落ちる、といった事故に
実装時に気をつけること
ひとことで言うと 「メモリに入れすぎない!」、考え方は大きく分けて2軸です ![]()
- 1件あたりを軽くする
- 一度にメモリへ載せる件数を減らす
① 1件あたりを軽くする
User.all のように取ると、ActiveRecord(AR) が 全カラムをSQLで取得してメモリに保持 してしまいます。
必要なカラム(name, age など)だけに絞って取得すると量を減らせます。
補足:AR オブジェクトの生成は CPU 負荷も高いので、pluck で素の値だけ取るとメモリも CPU も嬉しい ![]()
# ダメな例:全カラムを取得 & ARオブジェクトを全件生成
# name と age しか使わないのに、全カラム分のデータとARオブジェクトを保持してしまう
labels = User.all.map do |user|
"#{user.name}(#{user.age}歳)"
end
# 良い例:必要なカラムだけ取得。pluck は ARを作らず「値の配列」を返す
# 返り値は [["田中", 20], ["佐藤", 30], ...] なので、ブロックで分解して受け取る
labels = User.pluck(:name, :age).map do |name, age|
"#{name}(#{age}歳)"
end
pluck の戻り値は AR オブジェクトではなく「値の配列」なので、user.name ではなく |name, age| と分解して受け取るのが正解です。
「AR オブジェクトのまま列だけ絞りたい」場合は User.select(:name, :age) を使います。
② 一度にメモリへ載せる件数を減らす
User.all.each は全ユーザーを一度に取得してメモリに載せるため、100万件いれば100万件分の AR オブジェクトが乗り、メモリを圧迫してしまいます!
常にメモリへ載る最大件数は、こちらで制限しましょう。
# ダメな例:ユーザー全件を一気にメモリへ
User.all.each do |user|
user.do_something
end
# 良い例:find_each で一定件数ずつ取得して処理
# 100件取得 → 処理 → 解放 → 次の100件…を繰り返す
User.find_each(batch_size: 100) do |user|
user.do_something
end
注釈:場合によっては「Array」よりも、遅延評価である「ActiveRecord::Relation」の方が良いこともあるのでご注意を!
最後に
メモリの効率化と、可読性・保守性は トレードオフ になることが多いです。そのときどきで「どちらを優先すべきか」を判断できると良いと思います。
AI が出力した結果の品質を担保するのもエンジニアの役目 です。主観だと AI はこの辺りを吟味せずに出力することがあるので、ぜひ意識してみてください!