以下の内容はhttps://udzura.hatenablog.jp/より取得しました。


Uzumibi on CloudflareでDurable Object、Fetch、Queueをサポートした

タイトル通りです。

ぜ〜んぶ、Rubyから触れます!

続きを読む

Uzumibi の進捗...の話...

Uzumibi 0.5 をリリースした

リリースしたよ。

github.com

変えたところをいくつか。

router 、 * でマッチ可能にした

class App < Uzumibi::Router
  get '/hello' do |req, res|
    ...
  end

  get '/*' do |req, res|
    # catchallな処理を書く
    puts req.params[:*] #=> catchallしたslugが入ってる
  end
end

こうですね。ただ、そういえばSinatraは *.jpg とか *.* もできると気づいた。こっからっす!

ServiceWorker/WebWorkerを使って、ブラウザの中だけでRubyのルーティングにアクセスできるようにした

何言いよんねって感じの日本語になってしまった。

今、 uzumibi new -t serviceworker hoge みたいなコマンドを打つとServiceWorkerのプロジェクトが生成される。

make wasm && make serve するとWebサーバが立ち上がる。ブラウザでアクセスすると、Uzumibiアプリに対してリクエストを投げるためのフォームが表示されるので、色々操作するとリクエストが飛んでレスポンスが帰ってくる。404もわかる。

動作の様子

このUzumibiアプリが ServiceWorker のなかで動いているという塩梅。

techracho.bpsinc.jp

RailsでやっているこれをUzumibiでやってみたという感じで、 lib/app.rb にあるDSLでルーティングを追加して make wasm しなおせば反映される*1

ただ、ServiceWorker固有の制限(スーパーリロードすると動作しないとか)が面倒だなと思って*2、fetch() リクエストをただの WebWorker に送ってUzumibiからレスポンスを返すような実装も作った。それが webworker テンプレート。WebWorkerはただの非同期実行なので、ServiceWorkerでできるワーカの保持やfetch()の自動乗っ取りなどをしてくれないけど、「別途サーバを起動せずにRubyでAPIを用意する」みたいな用途はWebWorkerが合ってるかも。用途に応じて使い分けてください。

とはいえブラウザ標準技術は勉強したばかりで何もわからずなのです...。

今度、動的に Uzumibi App をServiceWorkerにデプロイするようなサンプルを作ってみたいところ。

newコマンドの受け入れテストをrunnでCIにした

uzumibi new コマンドからの開発の流れは、基本的に以下のようなことを想定している。

  • プロジェクトのテンプレートを作る
  • wasmをビルドする
  • サーバを立ち上げる
  • ブラウザなどでIt works! が出るのを確認する

uzumibi new で作られたファイルは何もしなくてもこれが動いて欲しいが、それなりの数サポートしているプラットフォームがあり、毎回手動で確認するのはアッ、ウッだった。

k1LoWさんが作っている runn は完全にこういうことの自動化にピッタリそうなので、AIの助けを借りながらCIに組み込んでみた。

github.com

始めは記法が難しかったがClaude Codeさんがググってくれたので徐々に理解できるようになった。

runn の良いところは、一つは上記の「サーバを立ち上げる」操作がバックグラウンド化できる(さらに、終了時の後始末も自動でやってくれる)点、もう一つは cdp ランナーがファーストクラスでサポートされている点だなと感じている。

例えば、 WebWorker テンプレートの場合こういう操作が想定されており、その性質上何かが表示されるだけではなく、「ブラウザでちゃんと動くか」が重要になる。

  • プロジェクトのテンプレートを作る
  • wasmをビルドする
  • サーバを立ち上げる
  • ブラウザでフォームから / へリクエストを送る
  • 結果が Status: 200 になるのを確認する

cdpを使えるため、ブラウザからfetchを送って結果を確認するという操作も自動化できる。まるでUzumibiのcliを検査するために作られたかのようなツールだなと感じている。

今後、例えば Cloudflare テンプレートでプロジェクトを作ったあと Durable Object を使ったコードを起動し、実際動くか試す...。みたいなテストも自動化できるのでは?と思っており、品質面で夢が広がる。

というかrunn、絶対仕事でも使おうと思った。肌感Rubyコミュニティの人にはあまり知られていなくて不思議。


という感じでUzumibiの方もじわじわ欲しい機能を作っているところ。

余談 of 余談: AI時代の個人OSS開発者の気持ち

*1:ServiceWorkerはこの辺むずくて、unregisterしないとダメか? 動的にキャッシュパス変えるとかしても良いかもな...

*2:ServiceWorker の用途を見ると、例えば事前に画像をfetchしてキャッシュして返すみたいなのを想定してそうで、スーパーリロードで動かないのはそういう関係っぽい?

続きを読む

mruby/edge 進捗 どうやって

進捗はない... 本質的な課題からも逃げがち... ではありますが、最近mruby/edgeでやってることを殴り書きします。

Playgroundをリリースした

mrubyedge.github.io

しました。正直まだそれなりに機能も足りず、バグもあるでしょうが、遊んでやってください。

1.1.0 で mruby/c と同じぐらいの数のメソッドを実装した(つもりな)ので、Rubyっぽいコードを試しやすくなってる気がします。気持ちの問題かもしれない。なおこの記事を書いてる時点では 1.1.5 が最新ということになっています。

github.com

mruby/edgeとしてはあとは math, json, regexp, random そして mruby-compiler2 と、 mruby-compiler2 の動作に必要なemscriptenの関数を含んでいます。

github.com

これだけ詰め込んでもまだ容量はギリ 1 MB を切っている。

