@ledsun blog

無味の味は佳境に入らざればすなわち知れず

Matz Keynote

僕と僕の所属する会社はRubyでの受託開発を生業にしています。 ここ数年は引き合いが多くて営業で困ったことがありません。 技術力を評価していただいている面もあると思いますが。 Rubyが魅力あふれるプログラミング言語で、お金を払ってでもRubyでアプリケーションを作りたい人が、世の中にたくさんいるので成り立っています。

Ruby言語の父、まつもとゆきひろさんいわく「Rubyを作っているのはコミュニティー」であるそうです。 つぎのようなコントリビュートを待っているそうです。

  • ブログで記事を書こう
  • バグレポートしよう
  • https://bugs.ruby-lang.org/ で、機能をリクエストしよう
    • 8割はリジェクトされます
  • バグをなおしましょう
    • githubでPRをうけつけています
  • 新機能のPRは、https://bugs.ruby-lang.org/ で議論して追加する決定をしないと、取り込まれません
  • ドキュメントの更新
    • typoの修正はいきなりPRでも受け入れます
  • Gemsを作りましょう
  • トリアージが回っていません
    • イシューがおおすぎて、大事なものを見落としています
    • 開発者会議の議事録が上手く残せていません
  • 日英以外の言語に翻訳してほしい
  • カンファレンス、ミートアップ、勉強会の開催
  • 開発者を雇って

そしたら RubyKaigi 2022 - ruby-jp にリストアップされているたくさんのブログが書かれました。 すばらしいコミュニティだなと思いました。

ruby/debug - The best investment for your productivity

rubykaigi.org

GitHub - ruby/debug: Debugging functionality for Ruby というRuby用のでデバッガの紹介です。

ruby/debugの主なコントリビューター

ruby/debugの主なコントリビューターが全員登壇する」というのが、僕の中の密かなRubyKaigi 2022 胸熱ポイントでした。

Stan LoさんもShopifyで働いているそうです。 ShopifyのRubyへの貢献度すごいですね。 と思ったら、割と最近、Shopifyに転職されたらしいです。 ShopifyのRubyコミッター吸引力すごいですね。

台湾出身らしくて、英語を話していても音が抜けなくて聞き取りやすかったです。 といっても聞き取れないので、デモをみながら一緒に ruby/debug の素振りをしてました。 ruby/debugをつかってみる - @ledsun blog が、その記録です。

Implementing Object Shapes in CRuby

rubykaigi.org

とても緊張してしゃべられてました。 丁寧なスライドで、英語全然わからない僕でも、Object Shapesってそういうものか、なるほどね!ってなったので、とても良かったです。

ていうのを、当日、直接本人に言えやって、いまさら思います。 英語でなんて言ったらいいかわからなくても、DeepLで翻訳した結果くらい見せさられただろうと・・・この記事を書きながら悔しく思っています。

Rubyの内部的にはインスタンス変数は配列に保存されています。 インスタンス変数にアクセスするときは、インスタンス変数名から配列のインデックスに変換します。 現状はハッシュテーブルでインスタンス変数名と対応するインデックスを管理しています。 Object Shapesを導入すると、ポインタを二つくらいたどるだけでインデックスがわかる。 という高速化手法だと理解しました。

その結果YJITで生成される機械語が、2行短くなって、メモリを読む箇所が3箇所から1箇所になるそうです。 どんだけ涙ぐましいチューニングなのか・・・。

計測すると、サブクラスの初期化時のインスタンス変数設定が2倍速くなるとか。 Rubyのクラスはほんとどサブクラスなので、これは効果でそうと期待します。 が、Railsベンチだと2%しか速くならなかったそうです。

めっちゃ頑張って実装したのに2%は悲しいですね。 そりゃ・・・緊張して発表しますわね・・・。

Feature #18776: Object Shapes - Ruby master - Ruby Issue Tracking System に発表と同様の説明があります。 DeepLで翻訳して読んでも、発表とちがってよくわかりません。 動画が公開されるが楽しみです。

20220927 追記

github.com

masterにマージされたようです。パチパチ

20220928 追記

ruby-trunk-changes.hatenablog.com

と思ったら、revertされたみたいです。 GC周りみたいですが・・・よくわかりません。

20220929 追記

ruby-trunk-changes.hatenablog.com

リベンジを果たしたようです。

How fast really is Ruby 3.x?

rubykaigi.org

