Slide 1

Slide 1 text

RuboCop Server Mode の仕組み Gotanda.rb#57 2024-03-06 - Hayato Kawai (@fohte)

Slide 2

Slide 2 text

あなた誰 名前: @fohte (ふぉーて) 川井 颯人 (Hayato Kawai) 所属: ウォンテッドリー株式会社 趣味: 🎮 🎹

Slide 3

Slide 3 text

RuboCop 使ってますか?

Slide 4

Slide 4 text

持ち帰ってほしいこと ● Server Mode で RuboCop の起動を高速化できる ○ プロセスを永続化することで高速化

Slide 5

Slide 5 text

RuboCop を爆速にしたい ● RuboCop は速くない ○ だいたい 2 秒〜 かかる ■ 1 ファイルに対しても 1 秒前後かかる ○ コーディング中はすぐにフィードバックしてほしい ■ 1 秒も待ちたくない!

Slide 6

Slide 6 text

rubocop-daemon gem を作りました (2018 年)

Slide 7

Slide 7 text

RuboCop 本体に 取り込まれました 🎉 (2022 年) https://github.com/ruboco p/rubocop/pull/10706 Big Thanks to @koic

Slide 8

Slide 8 text

詳しくは RubyKaigi 2022 の @koic さんの発表にて https://speakerdeck.com/koic/make-rubocop-super-fast

Slide 9

Slide 9 text

注意事項 ● 今回は rubocop-daemon の実装を紹介します ○ RuboCop の Server Mode と内部実装は同じなので、読み替え可能 ○ rubocop-daemon と Server Mode はコマンド体系が少し異なります ○ 詳細はドキュメントを参照してください : https://docs.rubocop.org/rubocop/usage/server.html

Slide 10

Slide 10 text

RuboCop はコマンド実行のたびに読み込み直すから遅い ● 通常の RuboCop はコマンド実行のたびに RuboCop を読み込む $ rubocop … require 'rubocop' RuboCop::CLI.new.run ターミナル RuboCop $ rubocop … require 'rubocop' RuboCop::CLI.new.run

Slide 11

Slide 11 text

RuboCop はコマンド実行のたびに読み込み直すから遅い ● この中でも require 'rubocop' が遅い $ rubocop … require 'rubocop' RuboCop::CLI.new.run ターミナル RuboCop $ rubocop … require 'rubocop' RuboCop::CLI.new.run

Slide 12

Slide 12 text

require 'rubocop'は遅い % ruby -rbenchmark -e '$LOAD_PATH.unshift("/path/to/rubocop/lib"); Benchmark.bm { |x| x.report { require "rubocop" }}' user system total real 0.573636 0.217166 0.790802 ( 0.815585) https://github.com/rubocop/rubocop/pull/10706#issuecomment-1152382752

Slide 13

Slide 13 text

require 'rubocop'は遅い ● require 'rubocop' のみで約 0.8 秒かかる ○ RuboCop のコア機能である「lint」は処理していない ■ lint するともう少し時間はかかるが、 1 ファイルだけであれば短い ■ ファイル数に比例して長くなる

Slide 14

Slide 14 text

require 'rubocop'は遅い ● 💡 つまりこれをキャッシュすればよいのでは? ○ これが rubocop-daemon のアプローチ

Slide 15

Slide 15 text

rubocop-daemon の高速化アプローチは単純 ● require 'rubocop' したプロセスを サーバーとして用意 (デーモン化) ○ このサーバーにリクエストする ○ アプリケーションサーバーを起動し続け、リクエストを待ち受けて処理するのと同じ rubocop-daemon (server) $ rubocop-daemon exec … ターミナル $ rubocop-daemon exec …

Slide 16

Slide 16 text

rubocop-daemon の RuboCop 実行フロー ● rubocop-daemon をサーバーとして起動する rubocop-daemon (server)

Slide 17

Slide 17 text

rubocop-daemon の RuboCop 実行フロー ● rubocop-daemon サーバーにリクエストする ○ サーバーでは RuboCop の内部メソッド (RuboCop::CLI#run) を 呼び出している rubocop-daemon (client) rubocop-daemon (server) 1. RuboCop 実行 リクエスト

Slide 18

Slide 18 text

rubocop-daemon の RuboCop 実行フロー ● サーバーが lint 結果を返す rubocop-daemon (client) rubocop-daemon (server) 1. RuboCop 実行 リクエスト 2. 実行結果返却

Slide 19

Slide 19 text

❯ ruby -rbenchmark -e 'Benchmark.bm { |x| x.report { `rubocop /path/to/file 2>&1 >/dev/null` }}' user system total real 0.000099 0.000637 0.602601 ( 0.886394) ❯ ruby -rbenchmark -e 'Benchmark.bm { |x| x.report { `rubocop-daemon exec -- /path/to/file 2>&1 >/dev/null` }}' user system total real 0.000050 0.000564 0.073490 ( 0.090813) ● 実行時間が 0.89 秒 -> 0.09 秒 (約 10 倍速) に短縮 🎉 rubocop-daemon で 10 倍弱高速化できる

Slide 20

Slide 20 text

余談: 今は LSP Mode もある ● RuboCop 1.53 から LSP Mode が追加された ○ https://docs.rubocop.org/rubocop/usage/lsp.html ● LSP = Language Server Protocol ○ VSCode 発 ○ エディタ上でコード補完や定義元ジャンプなど様々な機能を提供するための プロトコル ■ LSP サーバーに対し、エディタがサーバーに問い合わせる ■ Server Mode と同じような仕組み

Slide 21

Slide 21 text

LSP Mode と Server Mode はプロトコルが違うだけ ● LSP Mode: LSP を喋る ○ JSON-RPC でフォーマットが策定されている ○ 通信は TCP/IP が多い ● Server Mode: 独自プロトコルを喋る ○ "#{token} #{dir} #{command} #{args}\n#{body}" というフォーマット ■ RuboCop では version 情報が追加されていたりする ○ 通信は TCP/IP

Slide 22

Slide 22 text

エディタから使うなら LSP Mode がよさそう ● LSP は多くのモダンなエディタで実装されている ○ プラグインが提供されている場合も多い ● Server Mode だと、エディタが Server Mode の プロトコルを喋る必要がある ○ それ用の実装が必要 ○ エディタから rubocop コマンドを実行するのも手 ■ ただしサーバーに通信するための処理もあるので若干のオーバーヘッドがある

Slide 23

Slide 23 text

(再掲) 持ち帰ってほしいこと ● Server Mode で RuboCop の起動を高速化できる ○ プロセスを永続化することで高速化

Slide 24

Slide 24 text

(宣伝) 明日裏話をします Omotesando.rb #95 にて OSS の苦悩をはなします