$ ls -lh docs/*.wasm
-rwxr-xr-x  1 udzura  staff   922K  2月 15 15:15 docs/playground.wasm*

regexp がRustのregexベースで、でかいことがわかっているので若干工夫したいと思っています。このgem欲しい!とかあったら教えてください。

あとはPlaygroundなどを支える技術を...。

続きを読む

Uzumibi 0.3 のリリース

リリースした。往生際悪く(?)ブログを書く。


続きを読む

mruby/edgeで使うSipHashとFNVをかるっと比較

開発メモが流行ってるらしいので...、茶飲み話でも残すか...。

mruby/edge のHash

実装方針はこうしている:

  • 基本、標準のHashMapをそのまま使う
  • キーに関しては、オブジェクトごとに Hash トレートを実装してそれを使わせればいいじゃない...

ところでRustのHashMapはデフォルトでSipHashというものを使う。良い説明記事があるのでそちらにデリゲートしますね...。

zenn.dev

記事の通り、秘密鍵(ランダムネス)を用いるので推測されづらく、あと、アルゴリズムもシンプルで高速。

これでいいじゃん、なのだが、 FNV というハッシュアルゴリズムもあり、もっと小さいアプリケーションならこっちも使えないかと思って軽く試したというメモです。

FNV (実装は FNV-1a になってそう)はservoが作ってるやつがあって、小さいし、HashMapやHashSetの実装も添付していて、そのインタフェースは set(&K: Hash, V) とかで一緒になっているのでこれをそのまま使えばよし。

github.com

こんな感じでfeature flagで丸ごと別名を切り替えればそのまま使える(FnvHashMap::new() はないので、 FnvHashMap::default() を使う)

#[cfg(feature = "mruby-hash-fnv")]
pub type RHashMap<K, V> = fnv::FnvHashMap<K, V>;
#[cfg(feature = "mruby-hash-fnv")]
pub type RHashSet<K> = fnv::FnvHashSet<K>;
#[cfg(feature = "mruby-hash-fnv")]
pub type RHash = fnv::FnvHashMap<ValueHasher, (Rc<RObject>, Rc<RObject>)>;
#[cfg(not(feature = "mruby-hash-fnv"))]
pub type RHashMap<K, V> = std::collections::HashMap<K, V>;
#[cfg(not(feature = "mruby-hash-fnv"))]
pub type RHashSet<K> = std::collections::HashSet<K>;
#[cfg(not(feature = "mruby-hash-fnv"))]
pub type RHash = std::collections::HashMap<ValueHasher, (Rc<RObject>, Rc<RObject>)>;

ベンチ(単純にHashMap使う)

コードはpushしてある。ログは以下に抜粋:

default HashMap 10000 entries (1 char keys)
                        time:   [390.10 µs 390.54 µs 391.07 µs]

FNV HashMap 10000 entries (1 char keys)
                        time:   [338.16 µs 338.67 µs 339.22 µs]

default HashMap 10000 entries (5 char keys)
                        time:   [677.13 µs 677.60 µs 678.25 µs]

FNV HashMap 10000 entries (5 char keys)
                        time:   [550.01 µs 550.58 µs 551.34 µs]

default HashMap 10000 entries (10 char keys)
                        time:   [687.46 µs 688.55 µs 689.99 µs]

FNV HashMap 10000 entries (10 char keys)
                        time:   [604.11 µs 604.66 µs 605.48 µs]

default HashMap 10000 entries (20 char keys)
                        time:   [785.55 µs 790.05 µs 796.93 µs]

FNV HashMap 10000 entries (20 char keys)
                        time:   [806.96 µs 807.76 µs 808.73 µs]

default HashMap 10000 entries (50 char keys)
                        time:   [906.43 µs 911.02 µs 919.24 µs]

Benchmarking FNV HashMap 10000 entries (50 char keys): Warming up for 3.0000 s
FNV HashMap 10000 entries (50 char keys)
                        time:   [1.3509 ms 1.3520 ms 1.3536 ms]

Benchmarking default HashMap 10000 entries (100 char keys): Warming up for 3.0000 s
default HashMap 10000 entries (100 char keys)
                        time:   [1.1861 ms 1.2035 ms 1.2243 ms]

FNV HashMap 10000 entries (100 char keys)
                        time:   [2.4353 ms 2.4458 ms 2.4575 ms]

グラフです:

ベンチ(mruby/edgeの中で使った時

※ mruby/edgeの中で使っているHashMapは全部FNVに変えています

僕はこう思ったっす

  • キー長が10文字前後まではFNVの方が割と高速だったりする
  • 一方Hashするデータ数が大きいと劣化が目立つのはFNV
  • え、SipHash優秀じゃん...
  • しかし正直、普通に計測するとStringの生成とかの方がオーバヘッドになってたので(String使い回すように変えた)、あれ

結局、一応gem(feature flag)としては使い分けられるようにした。結構短いHashのキーしか使わないアプリケーションもあるかもしれない。

しかし、ランタイムも自分で作っていると、こういうふうにアルゴリズムで遊べるのはいい話か。

ちなみに安全性について

Rustのwasm32-unknown-unknownのような環境ではそもそもランダムネスを取得できないよなと思って軽くコードを見たら、以下の感じで:

github.com

システムコールが呼べないtargetではアドレスをソースに使うらしい。そうか〜という気持ちです。

また、キーのハッシュ関数にFNVを使う場合は、現実的にはsaltでも含めるといいのかなと思われる。そういうインタフェースを持たせたほうがいいかもではある。




以上の内容はhttps://udzura.hatenablog.jp/より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14