GitHub - fluent/fluentd: Fluentd: Unified Logging Layer (project under CNCF) のメンテナの方が、Ruby 3でどれぐらい速く動くようになったか計測した話です。 fluentdでは1秒間に1万オブジェクトを作ることもあるそうです。 オブジェクト生成ならObject Shapesの効果ですね。

1000万行のログを読ませて、パースに掛かった時間を測ったそうです。 CPUバウンドな処理のようです。 JITは効くみたいですが10%程度だったみたいです。 同じ処理を繰り返す小さいプログラムはJIT効きそうなイメージがあります。 ただ、実際にはプログラムによって効いたり効かなかったりするのが不思議です。 JITの気持ちがわかりません。

Pythonより大幅に遅いという衝撃的なグラフが見られました。

こちらのツイートによると、どうやらGCに時間が掛かっているようです。 なるほど。大量のオブジェクトを生成した分、大量のオブジェクトを破棄しないといけないのかもしれません。

Ractorも試したかったけどPluginが動かなくなったという話も面白かったです。 ですよね。Pluginシステムはアーキテクチャが多きく変わると難しいですよね。

GitHub - fujimotos/RubyKaigi2022: RubyKaigi 2022 resources にて、発表資料や計測に使ったスクリプトが公開されています。

2022/09/18 追記

twitter.com

このベンチマークはTruffleRubyだと速く動くのだそうです。

Create my own search engine

rubykaigi.org

みんなが大好きな咳さんの発表です。

druby.hatenablog.com

ご本人のブログに記事があります。 発表資料もこちらから見れます。

検索エンジンを作りました。」という発表です。 全文検索をやろうとすると、たいてい次のような課題をクリアしないと着手できません。

ポケモンカードゲームだとこの部分が終わっているので、いきなり検索の実装の話に入れとのことでした。 いつも通り目の付け所が面白いです。

ポケモンカードゲームのデッキ検索エンジンをつくるには、デッキの類似度を決める必要があります。 類似度は、デッキの特徴を表現するベクトルを作って内積を計算すれば数値に出来ます。 数値にすると大小がきまるので、あるデッキに似たデッキが見つかります。

デッキのベクトルをどうやって決めるのでしょうか? 素朴に考えるとカードの種類を次元として含まれる数を特徴量とすれば良さそうです。 使い勝手がよく、よく使われるカードは、どんなデッキにも含まれます。 デッキの特徴を強調するために、こういカードは特徴量を小さくしたいです。 ここで使うのがIDF(inverse document frequency)です。 カードの使用頻度の逆数です。 これを特徴量に掛けると、デッキの特徴を目立つようにします。

カード情報は正規化されていると言いましたが15000件あるので手作業では作れません。 公式のカード検索サイトから取得します。 15000種類ありますが柄違いの同じカードもあるので、この正規化は必要です。 ここの正規化の仕方も何か説明していましたが、聞きそびれました。

この辺からだんだんわからなくなってます。

  • 公式のカード検索サイトが時々間違っているので目検してる
  • 特徴ベクトルの比較ではマージソートと同じように比較する
  • エネルギーカード*1はたくさん入るから特別扱いする
  • Herokuにデプロイしたので、インスタンスが定期的に死ぬ。dRubyが使えなかった

みたいな話もしてたと思います。

話を聞いていたら、(珍しく)自分でもできるかも?みたいな感想を持ちました。 で、あらためてまとめてみると、めちゃ色々やってて大変じゃないですかー。

参考

*1:マジック・ザ・ギャザリングの土地カードみたいなもの?

Ruby Committers vs The World

rubykaigi.org

Rubyコミッターを壇上に並べて眺めるセッションです。 特に海外のコミッターさんは顔と名前が一致しないので、ありがたいセッションです。

github.com

parser.yという悪魔城と呼ばれる現状中田さんしか触れないやつを置き換えるパーサーを作るそうです。

github.com

gem_rbs_collectionをコミュニティベースでメンテナンスしていくそうです。 RBSファイルをRubyリポジトリに入れることが決まったような・・・?

github.com

TypeProfの改良を進めて行きます。 マンパワーが必要なそうなので、協力者を募集しているそうです。

github.com

Rubyを速くするのはめちゃくちゃ楽しいでみんなやりましょう。 Pythonを置いてけぼりにするぐらい速くしたいそうです。

github.com

RubyをGoみたいに気軽に並列プログラミングできる言語にしたいです。

