giftee engineer blog

RubyKaigi 2024 Day2 「Finding Memory Leaks in the Ruby Ecosystem」速報レポ

2024-05-16

ruby_memcheck によるメモリーリーク改善のお話の速報レポートです

thumbnail

こんにちは、ギフティのエンジニアの egurinko です。2024 年の RubyKaigi Day2 で行われた Peter Zhu さん、Adam Hess さんによる「Finding Memory Leaks in the Ruby Ecosystem」が大変面白く、速報レポートとしてご紹介したいと思います。

セッションの概要

Ruby 3.3 では Memory Leaks を検出する機能が追加されました。そこでこのセッションでは、Memory Leaks とは何か、Memory Leaks のインパクト、新しい機能の RUBY_FREE_AT_EXIT について紹介しています。

簡単な Memory Leaks の例

セッションでは、複雑なプログラムにおける Memory Leaks の見つけ方を解説しています。そこで、まずは簡単な Memory Leaks の例からセッションはスタートしました。

以下のようなコードの場合、while loop の中で Memory Allocation が行われています。

int main() {
  while(1) {
    char * name = malloc(256) // Allocate in Loop
    if(fgets(name, 256, stdin) == NULL) {
      break;
    }
    printf("%s", name);
  }
}

一方で、while loop の外側に char * name = malloc(256) を出すことで、Memory Allocation が 1 度だけになり、より良いコードとなります。

int main() {
  char * name = malloc(256) // Allocate once
  while(1) {
    if(fgets(name, 256, stdin) == NULL) {
      break;
    }
    printf("%s", name);
  }
}

このぐらいシンプルな例であれば、人が確認することで Memory の利用効率を改善することができますが、複雑なコードベースの場合はどうするのでしょうか?

Valgrind

そこで紹介されていたのが、Valgrind というツールです。Valgrind を使うことで memory leaks を検出することができます。しかし、Valgrind の実行結果を見ると、Ruby そのものに存在する Memory Leaks を検知してしまうため、Valgrind の結果に大量の Memory Leaks が出力され、意味のある結果を得ることが難しいと言われています。これについて詳しく知りたい方は、2022 年 RubyKaigi の Peter Zhu さんの発表をご参照くださいとのことです。

valgrind-result

そこで、ruby_memcheck を作成してこの問題に取り組んでいました。

ruby_memcheck

ruby_memcheck は Valgrind を利用し、Memory Leaks を検知しています。さらにその上で、経験則(heuristics)に基づき Ruby そのものの Memory Leaks の結果をフィルタリングするアプローチをとっていたそうです。しかし、このアプローチは hack のようなもので、根本的な対応には至らなかったそうです。

根本的な解決

冒頭の C コードの場合、下記のように Memory 解放をしてあげることでそもそも Memory Leaks がなくなり Valgrind には検出されません。

int main() {
  char * name = malloc(256) // Allocate once
  while(1) {
    if(fgets(name, 256, stdin) == NULL) {
      break;
    }
    printf("%s", name);
  }
  free(name); // Free before exit
}

そこで、全く同じ対応を Ruby でも行ったそうです(偉業ですね)。その機能は、Ruby 3.3 から入っており環境変数に RUBY_FREE_AT_EXIT=1 と設定することで使えます。

そして、この機能を使うことで、そもそも Ruby 由来の Memory Leaks のほとんどがなくなり、Valgrind の結果もかなり見やすくなったそうです。

元々は以下のように 104,272 件検出されていたそうですが、 valgrind-result-before

RUBY_FREE_AT_EXIT=1 にすると 32 件となり、大幅に改善されていることがわかります。 valgrind-result-before

RUBY_FREE_AT_EXIT=1 をどのように使えるか?

RUBY_FREE_AT_EXIT=1 を使うと、Ruby 由来ではない MemoryLeaks を検出できるため、有用な 2 つの使い方が紹介されていました。

1 つ目は、Native Gem の中の Memory Leaks チェックに使えるということです。実際に、nokogiri や liquid-c などの Memory Leaks を見つけたそうです。

2 つ目はアプリの中の Memory Leaks のチェックにも使えるとのことです。

まとめ

今回は RubyKaigi 2024 Day2 の「Finding Memory Leaks in the Ruby Ecosystem」について紹介させていただきました。このような Commiter の方々が Ruby パフォーマンスに貢献してくださっているのには本当に頭が下がるばかりです!

ギフティでは Ruby や Rails に興味のあるエンジニアも絶賛募集中です。気になる方は、是非一度カジュアル面談でもなんでも良いので一度お話ししましょう!

ギフティ採用ページを見てみる