RubyKaigi 2018 2日目に参加してきました。お昼に弁当食べたあと、仙台城跡を見に歩いて行ってきました(帰りはタクシーだったけど)。天気が良くて満足度が高かったです。
My way with Ruby
活動されている内容のはなし。
- フリーソフトウェアを使って Ruby でできることをたくさん増やす
- ライブラリのメンテナンス
- 130くらいメンテナンスしているライブラリがある
自分にとって必要だったから。
Web の feed
RSS/ATOM
- RSS/ATOM の validation
- Ruby 2.6 以降はキーワード引数に対応
REXML
- REXML → Ruby Electric XML
- ライブラリ作者と Ruby を通じてやりとりすることになった
- コード懇親会しますよ
- Matz も参加しますよ
- 2010年からメンテナーになった
- Ruby だけで書かれている
- install が簡単
- 未来の話
- NodeSet オブジェクトが取れるAPIがほしい
- CSS セレクタがあると良さそう
- 優先度は高くない。一緒にやりたい人いる?
プレゼンテーション
- Rabbit
- 2004年にリリースした
- 2010年から Matz も使い始めた
- RDのサポートが必須
- Ruby Document
- Text ベースのマークアップ
- スライドの公開
- プレゼンテーションツールに必要な機能 → GUI
- Ruby/GTK3
- 不足している機能があったら追加してからプレゼンを作っているそう
- Ruby/GI
- 自動的に binding を用意してくれるものっぽい
- 手で書くよりはオーバーヘッドがあるため遅い
- JITで高速化したいそう
- Ruby/Poppler
- Ruby/GStreamer
- rcairo
- GC 関係で問題があった → Ruby 2.4 で直された
- 簡単なインストール
- rake-compiler
Test
- test-unit
- 2008年からメンテナーになった
- グループ化の機能を追加した
- データ駆動テスト
- 逆順のバックトレース
- Ruby 2.5 の機能を、test-unit でも同じ挙動にした
- RR
全文検索
- Rroonga
- Rabbit のサイトとるりまサーチで使用している
- I18n
- YARD
- RDoc
- jekyll
- groonga-client
- Ranguba (WIP)
データ処理
- CSV parser
- 2018年からメンテナーになった
- CSVフォーマットの問題点
- パースするのが難しい、パースが遅い
- Red Arrow
- Apache Arrowの公式Rubyバインディングになった
- Ruby/GI を使ってる
- Apache Arrow
- スゴイ速いデータフォーマット
- いろんな言語がサポート
- 最近のデータ処理界隈ではすごい大事な1ピース
- Apache Parquet
- スゴイ速いデータフォーマット
- Red Data Tools
- Red Data Sets
- jekyll + jupyter notebook
- Red OpenCV
- Ruby/GI を使ってる
- RubyData Workshop を今日の夕方にやるよ〜
Faster Apps, No Memory Thrash: Get Your Memory Config Right
Appfolio のひと
Tiny Objects
Symbol みたいなもの。Extra なメモリ領域を取らないもの。
Small Objects
String みたいな 40-byte slot なオブジェクト。メモリページごとに 408 Slots が割り当てられる。
Big Objects
Slot のサイズにあわないような大きな領域を確保するオブジェクト。
GC
Ruby は 世代別 Mark/Sweep GC を採用している。 とても良いが、best-in-the-world でない。
- Major GC は全てのオブジェクトをチェック
- Minor GC は新しいオブジェクトのみをチェック
手動でGC を動かす方法
GC.start # major GC.start(full_mark: false) # minor
GC のフェーズ
- オブジェクトが増える → ゴミを集める → slots を拡張する
GC の Mark & sweep は時間がかかる
- 小さなオブジェクトをたくさん使うより、大きなオブジェクトを1つ使う方がしばしば良い
- gsub! や concat を使うと、ゴミが減り Mark & sweep で必要だった CPU 使用量をセーブできます。
RUBY_GC_XXXX という環境変数で GC を制御できる。(https://qiita.com/yuroyoro/items/14ec7079f6574ad74409 に書かれているようなもの)
- 環境変数をセットすれば起動時から GC を調整でき起動時間を短縮できます。
EnvMem: A Memory Tool
https://github.com/noahgibbs/env_mem
gem install env_mem
jemalloc
Ruby は jemalloc をサポートしてる。 jemallocを使うとend-to-end でトータル10%-12% くらいスピードアップした。
スピードあげるためのソリューション
- ゴミを少なく
- 環境変数
- jemalloc
- 最新のRuby
フラグメンテーション
Slot を使うと、 ページ内の 408 Slots が解放されるまで移動できない
GC::Profiler
GC で何が起こっているか詳細を知りたかったら → GC::Profiler.enable
GC::Profiler.enable # DO something GC::Profiler.report
Guild Prototype
Guild の進捗の話
Background of Guild
Motivation
- Productivity
- Performance by Parallel execution
- Ruby では Thread が並列動作しない
- 多くの人々は複数の CPU コアをフルに使いたいと望んでる
デモ
- 40スレッド動く CPU + ubuntu 16.10
- fib(23) x 100_000 回
- Guild を使うと13分で終わった
- シングルスレッドだと 3.5時間
- fib(9)くらいから Guild のほうがスピードアップし始める
ruby/test/**/*
のワードをカウントする- シングルだと1.73 sec
- 40 Guilds だと 6.13 sec でシングルより遅くなった
- 遅くなったのは GC のせいらしい
仕様
- non-sharable
- mutable object (Array, String...) を Guild 間で共有できない
sharable
- immutable objects は Guild 間で共有できる
- Class/Module objects
- Special mutable objects
- Isolated Proc
Guild間の通信 API
- Actor model
- shareable object を送信する
- non-shareble object を移動する
Guild の実装
- Before
- VM → Threads → Fibers
- After
- VM → Guilds → Threads → Fibers
- ネイティブなスレッドが並列動作するようになる
GC の処理が走ると、その間、全てのGuilds が止まる
これからやること
- 機能
- Prohibit
- 同期
- Shareable プロトコル
- 最適化
Guild の名前について
プレフィックスを P (Process), T (Thread), F (Fiber) から避けた
問題点
"Move" オペレーションが一般的じゃない
mruby can be more lightweight
なぜ mruby が小さくないと駄目か
よくネット記事に載っているボードが 96K しか RAM がない
どうやるか
コンセプト
- define で mruby の機能を無効にし、RAM の使用量を減らす
- 実装をかえて ROM に置いて RAM を空ける
結果
- default だと 153 KB のRAMを使う
- 機能を無効にしていくと 83 KB
- ROM に載せて 67 KB
どうやったか
- 実行時に決まっているメソッド、Symbolを ROM に配置する
- 実行中に追加されるメソッド、Symbolは RAM に置く
- RAM 上のメソッドを探索し、なければ ROM から探すようにした
- Hash のデフォルトのテーブルサイズを無理矢理小さくした
問題点
remove_method
で ROM に配置されたメソッドを削除できない
所感
ガチで組み込みシステムな話だった。昔そういう業態にいたので懐かしかったが・・・。
Ruby Programming with Type Checking
Steep
- Gradual typing for Ruby
- コメントとしての型アノテーション
- プログラマーによる型定義
- ローカルの型推論
version 0.3.0を昨日リリースした
Sorbet との比較
デモ
デモのコード
1 + "2"
コマンド
$ steep check
以下のような型を定義したファイルを別途用意できる。
class Presentation @title: String @speaker: Speaker def initialize: (title: String, speaker: Speaker) -> any def print: (?_Puts) -> void def title: -> String def speaker: -> Speaker end
型チェックのステップ
- シグネチャーを書く
- Ruby コードをアノテーションを付けて書く
- steep check
1.
へ戻る
デモ
strictなチェックをしたい場合には steep check --strict
とする
steep scaffold
で型定義を自動生成できる
型定義の 40 % はライブラリのためだった。もし gem が型定義付きでリリースされれば、それを削減できる。
メタプログラミング
- Rails みたいにメタプログラミング使いまくっているところの型チェックは大変そう
- とりあえず、今のところ ActionArg 使ってくださいとのこと
RNode with code locations
- アイコンは猫ではなく、フクロウです。
- 来週からトレジャーデータにjoinしますとのこと。
2.5 で RNode を変え、コードレンジが増えている。 以下のケースで便利だから追加
- 例外
- warning
- テストのカバレッジ
カラムとは。
- 2.4 までは行番号しか保持していなかった
- 2.5 からカラム情報も追加した
- 行の先頭からどれくらい離れているかという情報
- Byte length
(1,0)
みたいな表現。(行, カラム)
なぜ必要になったか
2.5 で追加されたブランチカバレッジとメソッドカバレッジで必要になった
- ブランチカバレッジ → 分岐の片方をどれだけ実行したか。
- メソッドカバレッジ
可視化する際にカラム情報が必要だった。
- 3項演算子によりひとつの行は複数の分岐を書くことができる
- 行番号しか無いと、どれが実行されたか判別できないため
Ruby スクリプトの Tokenize
$ ruby --dump=y -e '1 + 2' | grep Shifting Shifting token tINTEGER (1.0-1.1: ) Shifting token '+' (1.2-1.3: ) Shifting token tINTEGER (1.4-1.5: ) Shifting token '\n' (1.5-1.5: ) Shifting token "end-of-input" (1.5-1.5: )
Parsing
$ ruby --dump=y -e '1 + 2'
Build AST
$ ruby --dump=p -e '1 + 2' ########################################################### ## Do NOT use this node dump for any purpose other than ## ## debug and research. Compatibility is not guaranteed. ## ########################################################### # @ NODE_SCOPE (line: 1, code_range: (1,0)-(1,5)) # +- nd_tbl: (empty) # +- nd_args: # | (null node) # +- nd_body: # @ NODE_PRELUDE (line: 1, code_range: (1,0)-(1,5)) # +- nd_head: # | (null node) # +- nd_body: # | @ NODE_OPCALL (line: 1, code_range: (1,0)-(1,5)) # | +- nd_mid: :+ # | +- nd_recv: # | | @ NODE_LIT (line: 1, code_range: (1,0)-(1,1)) # | | +- nd_lit: 1 # | +- nd_args: # | @ NODE_ARRAY (line: 1, code_range: (1,4)-(1,5)) # | +- nd_alen: 1 # | +- nd_head: # | | @ NODE_LIT (line: 1, code_range: (1,4)-(1,5)) # | | +- nd_lit: 2 # | +- nd_next: # | (null node) # +- nd_compile_option: # +- coverage_enabled: false
Compile
$ ruby --dump=i -e '1 + 2' == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,5)>============================== 0000 putobject_OP_INT2FIX_O_1_C_ ( 1)[Li] 0001 putobject 2 0003 opt_plus <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache> 0006 leave
実装の話
トークン作ったときカラム情報を保持し、ISeq まで順に渡すような変更を地道にされたそう。偉業ですね
応用例
- Proc のコードロケーションを取得するメソッドが作れる
- NoMethodError のメッセージをより詳細にできる
- AST オブジェクトを取ることができる → Ruby 2.6 preview 2 に入りました
Type Profiler: An analysis to guess type signatures
Ruby 2.6 むけにエンドレスRange (1..) を実装した
ary[1..] (1..).each { ...} ary.zip(1..) { |x,i| ...}
既存の提案されている型システムについて
以下の既存の型システムを検証した
- Steep
- RDL
- contracts.ruby
- dry-types
- RubyTypeInference (by JetBrains)
- Sorbet (by Stripe)
RDL
- アカデミックな世界では超有名
- gem もある
- 型アノテーションをコードとして書く必要があるそう
検証結果として、Steep みたいに型情報を Ruby とは別のファイルに書くのが Ruby 3 の要求仕様っぽい感じと受け止めている
Steep
静的な型検査。今日、発表があったのでスキップ
contracts
RDL みたいに型アノテーションをコードとして書く
RubyTypeInference
TracePoint を駆使してテストコードから動的に型情報を抽出しているそう
ここまでのまとめ
Steep がいまのところ一番好印象
Type Profile
今後整備していきたいようなものの話。
- 型情報を推定するもの
- 多少間違っていてもユーザが直してくれるようなゆるいものを想定
- いろいろ実験・検証がまだ必要とのこと