github.com

Rubyで高速データ処理をできるようにしたい。 須藤が作ったローレベルなものを使う部分を作ってほしいそうです。 MySQLからデータを受け取る部分がApache Arrow形式になったら速くなるんだとか。

github.com

Rubyのバグを0にしたいです。 JeremyさんはRubyのバグをなおしまくっている人だそうです。 さらに開発者会議のBookkeepingもやっていて大活躍らしいです。

error_highlight: user-friendly error diagnostic

rubykaigi.org

発表の前半の内容は 【RubyKaigi発表予告】error_highlight: user-friendly error diagnostics - クックパッド開発者ブログ にあります。

後半はRuby 3.2 でerror_highligthのエスケープ処理で、\が増えなくなった話でした。

ledsun@MSI:~/error_highlight►ruby -v -e '"\\\\\\\\\\\\\\\\".gsuub("", "")'
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]
-e:1:in `<main>': undefined method `gsuub' for "\\\\\\\\\\\\\\\\":String (NoMethodError)

"\\\\\\\\\\\\\\\\".gsuub("", "")
          ^^^^^^
Did you mean?  gsub
               gsub!

Ruby3.1で\の数が増えて、^^^^^^の位置がズレています。 -eで渡すとエスケープしてる分 \ が増えているので、わかりにくいですね。

ledsun@MSI:~/error_highlight►ruby -v -e '"\\\\\\\\\\\\\\\\".gsuub("", "")'
ruby 3.2.0preview2 (2022-09-09 master 35cfc9a3bb) [x86_64-linux]
-e:1:in `<main>': undefined method `gsuub' for "\\\\\\\\":String (NoMethodError)

"\\\\\\\\".gsuub("", "")
          ^^^^^^
Did you mean?  gsub
               gsub!

Ruby 3.2では直っています。

このRuby側のエスケープ処理は、2003年のとある記事に基づいているそうです。 記事の内容的にはRubyではなく、ターミナル側で対策した方が良さそうな内容だったそうです。 そこで主要なターミナルをしらべて対応済みなので、Rubyからは該当する処理を消したそうです。 このときターミナルのソースコードから、件の記事への言及を発見したと言われてました。 ソースコードまで確認するの、さすがだな、すごいな、と思いました。

Syntax Tree

rubykaigi.org

Rubyのパーサーを書き直そうという野心的なプロジェクトを持っている人の発表です。 プロジェクトに対するモチベーションに関してはこちらのツイートのスレッドを見るのがよいと思います。

発表内容は

Ripperの構文木のイベントは190あります。 整理すると160になります。

みたいな、話とRuby構文木を操作する一般的な話をしていたような気がします。 よくわかりません。

そもそもRubyに限らずプログラムの構文木を操作するプログラムを書いた事がありません。 RubyでつくるRuby 読書感想文 - @ledsun blog で、本の手順に従って動かしてみたぐらいです。 「RubyでつくるRuby」では構文木をたどって順番に処理していきます。 任意の目的のために構文木の特定のノードを探して特定のルールに則って編集する、そしてまたプログラムにもどす。 いわゆるトランスパイラみたなプログラムを書いたことがありません。

こりゃなんか一発トランスパイラみたなプログラムを書いてみる必要があります。 そういえば、直近でC#のプログラムを特定のルールに従ってまとめて編集したい要求があります。 「複数クラスが一つのファイルに書かれている箇所を、クラス毎にファイルをわけたい」です。 ルールは簡単だし、ツール作ればスケールするし、めっちゃ最高じゃないですか。 と言うことでググってRoslynって、C#用のコンパイラプラットフォームを使えばよさそうと調べてました。

発表内容と全然関係ない?いや、構文木というテーマとしてはドンピシャのアイデアをもらいました。 今回のRubyKaigi 2022で一番の即物的な収穫です。

それで練習し始めたのが Roslynを使ってC#のクラス名を変更する - @ledsun blog です。

参考

Real World Applications with the Ruby Fiber Scheduler

rubykaigi.org

https://github.com/socketry/rubydns を作っていました。 多数のリクエストに対応するために GitHub - eventmachine/eventmachine: EventMachine: fast, simple event-processing library for Ruby programs の導入を考えました。

