Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

ガベージコレクション入門(前編):Rubyのメモリ管理の仕組み

コードを実行するとき、必ずメモリを使用します。Rubyのような言語でプログラムを書いていると、使えるメモリは無限にあるように感じられます。コードを実行しているシステムが持つ有限のメモリ量を意識することなく、どんどん書き進められるからです。今回のRuby Magicでは、この便利な仕組みがどのように実現されているのかを解説します。

少しの歴史

かつて、Rubyのようなスクリプト言語はまだ存在していませんでした。人々はCのような低水準プログラミング言語だけでコードを書いていました。これらの言語が「低水準」と呼ばれる理由の一つは、メモリの後片付けを自分自身で行わなければならない点にあります。たとえば、Stringを格納するためにメモリを確保したら、そのメモリをいつ解放するのかも自分で決める必要があるのです。

手動でのメモリ解放

これは、次のようなモックのRubyコードのイメージです。変数を宣言し、freeメソッド(※実際にはRubyに存在しない架空のメソッドです)を使って、変数を使い終わった後に確保したメモリを解放しています。

1_000_000.times do |i|
  variable = "Variable #{i}"
  puts variable
  free(variable)
end

面倒なプログラミングスタイル

ここには大きなリスクがあることに気づいたでしょうか。もし変数のfreeを忘れてしまったらどうなるのでしょう?その場合、変数の内容はプロセスが終了するまでメモリ上に残り続けます。これを繰り返していれば、やがてメモリを使い果たし、プロセスはクラッシュしてしまいます。

次の例は、もう一つのよくある問題を示しています。

1_000_000.times do |i|
  variable = "Variable #{i}"
  free(variable)
  puts variable
end

変数を宣言してfreeした後、再びその変数を使おうとしています。しかし、これは不可能です。すでに変数は存在しなくなっているからです。これがC言語であれば、プログラムはsegfault(セグメンテーション違反)でクラッシュします。おっと!

人間はミスを犯す生き物

人間は、こうしたミスを犯さずにいることが非常に苦手なことで知られています。そこで、メモリを自動的に片付ける仕組みが必要になりました。最も広く使われている方法(Rubyでも採用されています)が、ガベージコレクション(GC)です。

ガベージコレクション(GC)の仕組み

GCを採用している言語では、オブジェクトを手動で解放することなく自由に作成できます。オブジェクトを作成するたびに、それはガベージコレクタに登録されます。GCは、そのオブジェクトへのすべての参照を追跡しようとします。オブジェクトがもう使われていないと判断すると、クリーンアップ対象としてマークします。そして定期的に、ガベージコレクタはプログラムを一時停止し、マークされたすべてのオブジェクトをまとめて片付けるのです。

具体例で確認してみよう

先ほどのシンプルなループでは、GCの仕事は比較的容易です。ループの各イテレーションごとに、変数はどこからも参照されなくなります。そのため、変数はすぐにクリーンアップ対象としてマークできます。

1_000_000.times do |i|
  variable = "Variable #{i}"
  puts variable
end

次の例では、変数をputs_laterメソッドに渡しています。このメソッドは30秒待機してから、変数を出力します。

def puts_later(variable)
  Thread.new do
    sleep 30
    puts variable
  end
end
 
1_000_000.times do |i|
  variable = "Variable #{i}"
  puts_later variable
end

この比較的シンプルな例であっても、ガベージコレクタの仕事はかなり複雑になります。GCは、puts_laterメソッド内で変数が参照されていることを正しく認識しなければなりません。さらに、このメソッドはスレッドを起動するため、ガベージコレクタはスレッドを追跡し、その完了を待つ必要があります。変数をクリーンアップ対象としてマークできるのは、スレッドが終了した後だけなのです。

さらに複雑になるケース

複雑な例を挙げるまでもなく、ガベージコレクタの仕事がいかに困難であるかは想像に難くないでしょう。だからこそ、GCが本番環境でオーバーヘッドや問題を引き起こすことがあるのです。メモリを適切に解放するためには、プログラム内で何が起きているのかを極めて詳細に把握する必要があり、それには相当なCPUサイクルが費やされます。とはいえ、自分で後片付けをするよりはるかに楽ですね!

ガベージコレクションの世界はまだまだ奥深い

今回はガベージコレクションの導入編として、その基本的な考え方をご紹介しました。今後の記事では、RubyにおいてGCが具体的にどのように動作するのか、またアプリケーションのパフォーマンスを向上させるためにGCを計測・チューニングする方法について詳しく見ていきます。

更新:次回の記事はこちらからご覧いただけます。

  1. Linuxシェルスクリプト入門:変数の基本を徹底解説(第9回)

    これまでLinuxシェルスクリプティングに関する連載記事をお届けしてきましたが、当時は多くの方にご好評いただき、今でも十分に役立つ内容となっています。ここでは、変数の仕組み、実行方法、そしてシェルスクリプトでの実装について詳しく見ていきます。 コマンド出力の保存 コマンドの出力は標準出力やファイルにリダイレクトできるだけでなく、変数に格納することも可能です。コマンドの出力が大きすぎて画面に収まらない場合は、ファイルまたは変数に出力を保存するしかありません。変数に出力を保存する利点の一つは、確認速度が速いことです。変数はメモリ上に保存されるため、ファイルからの読み出しと比べて高速に処理されます。

  2. Google Chromeのメモリ使用量が多い?メモリリークの原因と対処法を徹底解説

    Google Chromeは軽快な動作とシンプルなインターフェースで、世界中で愛用されているブラウザです。筆者もその圧倒的なスピード感が気に入っており、アドオンが多く動作の重いFirefoxや、どうしても遅くなりがちなInternet ExplorerよりもChromeを選んできました。Microsoft EdgeはIEよりかなり高速になりましたが、GmailやGoogleドライブなどGoogleサービスを多用する身としては、やはりChromeが一番しっくりきます。ちなみに、新エンジン「Quantum」を搭載したFirefoxはChromeより速いとも言われており、乗り換えを本気で検討したこと