💎

RubyKaigi 2022 Kotlin愛好家たまにRubyistによるセッションレポート Day2

2022/09/12に公開

株式会社TOKIUMの坂上(HN: にしこりさぶろ〜)です!この記事では、9月8日(木)-10日(土)の3日間に渡り開催されたRubyKaigi 2022にて、聴講したセッションの中でも印象に残ったモノをいくつか紹介していきます。

仕事では、AndroidエンジニアとしてKotlinのコードを書いている時間が最も長く、コミュニティ活動もKotlin開発者として参加することが多いため、「特定の企業が開発し、OSS化された」「静的型付け言語」の世界を主戦場とする開発者の目線からレポートを残そうと思います。

Day2で印象に残ったセッションは、以下の3つです

Method-based JIT compilation by transpiling to Julia

こちらのセッションは、JITコンパイラを用いたRubyの実行速度の高速化についての提案がメインのトピックでした。

アジェンダ

  • メソッドベースのJITコンパイラを利用した、Rubyの高速化手法の提案
    • 高速化のモチベーション
    • Rubyでの大規模な数値計算が遅い理由
  • 高速化の実現
    • Juliaを使ったバイトコード最適化 + コンパイル実行のアプローチ紹介

解説 #1: 大規模な数値計算におけるRubyの実行速度

Rubyは、昔から大規模なデータ処理・数値計算の領域で使いづらいと言われ続けてきた言語でした。Numo::NArrayやRed Arrowといったライブラリ資産が充実しつつある近年でも、その評価にあまり変化はありません。

大きな要因として、数値計算ライブラリを用いて計算アルゴリズムを実装しても、ピュアなRubyでは速度が遅いという問題が挙げられます。これは、MJITやYJITを有効にした場合であっても変わりません。

解説 #2: Rubyの大規模数値計算はなぜ遅いのか、そしてその解決策

Rubyの大規模数値計算がなぜ遅いのか。その理由は、Rubyの言語仕様が「メソッドをいつでも再定義できる」ようになっており、「再定義された直後からコードが影響を受ける」ことに帰結します。Rubyはこの仕様を常に維持しようとするため、メソッドの再定義がされているかのチェックがメソッドの呼び出し時に毎回実行され、実行速度に無視できない負の影響を与えます。

メソッドがいつでも再定義できる動的性は便利に扱えるケースもある反面、数値計算においては不要です。したがって、「メソッドの動的な再定義機能を制限し、再定義チェックをスキップするネイティブコードをJITコンパイラで吐き出す」ことができれば、ボトルネックを解決することができます。同じ動的型付け言語であるPythonのNumbaというコンパイラは、この手法を用いて高速化を実現しています。

解説 #3: 面倒なこと(IR生成や最適化)はJuliaにやらせよう

Numbaのコンパイルプロセスを参考にすると、IR生成やバイトコード最適化の部分を用意する必要に迫られます。これを1から自前で実装するのはあまりに大変なため、RubyのASTをJuliaのコードに変換し、最適化やネイティブコードへのコンパイルをJuliaに任せるアプローチを採用しました。

2つの言語間の仕様差を吸収するため、ブリッジ用のコードを書く必要はあったものの、いくつかの数値計算において圧倒的な高速化を実現することができました。

感想

数値計算の高速化というゴールの実現のため、ボトルネックの目星をつけ、失うモノ以上の利益を享受できるか分析した上で課題を解決していくプロセスが詳細に説明されており、非常に勉強になりました。

普段は業務時間の殆どをアプリケーションの実装に費やしているため、ネイティブコードへの変換の過程まで考慮して課題解決にあたるケースはなかなかないのですが、深い知識は課題解決の手数を飛躍的に増やしてくれるのだと、このセッションを聞いて改めて感じました。構文解析やコンパイラの動作原理等、時間を作ってキャッチアップしていきたいと思います💪

How fast really is Ruby 3.x?

スライド: https://github.com/fujimotos/RubyKaigi2022/blob/38fe29222e9a717f32d29eb6e604a387c6b18382/20220908-RubyKaigi2022.pdf

fluentdのCommiterであるFujimoto Seijiさんによる、Ruby 3系の実行速度分析を中心とするセッションです。

アジェンダ

  • Ruby 3系は実際にどの程度速くなったのか
    • Ruby 3x3が達成できたのか、fluentdの立場から検証する
    • Railsから見た実行速度の分析との違い
    • Ruby 3系と他言語の速度比較

解説 #1: Ruby 3x3について

Ruby 3.0は、「Ruby 3x3(=Ruby2の3倍速くする)」というスローガンの通り、パフォーマンスの改善に大きな主眼が置かれていました。実際にRuby 3.0がリリースされると、多くの開発者がそのパフォーマンスの分析に取り組みましたが、それらはRailsアプリケーションでの分析が殆どでした。

RailsはフルスタックなWebアプリケーションフレームワークであり、Railsアプリケーションの実行時間の大半はデータベースとの接続部分、MySQLやPostgreSQLで実行されるデータ処理の待機時間に費やされます。したがって、Rubyが3倍速くなろうとRailsの実行速度には大きな影響がなく、パフォーマンスの改善を実感することはなかなか難しいといえます。

解説 #2: Rubyとfluentdの関係について

fluentdは、1日に1億を超える量のデータの処理もこなすRubyで実装されたログ収集ツールです。多拠点から収集したログを1つにまとめ、各データストアにマッチする形式に変換し、格納する処理を高速に実行するため、Rubyのランタイムを極限まで使い倒しているプロダクトと言えます。