DNSは一つのリクエストを受信すると複数の親サーバーにリクエストを送ります。 同期的に順番に問い合わせると時間が掛かります。 同時並行で問い合わせたいです。 現状のRubyで素直に実装すると送信するリクエスト毎にスレッドを立てて、レスポンスを待ちます。 IO待ちの場合は複数スレッドが同時に動きます。 これで同時並行の問い合わせが実現できます。 僕は LODQA : Question-Answering over Linked Open Data という検索エンジンをこんな構成で実装しました。 多分、似たようなだったのでしょう。

この方法の場合、処理できる問い合わせ数をスケールさせようとするとスレッドを作れる数が制約になります。 C10K問題では、スレッドのメモリ消費量が大きく、10000スレッド作れないと言ってた記憶しています。 C10K問題へんの典型的な回答がイベントループの実装です。 Rubyでイベントループを実装したものがEventMachineです。 つまり、もっともスタンダードな手法を試したという事だと思います。

EventMachineを導入するには、既存のコードを大きく書き換えないといけません。 アプリケーション全体を書き換えるのはやりたくないです。 そこで、Fiber Schedulerを作りました。 またFiber schedulerを使いやすくするために GitHub - socketry/async: An awesome asynchronous event-driven reactor for Ruby. を作りました。

https://socketry.github.io/async/guides/getting-started/index.html のサンプルコードを参考にして簡単なスクリプトを動かして見ます。

require 'async'

def slow_function
  Async do |task|
    task.sleep 1
  end
end

Async do
  slow_function
  slow_function
end

動かしてみて気がつきました。 これはJavaScriptのPromiseですね。 Asyncで包むとPromiseが返ってくるので、特別なことをしないと並行に実行されます。

Async do
  slow_function.wait
  slow_function.wait
end

待ちたい場合はwaitします。

もしかすると、いまはメモリ消費量はそんなに問題なくて、ネイティブスレッドを作ることが問題なのかもしれません。 そう考えるとMaNyでM:Nスレッドが実現すると、Asyncに置き換える必要すらなくスレッドで書いたコードがそのままスケールするようになるのかもしれません。

そういえばスレッドを使った非同期メソッドではRSpecのテストが失敗しなくて苦労した思い出があります。 これについては2018年のRubyKaigiでLTしてます。 Test asynchronous functions with RSpec - Speaker Deck スライドみてて思い出しました。 EventMachineのイベントループ中で遅い処理を挟むとサーバーが受けているすべてのリクエストへのレスポンスが遅くなるのでした。 それでスレッド使って非同期関数を使ってしのいでいました。 そしてRSpecでテストを書いたら、テストが失敗しなくて困りました。 なるほど、この用途で非同期関数を簡単に作れるのは嬉しいです。

あとはHTTPクライアントだけでなくて、ActiveRecordも速くなるそうです。 Rails 7のload_asyncと似たようなことを、Asyncをつかってやるのでしょうか? load_asyncはDBとのコネクションプールを余計につかうので、この辺も簡単にできるのは良さそうです。 イベントループをどこではじめるのか?などは、いまいちイメージがつかめていません。 あ、ちがいますね。 同一スレッドでうごいたらDBコネクションがわけられないですね。 それで Introduce `ActiveSupport::IsolatedExecutionState` for internal use by casperisfine · Pull Request #43596 · rails/rails · GitHub で、Thread.curretとFiber.currentを使い分けられるようにしたようです。

あとRails 7がFalconで動くみたいなことも言ってたので、機会をみて試してみたいです。

参考

追記

Help understanding Falcon/Rails/ActiveRecord Limitations and Future · Discussion #186 · socketry/falcon · GitHub を読む限りでは、現時点ではRails 7というかActiveRecordがFalcon上ではスッとは動かなさそうです。 たぶん、動くけどDBコネクションを共有するので、すべてのHTTPリクエストのSQLが直列に実行されて遅くなるのでしょうか? やっぱり、この辺は動かしてみないとイメージがつかめません。

https://github.com/rails/rails/pull/44219https://github.com/rails/rails/issues/42271で、対応を進めていそうです。 んー、でもReaperで死んだスレッドと紐付いているコネクションを自動開放する機能はFiberでどうやって実装するのでしょうか? AsyncでもFiberが死んだ状態を取れるのでしょうか?謎です。

A Faster CRuby interpreter with dynamically specialized IR

rubykaigi.org

圧倒的にわかりませんでした。 前提知識がたりません。

  • Sepcialization
  • Basic block versioning
  • RTL
  • IR(internal representation)
  • SIR
  • MIR

あたりがわかっていません。

