53
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

メモリ管理の基本と、Railsで省メモリに書くための実装Tips

53
Posted at

お疲れ様です!
皆さんは普段パフォーマンスを意識してコードを書いていますか? :thinking:
エンジニアなりたての頃は意識していなかったのですが、いろいろな経験を通して学ぶことができました!
今まで言語化してこなかったので自分の中で整理しつつ、メモリ管理について簡潔にまとめてみました!
興味ある方は読んでいただけると嬉しいです :thumbsup:

そもそもメモリとは?

プログラムが動いている間、データを一時的に置いておく「作業スペース」です。よく「机の広さ」に例えられます :desktop:

  • メモリ(机の上) … 今すぐ使うものを広げる場所。速いが容量は小さい
  • ディスク(引き出し/本棚) … たくさんしまえるが、出し入れは遅い

プログラムは変数・オブジェクト・読み込んだデータなどを、この「机の上(メモリ)」に置いて処理します。
机が広いほど一度にたくさん作業できますが、容量には限りがあるので、載せすぎると作業効率が落ちたり、最悪あふれて作業が止まったりします。

メモリってどこに何が置かれるの?

ざっくり3つの領域に分かれています。

領域 置かれるもの 特徴
静的領域 クラス・メソッドのコード、static(クラス変数)など プログラム起動中ずっと存在
ヒープ new で作ったインスタンスデータ 動的に確保される
スタック 今呼び出しているメソッドの処理(ローカル変数・引数) メソッドを抜けると自動で解放

今回の話で主に問題になるのは ヒープ です!

メモリを使いすぎるとどうなる?

「メモリに入れすぎない」と言われても何が困るのか? ざっくりこんな悪影響があります。

  • 処理が遅くなる … メモリが足りなくなると GC(ガベージコレクション)が頻繁に走り、その間アプリの処理が止まる。「なんか重い」の原因がこれだったりします
  • スワップで激遅に … 物理メモリが尽きると、OSがディスクをメモリ代わりに使い始める(disk swap)。ディスクはアクセスが遅いので一気に重くなります
  • 最悪プロセスが落ちる(OOM) … メモリを使い切ると OS が「Out Of Memory」でプロセスを強制終了。バッチが途中で死ぬ・サーバーが落ちる、といった事故に

実装時に気をつけること

ひとことで言うと 「メモリに入れすぎない!」、考え方は大きく分けて2軸です :point_down:

  1. 1件あたりを軽くする
  2. 一度にメモリへ載せる件数を減らす

① 1件あたりを軽くする

User.all のように取ると、ActiveRecord(AR) が 全カラムをSQLで取得してメモリに保持 してしまいます。
必要なカラム(name, age など)だけに絞って取得すると量を減らせます。

補足:AR オブジェクトの生成は CPU 負荷も高いので、pluck で素の値だけ取るとメモリも CPU も嬉しい :ok_woman:

# ダメな例:全カラムを取得 & 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 はこの辺りを吟味せずに出力することがあるので、ぜひ意識してみてください!

53
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?