ruby Ractor
Table of Contents
gauche test
何でも揃えてあるgaucheにテストのフレームワークは用意されてるか? Gauche — Practical Scheme Scripting
おや、看板が一新されてるぞ。rubyと同じ様に何かの記念? ひょっとしたら 1/4 世紀 経過しました、ぐらい?
やっぱり提供されてた。凄いなあ。じっくり拝見しましょ。
object dump
簡単にオブジェクトを点検できるらしいので、emacsから確認できる様にしてみた。
require "json" require "objspace" def dump(obj) JSON.pretty_generate(JSON.parse(ObjectSpace.dump(obj))) end str = '日本' puts dump(str)
inf-rubyを入れると、 inf-ruby (C-c C-s)でirbを起動、C-c C-q で終了する様になる。ソース窓の所で ruby-load-current-file (C-c C-k) すれば結果がirbの窓に出てくる。
irb(main):001> {
"address": "0x7fc9e6ce3af8",
"type": "STRING",
"shape_id": 0,
"slot_size": 40,
"class": "0x7fca029eec50",
"embedded": true,
"chilled": true,
"bytesize": 6,
"encoding": "UTF-8",
"coderange": "valid",
"memsize": 40,
"flags": {
"wb_protected": true,
"age": 0
}
}
=> true
こんな評価結果が表示された。
oracle problem
前回の最後にチャッピー君が話題を拡げる為、期待値を持たない検証手法(oracle problem) 面白いですよと振ってきた。初見なので馬鹿にされない様に下調べしとく。だって、 銀座のママ宜しく、教養を試している節が有るからね。
これを拝読すると、テスト・コードを必ず記述しましょとは全く違う次元の 話だって事が良くわかる。普通のテストは、リファクタリングしても機能が損傷しなかったかの 確認ってか、免罪符でテストしてるのかな(猛抗議を受けそう)。
Open3
前回のコード・リーディングで解った積りになってた部分を取り出してきて、 味わってみる。
def run_program(cmd, input)
stdout, stderr, status = nil
Timeout.timeout(TIMEOUT_SEC) do
stdout, stderr, status = Open3.capture3(cmd, stdin_data: input)
end
unless status.success?
raise "runtime error (exit=#{status.exitstatus}): #{stderr.strip}"
end
parse_output(stdout)
rescue Timeout::Error
raise "timeout"
end
ここから読み取れるのは、終了ステータスが成功じゃなかった場合、runtime errorと 判定して、詳細な事は、stderrを参照できるって事だ。エラー処理も綺麗に記述 できる物だね。何かの時の参考にしよう。
caputure3ってOS的には、forkしてexecして親子の絆を結んで通信。更にはwaitで、
子の終了を待つっていう複雑な事をしてるはず。lib/open3.rbを見たら、さりげない
風に定義されてたぞ。IOは、 out_reader = Thread.new { o.read } こんな風に、
待ちになったら、他に譲るっていう無駄の無い設計になってた。
Ractor
gaucheの説明書を見てたら、 9.35 gauche.threads - スレッド もサポートしてた。使い所と注意点も説明されてて嬉しい。 そんじゃrubyはと探してみたら、Threadの更に上を行く機能を発見。 その名はRactor。
Threadって所詮1つのCPUでの実行でしかなかった。Ractorは、複数のCPU でタスクを実行するんで、パソコンをこき使う感がして得した気分になりそう。 実例が出てたので確かめてみる。
sakae@lu:tmp$ ruby rac.rb 処理開始 rac.rb:18: warning: Ractor API is experimental and may change in future versions of Ruby. タスクA: 開始 タスクB: 開始 タスクB: 終了 (5.385616974s) タスクA: 終了 (5.396340336s) 全処理終了 (5.396918182s)
まだ実験の最中だからって断りが出てきた。3.X時代はtakeで待ち合わせなんだけど、 ruby 4.0.0では、joinになってる。
# 重い計算処理(CPUバウンドなタスク)
def cpu_heavy_task(task_name)
puts "#{task_name}: 開始"
start_time = Time.now
100_000_000.times do |i|
dmy = i * i
end
end_time = Time.now
puts "#{task_name}: 終了 (#{end_time - start_time}s)"
end_time - start_time
end
puts "処理開始"
start_total_time = Time.now
r1 = Ractor.new { cpu_heavy_task("タスクA") }
r2 = Ractor.new { cpu_heavy_task("タスクB") }
result1 = r1.join
result2 = r2.join
end_total_time = Time.now
puts "全処理終了 (#{end_total_time - start_total_time}s)"
Ractorとなっている所をThreadに変更すると、1CPUの能力を分けあって動く コードになる。
処理開始 タスクA: 開始 タスクB: 開始 タスクA: 終了 (10.412197686s) タスクB: 終了 (10.225076968s) 全処理終了 (10.426710411s)
その結果、処理時間は倍になる。勿論、タスクを1つだけにすれば、処理時間は 半分になるのは、言うまでも無い(って、実行してるじゃん)。
処理開始 タスクA: 開始 タスクA: 終了 (5.13967108s) 全処理終了 (5.140015627s)
先輩はgolangだ。goと言う魔法の語句を付けるだけで、複数CPUを利用してくれる。 開発当初からサポートされてるんで、安定してるぞ(オイラーは使用実績無しですけど)。
package main
import (
"fmt"
"sync"
"time"
)
func cpuHeavyTask(taskName string, wg *sync.WaitGroup) {
defer wg.Done() // タスク完了時にWaitGroupに通知
fmt.Printf("%s: 開始\n", taskName)
startTime := time.Now()
for i := 0; i < 10_000_000_000; i++ {
_ = i * i
}
endTime := time.Now()
fmt.Printf("%s: 終了 (%v)\n", taskName, endTime.Sub(startTime))
}
func main() {
fmt.Println("処理開始")
startTime := time.Now()
var wg sync.WaitGroup // 終了待ちのためのWaitGroup
wg.Add(2)
go cpuHeavyTask("タスクA", &wg)
go cpuHeavyTask("タスクB", &wg)
wg.Wait()
endTime := time.Now()
fmt.Printf("全処理終了 (%v)\n", endTime.Sub(startTime))
}
goroutineの実装 これを見ると、src/runtimeに実装されてるんだね。
io.Readerをすこれ 上記を探してる時、見つけたもの。interfceはgoの 隠れた銘品だそうです。
group_by
前回のblack-test結果の一部。
--- Test: single --- genA: ERROR: invalid output line: "a 1 \n" genB: OK (1 symbols) genC: OK (1 symbols) genD: OK (1 symbols) ✅ Majority result (2/3 agree) Agreeing: genB, genC
良く見ると、3人の候補のうち、2人(genB, genC)の意見が一致し、genDと意見が 割れている。
参考に huffman.go にも参戦してもらう。作成者曰く、メタモルフィック関係の考慮 と言う事で、ハフマン符号は左右の枝に0と1のどちらを割り当てるかで結果が変わるため、この実装では左を . (0)、右を - (1) に固定しています。 の説明が有った。
rdbgで92行目にBPを置いて確認。
[87, 96] in test.rb
87| if outputs.size < 2
88| warn "Not enough successful programs for voting"
89| next
90| end
91|
=> 92| winner, votes = majority_vote(outputs)
93|
94| if votes == 1
95| warn "❌ No majority agreement"
96| outputs.each do |prog, out|
=>#0 block {|name=:single, input="a"|} in <main> at test.rb:92
#1 [C] Hash#each at #<none>:0
# and 1 frames (use `bt' command for all frames)
Stop by #0 BP - Line /tmp/huffman/test.rb:92 (line)
(rdbg) pp outputs # command
{genB: {"a" => [1, "."]},
genC: {"a" => [1, "."]},
genD: {"a" => [1, "-"]},
golang: {"a" => [1, "."]}}
genDの結果が他と異なっていた。
mruby
OpenBSDではportsになってるんだけど、何となくREADMEを眺めていたら、 rakeでも大丈夫そうだったので、試してみた。
https://github.com/mruby/mruby/archive/refs/tags/3.3.0/mruby-3.3.0.tar.gz
ob$ rake
:
gems/mruby-bin-strip/tools/mruby-strip/mruby-strip.o
LD build/host/bin/mruby-strip
ld: warning: parse.y:4910 (mrbgems/mruby-compiler/core/parse.y:4910)(y.tab.o:(p
arse_string) in archive /tmp/mruby-3.3.0/build/host/lib/libmruby.a): warning: s
trcat() is almost always misused, please use strlcat()
GEN build/host/bin/mruby-strip -> bin/mruby-strip
Build summary:
================================================
Config Name: host
Output Directory: build/host
Binaries: mrbc, mruby-config
Included Gems:
mruby-array-ext - Array class extension
mruby-bigint - Integer class extension to multiple-precision
mruby-bin-config - mruby-config command
mruby-bin-debugger - mruby debugger command
- Binaries: mrdb
;
Config Name: host/mrbc
Output Directory: build/host/mrbc
Binaries: mrbc
Included Gems:
mruby-bin-mrbc - mruby compiler executable
mruby-compiler - mruby compiler library
コンパイル時間を計測
2m02.55s real 1m41.32s user 0m15.66s system ; real i386 5m25.22s real 1m00.33s user 3m18.80s system ; VMWare(amd64)
仮想マシンの性格が良く表われているな。OSに依頼する事が有るとオーバーヘッドが 非常に過大になるとな。それにIO待ちな時間も多い。 userでの実行ならば、変な介入が無いので、amd64の方が速い。 あるいは、マシンが高速だからだな。 余興でも、こういうデータを取得しておくと参考になるな。
CPP src/allocf.c -> build/host/src/allocf.pi CPP src/array.c -> build/host/src/array.pi : CPP mrbgems/mruby-compiler/core/y.tab.c -> build/host/mrbgems/mruby-compiler/core/y.tab.pi CC mrbgems/mruby-bin-mrbc/tools/mrbc/mrbc.c -> build/host/mrbc/mrbgems/mruby-bin-mrbc/tools/mrbc/mrbc.o CC mrbgems/mruby-bin-mrbc/tools/mrbc/stub.c -> build/host/mrbc/mrbgems/mruby-bin-mrbc/tools/mrbc/stub.o CC src/allocf.c -> build/host/mrbc/src/allocf.o CC src/array.c -> build/host/mrbc/src/array.o : AR build/host/mrbc/lib/libmruby_core.a ar: warning: creating /tmp/mruby-3.3.0/build/host/mrbc/lib/libmruby_core.a LD build/host/mrbc/bin/mrbc ld: warning: parse.y:4910 (mrbgems/mruby-compiler/core/parse.y:4910)(y.tab.o:(parse_string) in archive /tmp/mruby-3.3.0/build/host/mrbc/lib/libmruby_core.a): warning: strcat() is almost always misused, please use strlcat() GEN mrblib/*.rb -> build/host/mrblib/mrblib.c :
そしてコンパイルの過程は上記の様。しっかりbisonの世話になってる。rubyに特化 したパーサーの出番って有るようで無いような、有るみたい(我ながら日本語がおかしいな)。 次は期待のgdb登場。
ob$ gdb -q mruby
Reading symbols from mruby...
(gdb) b write
Breakpoint 1 at 0xdd550
(gdb) r -e 'puts("HELLO")'
Starting program: /tmp/mruby -e 'puts("HELLO")'
(gdb) bt
#0 __i386_read_tcb (offset=0) at /usr/include/machine/tcb.h:41
#1 _libc_write_cancel (fd=1, buf=0x44218000, nbytes=5)
at /usr/src/lib/libc/sys/w_write.c:26
#2 0x04470a3f in __swrite (cookie=0x243f8480 <__stdout>,
buf=0x44218000 "HELLO", n=5) at /usr/src/lib/libc/stdio/stdio.c:65
#3 0x044352c8 in __sflush (fp=0x243f8480 <__stdout>)
at /usr/src/lib/libc/stdio/fflush.c:77
#4 0x0443519a in _libc_fflush (fp=0x243f8480 <__stdout>)
at /usr/src/lib/libc/stdio/fflush.c:48
#5 0x182a06f0 in mrb_print (mrb=0x71037000, self=...)
at /tmp/mruby-3.3.0/mrbgems/mruby-print/src/print.c:57
#6 0x182556a3 in mrb_vm_exec (mrb=0x71037000, proc=<optimized out>,
pc=<optimized out>) at /tmp/mruby-3.3.0/src/vm.c:1902
#7 0x1824ee39 in mrb_vm_run (mrb=0x71037000, proc=0x6a8f3d30, self=...,
stack_keep=0) at /tmp/mruby-3.3.0/src/vm.c:1331
#8 0x1824e075 in mrb_top_run (mrb=0x71037000, proc=0x6a8f3d30, self=...,
stack_keep=0) at /tmp/mruby-3.3.0/src/vm.c:3121
#9 0x182672bd in mrb_load_exec (mrb=0x71037000, p=0x513a9010, c=0x6a5ae810)
at mrbgems/mruby-compiler/core/parse.y:6919
#10 0x182678b2 in mrb_load_nstring_cxt (len=13, mrb=<optimized out>,
s=<optimized out>, c=<optimized out>)
at mrbgems/mruby-compiler/core/parse.y:6991
#11 mrb_load_string_cxt (mrb=0x71037000, s=0x6a595800 "puts(\"HELLO\")",
c=0x6a5ae810) at mrbgems/mruby-compiler/core/parse.y:7003
#12 0x1821d5dc in main (argc=3, argv=0xcf7fb0d4)
at /tmp/mruby-3.3.0/mrbgems/mruby-bin-mruby/tools/mruby/mruby.c:360
mrbgems/mbuby-print/* って事は、全部gemで組み換え自由って事なのかな。 遺伝子編集ならモンサント社でキャスパー9か。いや、中山先生のiPS細胞による デザーナーベィビーだよ。Matz先生認定のcoreを核にして、ミニマリストのmruby を制作するのも楽しそう。
README
ロゴとネーミングの法律―事業を守る商標のしくみ なんて本を読んだ。
身近な例で解説してくれてるんで、法律の趣旨が良く解った(つもり)よ。 まあ、本当に解ってしまえば、弁理士なんていらなくなるんだけどね。 そんな士業はAIに依頼しちゃえばいいのでは? 著者の弁理士さんも、危機感を 持って、言及(コラムで)されてたぞ。
商標は基本、先願主義。似たようなのはダメよだ。但し分野が違えばOK。 消費者が混同する恐れが無いから。で、こういうのを検索できるようになってるそう。 https://www.j-platpat.inpit.go.jp/
何だググると一緒じゃんト高をくくっていると、数々の罠が有るそうな。やっぱり 弁理士に依頼しましょってのが正解みたい。