IRがinstructionsと機械語の間の中間表現かなと理解しました。 それを特別にいじったものがSpecialized internal representationで、略してSIRと呼ぶみたいです。

Fixing Assignment Evaluation Order

rubykaigi.org

1行のRubyプログラムを左から順に評価していきます。 これは意図時にデザインしたルールではなく、実装の結果です。

a[0], a[1] = [b, b] のときはそうではありません。

発表者のJeremy Evansさんは、Rubyのバグを大量に直しているコミッターの方です。 そのなかでも面白いバグを初回してくれました。

内容はよくわからなかっかです。 ruby --dump=iを実行するとRubyのinstructionsを表示することがわかりました。 試しに a = 1のinstructionsを表示してみます。

ledsun@MSI:/m/c/U/led_l►ruby --dump=i -e 'a = 1'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,5)> (catch: FALSE)
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] a@0
0000 putobject_INT2FIX_1_                                             (   1)[Li]
0001 dup
0002 setlocal_WC_0                          a@0
0004 leave

表示されている命令の意味はよくわかりません。 a = 2b = 1のinstructionsを表示すると、内容が変わっていることがわかります。

ledsun@MSI:/m/c/U/led_l►ruby --dump=i -e 'a = 2'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,5)> (catch: FALSE)
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] a@0
0000 putobject                              2                         (   1)[Li]
0002 dup
0003 setlocal_WC_0                          a@0
0005 leave
ledsun@MSI:/m/c/U/led_l►ruby --dump=i -e 'b = 1'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,5)> (catch: FALSE)
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] b@0
0000 putobject_INT2FIX_1_                                             (   1)[Li]
0001 dup
0002 setlocal_WC_0                          b@0
0004 leave

この機能をつかって、a.b, c[0] = d, e のinstructionsを表示して、Ruby 3.0から3.2までの変更を説明してくれました。 実際に手元のPCで試してみると、発表と同じように表示されるinstructionsが変わっているのがわかります。

ledsun@MSI:/m/c/U/led_l►ruby -v --dump=i -e 'a.b, c[0] = d, e'
ruby 3.0.3p157 (2021-11-24 revision 3fb7d2cadc) [x86_64-linux]
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,16)> (catch: FALSE)
0000 putself                                                          (   1)[Li]
0001 opt_send_without_block                 <calldata!mid:d, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0003 putself
0004 opt_send_without_block                 <calldata!mid:e, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0006 newarray                               2
0008 dup
0009 expandarray                            2, 0
0012 putself
0013 opt_send_without_block                 <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0015 topn                                   1
0017 opt_send_without_block                 <calldata!mid:b=, argc:1, ARGS_SIMPLE>
0019 pop
0020 pop
0021 putself
0022 opt_send_without_block                 <calldata!mid:c, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0024 putobject_INT2FIX_0_
0025 topn                                   2
0027 opt_aset                               <calldata!mid:[]=, argc:2, ARGS_SIMPLE>
0029 pop
0030 pop
0031 leave
ledsun@MSI:/m/c/U/led_l►ruby -v --dump=i -e 'a.b, c[0] = d, e'
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,16)> (catch: FALSE)
0000 putself                                                          (   1)[Li]
0001 opt_send_without_block                 <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0003 putself
0004 opt_send_without_block                 <calldata!mid:c, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0006 putobject_INT2FIX_0_
0007 putself
0008 opt_send_without_block                 <calldata!mid:d, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0010 putself
0011 opt_send_without_block                 <calldata!mid:e, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0013 newarray                               2
0015 dup
0016 expandarray                            2, 0
0019 topn                                   5
0021 swap
0022 opt_send_without_block                 <calldata!mid:b=, argc:1, ARGS_SIMPLE>
0024 pop
0025 topn                                   3
0027 topn                                   3
0029 topn                                   2
0031 opt_aset                               <calldata!mid:[]=, argc:2, ARGS_SIMPLE>[CcCr]
0033 pop
0034 pop
0035 setn                                   3
0037 pop
0038 pop
0039 pop
0040 leave
ledsun@MSI:/m/c/U/led_l►ruby -v --dump=i -e 'a.b, c[0] = d, e'
ruby 3.2.0preview2 (2022-09-09 master 35cfc9a3bb) [x86_64-linux]
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,16)> (catch: false)
0000 putself                                                          (   1)[Li]
0001 opt_send_without_block                 <calldata!mid:a, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0003 putself
0004 opt_send_without_block                 <calldata!mid:c, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0006 putobject_INT2FIX_0_
0007 putself
0008 opt_send_without_block                 <calldata!mid:d, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0010 putself
0011 opt_send_without_block                 <calldata!mid:e, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0013 newarray                               2
0015 dup
0016 expandarray                            2, 0
0019 topn                                   5
0021 swap
0022 opt_send_without_block                 <calldata!mid:b=, argc:1, ARGS_SIMPLE>
0024 pop
0025 topn                                   3
0027 topn                                   3
0029 topn                                   2
0031 opt_aset                               <calldata!mid:[]=, argc:2, ARGS_SIMPLE>[CcCr]
0033 pop
0034 pop
0035 setn                                   3
0037 pop
0038 pop
0039 pop
0040 leave