解説 #3: 過去のRubyとの速度比較

また、fluentdはRuby 1系の時代から開発が続いている息の長いプロダクトです。更に、過去のバージョンのRubyもそれぞれ同梱した状態でスナップショットを配布しているため、Rubyのバージョン毎のパフォーマンス比較には最適な要素が揃っています。

実際にパフォーマンス比較をしてみたところ、以下のような結果となりました。数値を見てわかる通り、fluendの立場からはRuby 3x3は概ね実現されたと言えます。

  • Large TSV
    • 1.9.3 → 3.2.0+YJITで3.15倍の高速化🎉
  • Nginxログ
    • 1.93 → 3.2.0+YJITで2.5倍の高速化🎉

それでは他の言語との比較はどうでしょうか。結論から言うと、テキスト処理の領域ではPerlやPython等、他の言語と見劣りするケースがあります。これは、Rubyだけでなく他の言語、特にPythonは毎年1.5倍の速度向上を繰り返し、4年で速度を5倍にしようという「シャノンプラン」に代表される改善施策が着実に成果を上げている背景もあります。

感想

Ruby 3x3が達成されたのか、Rails以外の視点から検証するという内容でしたが、発表そのものがfluentdだからこそできるコミュニティへの貢献になっていたことが印象的でした。そして、ただ速くなった側面だけでなく、他の言語まで対象に含めたケースでも検証し、コミュニティの成果を正当に伝えようとしていた点から、Rubyに対する深い愛を感じました。

弊社プロダクトでも利用しているfluentdについて、ドキュメントだけでは知り得ない部分まで知れたことも非常に良かったと思います。

Create my own search engine.

Rubyと自然言語処理をベースとするロジックで、ポケモンカードゲームのデッキ検索・類似度計算システムを実装した関将俊さんの発表です。

アジェンダ

  • ポケモンカードゲームのデッキ情報の検索サービスを実装した話
    • ポケモンカードゲームの基礎知識解説
    • サービスの機能紹介
  • 実装の詳細解説
    • 自然言語処理をベースとしたデッキの類似度計算について
    • カードデータの取得・管理について
    • Herokuを使ったシステム構成について

解説 #1: 実装したポケモンカードゲームのデッキ検索サービス、その機能紹介

ポケモンカードゲーム(以下ポケカ)とは、ポケモンの世界をカードで再現した、2人対戦のカードゲームです。カードには「ポケモン」「トレーナーズ」「エネルギー」の3種類が存在し、これらを60枚組み合わせた「デッキ」を作り、対戦します。

実装したデッキ検索サービスは、Twitterのツイートからポケカのデッキを収集し、それらを検索する機能があります。このシステムの中心は、「2つのデッキの類似度を計算する仕組みとデータ構造」になっており、よく似ているデッキを表示して差分からデッキ構築の工夫を見つけたり、デッキの変更履歴を追ったり、特定のカードを使用しているデッキを探したりができます。

解説 #2: 自然言語処理をベースとしたデッキの類似度計算の仕組み

ポケカのカードには一意のIDが割り当てられており、これらは公式サイトのカード検索で確認することができます。また、公式のデッキ構築サービスでは、登録するとデッキコードが発行され、デッキの情報を簡単にやりとりすることができます。

デッキ構築サービス上では、デッキは「カードID」と「枚数」のタプルの列で表現されています。これはよく見ると、「自然言語処理におけるベクトル化された文章」に似ていることが分かります。カードは単語、デッキはその制約から最大60単語しか含まれない文章に対応させることができることから、デッキは自然言語の文章のサブセットと言えます。

実際にデッキ情報から類似度を計算するには、同じ効果・意味のカードの正規化処理や、デッキに制限なく何枚でも入れられる基本エネルギーカードの類似度への影響を調整するなど、固有の処理が必要です。

解説 #3: Herokuを使ったシステム構成

このデッキ検索システムは、Herokuの無料プランで動作しています。30分のアイドルで停止するコンテナであるため、それを前提とした実装にしています。

クローラーでは、Twitterのタイムラインからデッキ構築サイトのURLを含むツイートを集めています。クローラーとサービスのコンテナ寿命はそれぞれ関連がないため、Heroku PGにデータを仲介する工夫をしています。

感想

自分の使いたいシステムを自由に実装した話でした。僕自身、個人開発でとあるキャラクターとKotlinを学べるWebアプリを開発したり、スマホ端末をペンライトにできるWebサービスをリリースしたりしていることから、大きなシンパシーを感じた発表でした。

特に、デッキの構築を自然言語の文章として扱う着眼点には驚かされました。原理を教えてもらえれば納得はできるのですが、配列データをパッと見ただけでこのインスピレーションに到れるのは、普段からデータ分析のタスクを数多くこなしているからこそなのでは、と感じました。

自分の個人開発プロダクトは、どうしても機能性やUIの美しさに主眼が向いてしまうため、目に見えない裏側のロジックの部分でもユニークな価値を生み出せるようになりたいと強く感じたりもしました🧐

【Day1・Day3のセッションレポートはこちら】

https://zenn.dev/tokium/articles/rubykaigi2022-day1-sakaue
https://zenn.dev/tokium/articles/rubykaigi2022-day3-sakaue

株式会社TOKIUM テックブログ

Discussion