#RubyKaigi 2024 LTで「Improved REXML XML parsing performance using StringScanner 」というタイトルで発表しました。

RubyKaigi 2024 LT6年振り にLT発表させて頂きました。

今回の内容は、おおよそ naitoh.hatenablog.com を5分に縮めた内容になります。

具体的には、下記になります。

  • 自分が作成している RBPDF gemSVG 画像(XMLで記述されています。)を処理したいので、XML処理ライブラリの中でインストールの容易な REXML を使いたい。
  • REXML は C拡張の gem の libxml-ruby と比較して dom で65倍、sax でも 21倍遅いので、速くしたい。
  • Ruby 3.3 で YJIT を有効にするだけで dom で65倍→44倍、sax で 21倍→14倍の性能差まで改善されるが、まだ遅いのでさらに改善したい。
  • RubyKaigi 2019 の Better CSV processing with Ruby 2.6 で、StringScanner を用いって CSV gem を速くした話で REXML も速くしたいという話があった。
  • REXML は Regexp クラスを用いて実装されているので、StringScanner で書き換えを実施すれば良いか検討。
    • 単純なケースでは StringScanner は Regexp より 1.67 倍速い
    • StringScanner の処理手順(先頭文字列から順次処理)と Regexp の処理手順(複数一括マッチ)を考慮すると、性能の単純比較は無理だが、メモリの効率の観点から StringScanner の方が最適化され速そう。
  • StringScanner への書き換えは下記の手順で実施。
    1. ベンチマーク追加
    2. プロファイラで最適化ポイントを調査し、XMLパース処理がボトルネックであることを確認
    3. XMLパース処理をStringScannerで書き換え
      • StringScanner のバグ修正
      • REXML の XML 仕様違反の修正
    4. ベンチマークで効果を確認
    5. 上記の繰り返し
  • 結果、下記まで性能改善することができた。
    • YJIT無効状態で、dom: 65倍→60倍に短縮、sax: 21倍→17倍に短縮。
    • YJIT有効状態で、dom: 44倍→25倍に短縮、sax: 14倍→8.6倍に短縮。🎉

speakerdeck.com

※ 資料で計測したコードは https://gist.github.com/naitoh/abc5134fdf37bb3952e36f1fb77163b0 になります。 (YJIT 無効時は、RubyVM::YJIT.enable の行をコメントアウトして計測。)

Ruby 1.8.7 の頃の絶望的(300倍くらい?)なREXMLの遅さ から比較すると、同じオーダの性能差まで短縮できたので、用途によっては本番環境でも使えるのではないでしょうか。

今回のLTは無事、時間内に終わりましたが、緊張して声が震えてしまいました。いろんな場でLTして緊張しないようになりたいですね。 今年のRubyKaigi も楽しかったです。感謝!