どうやらJeremy Evansさんはこういうところを直しているみたいです。 instructionsが読めないので、具体的にどうなおしたのかはわかりませんでした。

を読んだら理解できるようになるでしょうか?

tatsu-zine.com

History of Japanese Ruby reference manual, and future

rubykaigi.org

t.co

Rubyのリファレンスマニュアルの歴史の話でした。

RD, RWiki, ruremaと変わってきました。

bticlustって時々聞くなあと思ったら、RDの代わりになるツールみたいです。 Ruby 1.8のころからあるみたいです。 僕が観測したのは、鹿児島Ruby会議01 で、盛り上がってはなちんさんとpockeさんが活動してって頃でした。 2019年末です。

Rubyのリファレンスマニュアルの整備では大きく二つの対象があります。

  1. ドキュメントそのもの
  2. ドキュメントを生成するツール bitclust のメンテナンス

ドキュメントの整理では

  • コピペ可能なサンプルコードの整備
  • サンプルコードの色づけ

などが止まっているそうです。

bitclustの整備では

  • RDをMarkdownに変えて、編集出来る人を増やしたい
  • Changelogやsetup.rbなどいらなくなったファイルを整理したい

help wanted。 参加したい人は https://github.com/rurema/doctree か、ruby-jp slackの #rurema チャンネルで連絡して欲しいそうです。

Stories from developing YJIT

rubykaigi.org

は、RubyのYJITでどんな挑戦をしてきたかの話でした。

最初はRubyのinstructionを1:1で機械語に置き換えるところか始めました。 Railsでは遅くなりました。 つぎにJIT部分をフロントエンドとバックエンドに分けました。

もう、この辺でわかりません。 フロントエンドとバックエンドにわけるとなにがうれしいのか、そもそもなんで1:1の変換で遅くなるのか? わかってないなりに続きを聞きました。

生成した機械語のコードが条件分岐を増やすので、CPUの予測がはずれやすくなりました。 その対策としてLazy Basic Block Versioningを導入しました。

「Lazy Basic Block Versioning」はJITに関連した発表で、ちょいちょい聞く単語です。 説明してくれてうれしいです。 わかりません。 なんかstubを使うらしいです。 ある変数の型が実行中に変わることが少ないので、変わらないと仮定して、stubでキャッシュして使い回す?

YJITはRubyよりinstructionsを減らしました。

instructionsもよくわからないんです。 Rubyがinstructions持っているのはわかるんですが、YJITも持っているのがわかりません。 YJITがinstructionsと機械語と持つようになったのが、フロントエンドとバックエンドにわけたという意味なのでしょうか?

  • cfp-sp (stack pointer)
  • cfp-pc (program counter)

サンプル見せて説明してくれたのですが、よくわかりません。 stackのなかみのメモリに書き出すみたいです。 でも、どんなときに書き出すのかとか、書き出すとなにがうれしいのかなどわかりませんでした。

追記

理解の参考になりそうなツイートを集めました。

20220918 追記

Ruby が YJIT でなんで速くなるのか? Lazy Basic Block Versioning をサクッと理解してみた - estie inside blog

Roslynを使ってC#のクラス名を変更する

Roslynを使ってC#のソースコードを編集する - @ledsun blog でRoslynをつかうとC#ソースコードが編集出来るとわかりました。 編集内容を変えて練習します。 クラス名を変えてみます。

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

