rust and ruby
OpenBSD upgrade
on console
sysupgrade # finished auto reboot, same as apt full-upgrade sysmearge # for /etc/*.conf pkg_add -u # same as apt upgrade pkg_delete -a # same as apt autoremove cd /usr tar zxf ports.tar.gz cd /usr/src tar zxf src.tar.gz # and sys.tar.gz find . -name 'CVS' -exec rm -rf {} +
OpenBSDはOS部分とPKG部分が完全に分れている。だからアップグレードも2本立て。
kernelはsys、ユーザーランドはsrc、そしてpkgはportsのtar玉になってる。3ステップで、OpenBSDの全てがソースレベルでそろう。これが何といってもOpenBSDの魅力だ。
CVSって名残がちりばめられているんで、findで見付て削除しておく。sysmergeってsysmerge.shっていう600行のスクリプトなのね。思う所が有るんで、読んでみるかな。
rustc
ob$ rustc -V rustc 1.59.0
やっと、2021年式になった。
ruby
ruby 3.1.1p18 (2022-02-18 revision 53f5fc4236) [x86_64-openbsd]
タイミングが惡いとこうなる(emacs28も、タイミングが悪くて間に合わなかった)。
gdbに対処出来るようにソースを残しておこう。ならば、gdbで64bit幅のHEXを見せられるのは苦痛。そんな亊は昔から承知で32bit版のOSも用意してる。
普通にコンパイルしたら、ext/opensslの所でエラーになった。これはもう、カンニング竹山だなと思って、portsの中を調べてみたよ。
ob$ cd /usr/ports/lang/ruby/ ob$ ls 1.8/ 2.3/ 3.0/ patches/ 1.9/ 2.4/ 3.1/ pkg/ 2.0/ 2.5/ Makefile ruby.port.mk 2.1/ 2.6/ Makefile.inc rubygems-ext.PLIST 2.2/ 2.7/ files/
これは感激、歴代のrubyを再現出来るな。で、目当てなpatchが有るかな?
ob$ ls 3.1/patches/ patch-common_mk patch-compile_c patch-configure patch-ext_etc_etc_c patch-ext_extmk_rb patch-ext_openssl_ossl_pkey_c patch-ext_ripper_depend patch-include_ruby_internal_has_builtin_h patch-lib_fileutils_rb patch-lib_mkmf_rb patch-lib_rubygems_commands_install_command_rb patch-lib_rubygems_dependency_installer_rb patch-lib_rubygems_ext_ext_conf_builder_rb patch-template_builtin_binary_inc_tmpl patch-thread_pthread_h
ちゃんと用意されてたので、有り難く、目でpatchを適用。他にもパッチしておいた方がよさそうだけで、testしたらall-passだったので、取り敢えずこれでいく。
tracer by debug.gem
tracerをtraceすると言う再帰の実験。題材は何でもいいんだけど、これまたtracerから頂いた例を使う。お前、何も考えていないだろう! バレたらしょうがない。素直に認めるよ。
require 'tracer' Tracer.on class A def square(a) return a*a end end # a = A.new p a.square(5) Tracer.off
(rdbg) trace line into: LOG # command Enable LineTracer (enabled) into: LOG (rdbg) c # continue command #0:test.rb:3::-: class A #0:test.rb:3::C: class A #0:test.rb:4::-: def square(a) #0:/usr/local/lib/ruby/gems/3.1/gems/debug-1.5.0/lib/debug/session.rb:2056:Module:>: def method_added mid; end #0:/usr/local/lib/ruby/gems/3.1/gems/debug-1.5.0/lib/debug/session.rb:2056:Module:<: def method_added mid; end #0:test.rb:7::E: end #0:test.rb:9::-: a = A.new #0:test.rb:10::-: p a.square(5) #0:test.rb:4:A:>: def square(a) #0:test.rb:5:A:-: return a*a #0:test.rb:6:A:<: end 25 #0:test.rb:11::-: Tracer.off
debug.gemをざっと見したら、独自のtracerを実装してますね。
ob$ wc LOG 13310 79859 1288759 LOG ob$ cat LOG | grep -v rubygems | wc 78 467 7841
それはいいんだけど、なんでこんなに余計なものが走っているの?
irb(main):001:0> $: => ["/usr/local/lib/ruby/site_ruby/3.1", "/usr/local/lib/ruby/site_ruby/3.1/x86_64-openbsd", "/usr/local/lib/ruby/site_ruby", "/usr/local/lib/ruby/vendor_ruby/3.1", "/usr/local/lib/ruby/vendor_ruby/3.1/x86_64-openbsd", "/usr/local/lib/ruby/vendor_ruby", "/usr/local/lib/ruby/3.1", "/usr/local/lib/ruby/3.1/x86_64-openbsd"]
gemsって、あくまで vendor_ruby
の一員っぽいな。
rust and ruby
Improving Ruby Performance with Rust
The Tie Between Ruby and Rust.
def fib(n) return n if n <= 1 fib(n - 1) + fib(n - 2) end puts fib(38)
sakae@deb:~/artichoke$ time target/debug/artichoke fib.rb 39088169 real 1m3.490s user 1m3.171s sys 0m0.084s sakae@deb:~/artichoke$ time ruby fib.rb 39088169 real 0m10.121s user 0m10.040s sys 0m0.028s
debugバージョンだったから、性能が出ないのかな?
sakae@deb:~/artichoke$ time target/release/artichoke fib.rb 39088169 real 0m26.839s user 0m26.760s sys 0m0.012s
長い時間(20分)かけてrelease版を作った結果。まだまだMRIには勝てないな。でもリリース版にしたおかげで、当初の54Mから7Mにダイエット出来たよ。後、replと言うかirb相当も有るしね。
sakae@deb:~/artichoke$ target/release/airb artichoke 0.1.0-pre.0 (2022-04-27 revision 5606) [i686-unknown-linux-gnu] [rustc 1.60.0 (7737e0b5c 2022-04-04) on i686-unknown-linux-gnu] >>> 'hello'.size() => 5
じっくりとソースを眺めておけ。それがOSSの楽しみさ。
そして丸ごとと言う豪快な亊は勘弁と言うなら、rubyとrustの協調動作って方法がある。 rubyで時間を食てる部分をスポット的にrustにしてあげようと言う方法。ext-libを今迄はC言語で記述してたけど、それをrustで書きましょってやつね。
vbox$ cargo search ruru ruru = "0.9.3" # Native Ruby extensions in Rust rutie = "0.8.4" # The tie between Ruby and Rust. rutie-serde = "0.3.0" # rutie serde integration rosy = "0.0.9" # Ruby bindings for Rust. magnus = "0.2.1" # High level Ruby bindings for Rust. rurust = "0.2.0" # High level Ruby VM bindings
但し、ruruからrutieへと進化してるんだけど、ruby2.Xとか64Bit版とかshare-libでrubyが構築とか、linuxだけとか、制約が多い風だ。よって、オイラーは敬遠するよ。
from gem ?
sakae@deb:/tmp/fib$ rake rust_build --trace rake aborted! LoadError: cannot load such file -- rspec/core/rake_task <internal:/usr/local/lib/ruby/3.1.0/rubygems/core_ext/kernel_require.rb>:85:in `require' <internal:/usr/local/lib/ruby/3.1.0/rubygems/core_ext/kernel_require.rb>:85:in `require' /tmp/fib/Rakefile:5:in `<top (required)>' /usr/local/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/rake_module.rb:29:in `load' /usr/local/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/rake_module.rb:29:in `load_rakefile' /usr/local/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:710:in `raw_load_rakefile' /usr/local/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:104:in `block in load_rakefile' /usr/local/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:186:in `standard_exception_handling' /usr/local/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:103:in `load_rakefile' /usr/local/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:82:in `block in run' /usr/local/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:186:in `standard_exception_handling' /usr/local/lib/ruby/gems/3.1.0/gems/rake-13.0.6/lib/rake/application.rb:80:in `run' /usr/local/lib/ruby/gems/3.1.0/gems/rake-13.0.6/exe/rake:27:in `<top (required)>' /usr/local/bin/rake:25:in `load' /usr/local/bin/rake:25:in `<main>'
そんな亊知らんがな。浦島太郎だ。
VBOX$ gem install rspec VBOX$ rake -T rake build # Build rutie_ruby_example-0.1.0.gem into the pkg dir... rake build:checksum # Generate SHA512 checksum if rutie_ruby_example-0.1.... rake clean # Remove any temporary products rake clobber # Remove any generated files rake install # Build and install rutie_ruby_example-0.1.0.gem into... rake install:local # Build and install rutie_ruby_example-0.1.0.gem into... rake release[remote] # Create tag v0.1.0 and build and push rutie_ruby_exa... rake spec # Run RSpec code examples
rspecって、ruby流のテストツールだな。rakeにしろDSLだから、付き合いきれんぞ。
simple is good
rakeとかGemって苦手だ。取り敢えず、避けておこう。そこで、めちゃシンプルなやつを試す。
遅い所をC言語で書くんじゃなくてRust言語にしちゃえって方針。
sakae@pen:/tmp/rffi$ cat src/lib.rs #[no_mangle] pub extern "C" fn fib(n: u32) -> u32 { if n <= 1 { n } else { fib(n - 1) + fib(n - 2) } }
こんなスペックでコンパイルする。
sakae@pen:/tmp/rffi$ cat Cargo.toml [package] name = "rffi" version = "0.1.0" edition = "2021" [dependencies] [lib] name = "fib" crate-type = ["dylib"]
そして、出来た奴をrubyに取り込むGemを用意する。 sudo gem install ffi 断トツで使われているな。裏を返せば、rubyは遅いから、何とかしたいと皆あがいているんです。
下記は、その駆動方法。
sakae@pen:/tmp/rffi$ cat rfib.rb require "ffi" module Fib extend FFI::Library ffi_lib 'target/release/libfib.so' attach_function :fib, [:uint], :uint end puts Fib.fib(40)
そして、実行例。
sakae@pen:/tmp/rffi$ time ruby fib.rb 102334155 real 0m12.069s user 0m12.024s sys 0m0.042s sakae@pen:/tmp/rffi$ time ruby rfib.rb 102334155 real 0m0.516s user 0m0.457s sys 0m0.059s sakae@pen:/tmp/rffi$ time target/release/rffi ;; only rust 102334155 real 0m0.386s user 0m0.366s sys 0m0.020s
ゴールデンな例だけど、この方針で十分だろう。出来上がったlibfib.soのサイズが5.1Mあるけど、見なかった亊にしておく。
fiddle
上記では Gemで供給されてるffiってのを使ってしまったけど、オイラーはGem怖い。なるべく使いたくないって亊で、標準装備の fiddle ってのも試してみた(ruby 2.7にも同梱されてる)。 library fiddle
もう、そのものって亊ですな。dlopenで目ざすライブラリィーをオープン。それから、関数名と、入力の型(複数指定出来るように配列で)、それから出力の型を指定。後は呼ぶだけーーー。
ob$ cat ffib.rb require "fiddle" frust = Fiddle.dlopen('target/release/libfib.so') fib = Fiddle::Function.new( frust['fib'], [Fiddle::TYPE_INT], Fiddle::TYPE_INT ) puts fib.call(40)
実は、上の奴、rustをかじった人だと、型違いでコンパイルエラーになると看破するだろう。その点rubyは緩いからね。だからRBSなんてのが開発されるんだな。Ruby 3.1.0 リリース
ちゃんとするには、BasicTypesを入れて、uintを使えるようにする。新たにモジュールを作ってから使うのが、標準な方法みたい。日本生まれのrubyは日本語が充実してて嬉しいぞと。
[sakae@fb /tmp/rffi]$ cat zfib.rb require 'fiddle/import' require 'fiddle/types' module M extend Fiddle::Importer dlload 'target/release/libfib.so' include Fiddle::BasicTypes extern "uint fib(uint)" end puts M.fib(40)
肝は、rustに有り
ffiにしろfiddleを使うにしろ、呼出したい関数は、rustのsrc/lib.rsに記述した。 その味噌は、冒頭にある
#[no_mangle]
これのおかげで、関数名が素直に表現されている。ruby側から何の苦労もなく使える。
ob$ nm target/release/libfib.so | grep fib 000bd410 T fib 00000000 F fib.93c013d6-cgu.0 00000000 D rust_metadata_fib_5fa1543a93c013d6
このプログマを無効にして、コンパイルすると、fibって名前の前後に暗号めいた(rustで扱い易い)文字が付加されている。これじゃ、ruby側からおいそれとは、使えないだろう。
ob$ nm target/release/libfib.so | grep fib 000bd430 T _ZN3fib3fib17hca9b5a0f6149f16dE 00000000 F fib.93c013d6-cgu.0 00000000 D rust_metadata_fib_5fa1543a93c013d6
profiler
無闇やたらにsoファイルを作っても徒労に終わる。遅い所を見つけ出すのが肝要。そんな時に役立つのが、プロファイラーだ。少し探ってみる。
stackprof を、使った例。 プロファイラを使ってRubyのコードをパフォーマンス改善したい
library profile gem install profile
ちょいと使うには、これがお手軽。下記は、上で出て来たやつの実行例。
[sakae@fb /tmp/rffi]$ ruby -r profile zfib.rb 102334155 % cumulative self self total time seconds seconds calls ms/call ms/call name 98.06 1.83 1.83 1 1832.80 1833.04 Fiddle::Function#call 1.14 1.85 0.02 26 0.82 4.39 Kernel#require 0.09 1.86 0.00 1 1.67 1.67 Fiddle::Handle#initialize 0.06 1.86 0.00 2 0.60 2.50 Fiddle::Importer#dlload 0.05 1.86 0.00 96 0.01 0.01 Module#method_added 0.05 1.86 0.00 1 0.89 0.89 TracePoint#enable :