var st = CSharpSyntaxTree.ParseText(@"
public class TargetClass 
{
    // This is a comment
    public void M() {
        1 + 1;
    }
}
");
var root = st.GetRoot();
var klass = root.DescendantNodes().OfType<ClassDeclarationSyntax>().Single();
var rewriten = root.ReplaceNode(klass, klass.WithIdentifier(
    SyntaxFactory.ParseToken("Abc")
    )
);
Console.WriteLine(rewriten);

全体の流れはfor文の編集と同じです。 クラス名の変更ではつぎのようなコードが出てきます。

klass.WithIdentifier(
    SyntaxFactory.ParseToken("Abc")
)

変更したいノード毎に定義されているWithXxxxメソッドで変更したい項目を変更します。 WithXxxxメソッド毎に受け取るSyntaxXxxx型が決まっています。 SyntaxFactory.ParseXxxxメソッドを使って、渡すインスタンスを作ります。

このメソッドの組み合わせの勘が効くようになるまでは練習が必要そうです。

実行結果です。

実行結果

クラス名の後ろの改行は消えました。 この辺を保持しつつ変更する方法もあるのかもしれません。

Roslynを使ってC#のソースコードを編集する

自分で自分用のC#ソースコード編集ツールをつくったら捗りそうなことに気がつきました。 ググってみたら GitHub - dotnet/roslyn: The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs. というのでC#ソースコードのパースとコード生成が出来そうです。

c# - Edit loop in Roslyn - Stack Overflow で、それっぽいサンプルコードを見つけました。 試しにうごかしてみます。

まずは簡単そうなほうから試します。

// https://stackoverflow.com/questions/25568802/edit-loop-in-roslyn
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

SyntaxTree tree = CSharpSyntaxTree.ParseText(
    @"using System;
using System.Collections.Generic;
using System.Text;

static void Main(string[] args)
{
    for(int i=0; i<10; i++)
        int a = i;
}");
var root = tree.GetRoot();

var forStmt = root.DescendantNodes().OfType<ForStatementSyntax>().Single();
var rewritten = root.ReplaceNode(forStmt,
    forStmt.WithCondition(
        SyntaxFactory.ParseExpression("i>=0")
    ).WithStatement(
        SyntaxFactory.ParseStatement(@"    {
        Console.WriteLine(i);
        Console.WriteLine(i*2);
    }
")
    ));

Console.WriteLine(rewritten);

動かすと次のように編集されたソースコードが表示されます。

実行結果

勉強のために説明をしてみます。

SyntaxTree tree = CSharpSyntaxTree.ParseText(
    @"using System;
using System.Collections.Generic;
using System.Text;

static void Main(string[] args)
{
    for(int i=0; i<10; i++)
        int a = i;
}");

で、C#をパースして構文木に変換します。

var forStmt = root.DescendantNodes().OfType<ForStatementSyntax>().Single();

構文木からfor文を表すノードを取得します。

var rewritten = root.ReplaceNode(forStmt,
    forStmt.WithCondition(
        SyntaxFactory.ParseExpression("i>=0")
    ).WithStatement(
        SyntaxFactory.ParseStatement(@"    {
        Console.WriteLine(i);
        Console.WriteLine(i*2);
    }
")
    ));

で、for文を編集したものに置き換えます。 ここがややこしいです。

  1. 破壊的な変更ではない。もとのrootを変更するのではなくて、変更された新しい構文木が生成されます。
  2. 作りたい文にあわせて、良い感じのノードを作る
Console.WriteLine(rewritten);

で、構文木からコード生成します。 文字列化するだけで、コードが帰って来ます。

もう一つのサンプルも動作は同じです。

// https://stackoverflow.com/questions/25568802/edit-loop-in-roslyn
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

SyntaxTree tree = CSharpSyntaxTree.ParseText(
    @"using System;
using System.Collections.Generic;
using System.Text;

static void Main(string[] args)
{
    for(int i=0; i<10; i++)
        int a = i;
}");
var root = tree.GetRoot();
var rewritten = new Rewriter().Visit(root);
Console.WriteLine(rewritten);

class Rewriter : CSharpSyntaxRewriter
{
    public override SyntaxNode VisitForStatement(ForStatementSyntax node)
    {
        // update the current node with the new condition and statement
        return node.WithCondition(
            SyntaxFactory.ParseExpression("i>=0")
        ).WithStatement(
            SyntaxFactory.ParseStatement(@"{
              Console.WriteLine(i);
              Console.WriteLine(i*2);
            }")
        );
    }
}

構文木を変更するややこしい部分を Rewriter クラスに分離しています。