@ledsun blog

無味の味は佳境に入らざればすなわち知れず

V60珈琲王2 コーヒーメーカーを買った

www.hario.com

今まで使っていたV60珈琲王(前の型を使ってました)の調子が悪くなってきました。 抽出中に全ボタンが点灯して止まってしまう現象がおきます。 以前から時々起きていたのですが、頻度が上がってきた気がします。 ハリオ V60 珈琲王 - @ledsun blog をみるとちょうど10年使っているようです。 10年も使えば壊れるのも仕方ありません。 買い換えてもいいでしょう。

価格コムで見ると実売1万2千円でした。*1

これから10年使うと考えれば年1200円です。 しかもサイズが小さくなって、台所のスペースが広くなります。 これは買わない方が馬鹿では?と、脳の片隅でささやく奴がいます。 次に気がついたときには届いていました。

WSL 1.0.3.0でCannot open display ":0"が直りました。

Cannot open display ":0" - @ledsun blog で起きました。 Distrodを使うのをやめました - @ledsun blog で直りました。 11月16日に再発しました。

Cannot open display ":0:がでています。

このときのWSLのバージョンです。

PS C:\Users\led_l> wsl.exe --version
WSL バージョン: 1.0.0.0
カーネル バージョン: 5.15.74.2
WSLg バージョン: 1.0.47
MSRDC バージョン: 1.2.3575
Direct3D バージョン: 1.606.4
DXCore バージョン: 10.0.25131.1002-220531-1700.rs-onecore-base2-hyp
Windowsバージョン: 10.0.22621.819

今日Microsoft StoreでWSLのあたらしいバージョンが公開されました。

WSLがアップデートされました。

エラーが消えました。

バージョンを確認します。

PS C:\Users\led_l> wsl.exe --version
WSL バージョン: 1.0.3.0
カーネル バージョン: 5.15.79.1
WSLg バージョン: 1.0.47
MSRDC バージョン: 1.2.3575
Direct3D バージョン: 1.606.4
DXCore バージョン: 10.0.25131.1002-220531-1700.rs-onecore-base2-hyp
Windowsバージョン: 10.0.22621.819

WSL バージョン: 1.0.3.0 で直ったみたいです。

RubyVM.evalAsyncの中でRubyVM.evalAsyncを呼ぶとエラーが起きる

JS::Object.awaitが返らないときがある? - @ledsun blog にて次のような疑問を持ちました。

this.vm.evalAsync(script.ScriptBody)の中でthis.vm.evalAsync(script.ScriptBody)するのがよくないのでしょうか?

素朴にevalAsyncの中でevalAsyncを呼ぶとどうなるのか試してみます。

<html>
  <script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@0.5.0-2022-12-05-a/dist/browser.script.iife.js"></script>
  <script>
    function main() {
      window.rubyVM.evalAsync(`
        require 'js'
        JS.global[:rubyVM].evalAsync("puts 'a'")
      `)
    }

    // RubyVMの初期化を待ってから、mainを実行します。
    const id = setInterval(() => {
      if (window.rubyVM) {
        clearInterval(id)
        main()
      }
    }, 300)
  </script>
</html>

実行すると

見慣れないエラーが出ます。

vm_call_cfunc: cfp consistency errorRubyのエラーのようです。 Bug #11903: [BUG] vm_call_cfunc - cfp consistency error - Ruby master - Ruby Issue Tracking System に同じエラー文面に関するバグ報告があります。 報告内容は関係ないだろうと思って読んでいません。

Uncaught (in promise) RuntimeError: unreachableはAsyncifyに関わるエラーのようです。 [Asyncify] RuntimeError: unreachable · Issue #15488 · emscripten-core/emscripten · GitHub に同じエラー文面に関するバグ報告があります。

まあ、よくわかりません。

ちなみにどちらかのevalAsyncをevalに返ると動きます。

window.rubyVM.eval(`
  require 'js'
  JS.global[:rubyVM].evalAsync("puts 'a'")
`)

または

window.rubyVM.evalAsync(`
  require 'js'
  JS.global[:rubyVM].eval("puts 'a'")
`)

です。

JS::Object.awaitが返らないときがある? - @ledsun blog のときはこのエラーは起きていません。 evalAsync内でfetchして取得したRubyスクリプトをevalAsyncしてるはずなので、同じ現象がおきるはずだと思っていました。 何かが違うようです。・・・。 RubyJavaScriptを行ったり来たりしているところが、いまいち追いかけられなくて、頭の中でワープさせているんですが・・・正しく想像出来ていなさそうです。

evalAsyncの中はFiberで実装されている*1のですが・・・また、これがコードがどこにどう飛ぶのがかわかりません。

追記

Bug with passing Procs around: "RuntimeError: unreachable" · Issue #34 · ruby/ruby.wasm · GitHub を眺めていたらもう少し短い再現コードが書けることに気がつきました。

<script type="module">
  import { DefaultRubyVM } from 'https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@0.5.0-2022-12-05-a/dist/browser.esm.js'

  const main = async () => {
    // Fetch and instantiate WebAssembly binary
    const response = await fetch(
      "https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@0.5.0-2022-12-05-a/dist/ruby+stdlib.wasm"
    );
    const buffer = await response.arrayBuffer();
    const module = await WebAssembly.compile(buffer);
    const { vm } = await DefaultRubyVM(module);
    window.rubyVM = vm;

    vm.evalAsync(`JS.global[:rubyVM].evalAsync("puts 'a'")`)
  };
  main();
</script>

JS::Object.awaitが返らないときがある?

JS::Object.await ふたたび - @ledsun blog で、JS::Object.awaitの使い方がわかりました。 そこでfetchを使ってrequire_relativeを実装します。

import { RubyScript } from "./RubyScript";
import { EvaluatedScriptStack } from "./EvaluatedScriptStack";

class RubyWasm {
  private _loadedPathes: Set<string> = new Set();
  private _stack: EvaluatedScriptStack;

  constructor(stack: EvaluatedScriptStack) {
    this._stack = stack;
  }

  async requireRelativeURL(relative_feature): Promise<boolean> {
    const filename = relative_feature.endsWith(".rb")
      ? relative_feature
      : `${relative_feature}.rb`;
    const url = new URL(filename, this._stack.currentURL);

    // Prevents multiple loading.
    if (this._loadedPathes.has(url.pathname)) {
      return false;
    }

    const response = await fetch(url);
    if (!response.ok) {
      return false;
    }

    const text = await response.text();
    await this._stack.eval(new RubyScript(url, text));

    return true;
  }
}

export function defineRubyWasm(stack: EvaluatedScriptStack): void {
  const loadedPathes: Set<string> = new Set();

  const global = window as any;
  global.rubyWasm = new RubyWasm(stack);
}

fetchして結果を待ってawait this._stack.eval(new RubyScript(url, text))します。 stackは次のようなクラスです。

import { RubyScript } from "./RubyScript";

// To achieve require_relative, we need to resolve relative paths in Ruby scripts.
// Remember the URL of the running Ruby script.
export class EvaluatedScriptStack {
  private _vm;
  // Stores the URL of the running Ruby script to get the relative path from within the Ruby script.
  private _stack: Array<RubyScript>;

  constructor(vm) {
    this._vm = vm;
    this._stack = [];
  }

  async eval(script: RubyScript): Promise<void> {
    this._stack.push(script);
    await this._vm.evalAsync(script.ScriptBody);
    this._stack.pop();
  }

  get currentURL(): URL {
    return this._stack.at(-1).URL;
  }
}

実行中のRubyスクリプトのURLを保持しつつawait this._vm.evalAsync(script.ScriptBody)します。 そして、requireRelativeURLrequrie_relativeを置換したrequire_relative_urlメソッドで呼び出します。

import { RubyVM } from "..";

export function define_require_relative_url(vm: RubyVM) {
  const script = `
  require "js"
  module Kernel
    def require_relative_url(relative_feature)
      JS.global[:rubyWasm].requireRelativeURL(relative_feature).await
    end
  end
  `;
  vm.eval(script);
}

これを次のようなHTMLを実行します。

<html>
  <script src="../../ruby-head-wasm-wasi/dist/browser.script.iife.js"></script>
  <script type="text/ruby">
    require_relative "a"
    puts "Hello, world!"
  </script>
</html>

aの読込に成功します。

a.rbを読み込んだあとにHello, world!が表示されます。 RubyJavaScriptのPromiseを待てています。 つぎに読み込むファイルを増やします。

<html>
  <script src="../../ruby-head-wasm-wasi/dist/browser.script.iife.js"></script>
  <script type="text/ruby">
    require_relative "a"
    require_relative "b"
    puts "Hello, world!"
  </script>
</html>

aとbの読込に成功します。

つぎに、a.rbを経由してb.rbを読み込んでみます。

require_relative "b"

p "a loaded!"

bの読込に成功します。が、そこで止まります。

a.rbを読み込んだあとにHello, world!が表示されてほしいです。 this._vm.evalAsync(script.ScriptBody)の中でthis._vm.evalAsync(script.ScriptBody)するのがよくないのでしょうか?

ruby.wasmクイズ

次のスクリプトはエラーになるでしょうか?

<html>
  <script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@0.5.0/dist/browser.script.iife.js"></script>
  <script type="text/ruby">
    require "js"
    JS::global.send(:require, 'erb')

    erb = ERB.new("Hello <%= val %>!")
    puts erb.result_with_hash val: "world"
  </script>
</html>

ヒント。次のスクリプトはエラーになりません。

<html>
  <script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@0.5.0/dist/browser.script.iife.js"></script>
  <script type="text/ruby">
    require "erb"

    erb = ERB.new("Hello <%= val %>!")
    puts erb.result_with_hash val: "world"
  </script>
</html>

ヒント2。JS::globalRubyのオブジェクトです。 つまりKernelを継承しています。

ヒント3

irb(main):002:0> 1.send(:require, 'erb')
=> true

つぎのようなコードを書いたら無限ループしました。というお話でした。

  1. JavaScriptwindow.js_require_relativeって関数を定義
  2. RubyKernel#js_require_relativeって関数を定義
  3. 後者からJS.global.js_require_relativeを呼んだ
<html>
  <script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@0.5.0/dist/browser.script.iife.js"></script>
  <script>
    window.js_require_relative = function() {
      console.log("called")
    }
  </script>
  <script type="text/ruby">
    require "js"

    module Kernel
     def js_require_relative
       JS::global.js_require_relative()
     end
    end

    js_require_relative
  </script>
</html>

を実行すると

SystemStackErrorが発生します。

JS:globalJavaScript側のメソッド検索はmethod_missingで行っています。

https://github.com/ruby/ruby.wasm/blob/802ecc5047ac9ee7270b58e7f11cea87b8172f36/ext/js/lib/js.rb#L84-L90

  def method_missing(sym, *args, &block)
    if self[sym].typeof == "function"
      self.call(sym, *args, &block)
    else
      super
    end
  end

Kernel#js_require_relativeが定義されているため、method_missingが呼び出されていないのだと思います。 次のようにKernel#js_require_relativeをprivateメソッドにします。

<html>
  <script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@0.5.0/dist/browser.script.iife.js"></script>
  <script>
    window.js_require_relative = function() {
      console.log("called")
    }
  </script>
  <script type="text/ruby">
    require "js"

    module Kernel
     private def js_require_relative
       JS::global.js_require_relative()
     end
    end

    js_require_relative
  </script>
</html>

するとJS::global.js_require_relative()NoMethodErrorが発生し、無事にwindow.js_require_relativeが呼び出されます。

そもそも

  • 同名してうれしいのか?
  • windowオブジェクトに直接関数を増やしたらダメだろ

など、前提条件に疑問はあります。

Microsoft People はアンインストールできない

Microsoft Storeで「更新プログラムを取得する」と高頻度でMicrosoft Peopleというアプリが出てきます。 意識的に使った事が無いアプリをたびたび更新するのは癪に障ります。 アンインストールできないでしょうか?

How to Uninstall Microsoft People in Windows 10 - MajorGeeks によると

Open PowerShell and type in:

Get-AppxPackage people | Remove-AppxPackage

続いてしたの方を読むと

this solution no longer works in the newest Windows update (update 2004)

とあります。 試してみます。

PS C:\Users\led_l> Get-AppxPackage *people* | Remove-AppxPackage
Remove-AppxPackage: 次の HRESULT で展開に失敗しました: 0x80073CFA, 削除に失敗しました。ソフトウェアの製造元に問い合わせてください。 (HRESULT からの例外:0x80073CFA)
エラー 0x80070032: C:\Windows\SystemApps\Microsoft.Windows.PeopleExperienceHost_cw5n1h2txyewy からのパッケージ Microsoft.Windows.PeopleExperienceHost_10.0.22621.1_neutral_neutral_cw5n1h2txyewy に対する AppX 展開 Remove 操作に失敗しました。このアプリは Windows の一部であるため、ユーザー単位でアンインストールすることはできません。管理者は、[Windows の機能の有効化または無効化] を使用して、コンピューターからのアプリの削除を試み
ることができます。ただし、アプリをアンインストールできない場合もあります。
                                                                                                                                   ]
注: 詳細については、イベント ログで [ActivityId] 464b2004-05e1-0000-6f42-e546e105d901 を検索するか、コマンド ラインの Get-AppPackageLog-ActivityId 464b2004-05e1-0000-6f42-e546e105d901 を使用してください

やはり出来なさそうです。

「デバッガーでの例外設定」とfinallyブロック内のブレークポイント

C#をデバッグ実行するとfinallyブロックに入らない - @ledsun blogを書いたら、つぎのような情報をもらいました。

ためしてみます。

デバッガーでの例外の管理 - Visual Studio (Windows) | Microsoft Learn で設定する値だと思います。

[デバッグ] > [Windows] > [例外設定]

から設定出来るようです。

[デバッグ] > [ウインドウ] > [例外設定]

全部のオプションを外してデバッグ実行します。

今回の現象と「デバッガーでの例外設定」は関係無いようです。

ついでに面白い、.NET Frameworkと.netの違いを見つけました。 デバッグ無し実行をしたときに終了コードが表示されるかどうかが異なります。

.NET Frameworkでは終了コードが表示されません

.netでは終了コードが表示されます。

codeblocks - C++ status -1073741676 - Stack Overflow によると、表示されているコードは EXCEPTION_INT_DIVIDE_BY_ZEROだそうです。 つまり、発生した例外DivedeByZeroExceptionに対応した値のようです。

RubyVMインストラクション1日目

25日間で理解するRubyVMインストラクション - @ledsun blog の1日目が公開されました。

RubyVMはスタックベース仮想マシンです。 スタックに入る値はRubyのオブジェクトがそのまま入る点が特徴的なようです。

例えばduparrayという命令では配列[1, 2, 3]がそのままスタックに入ります。

ledsun@MSI:/m/c/U/led_l►ruby --dump=insns -e '[1, 2, 3]'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,9)> (catch: false)
0000 duparray                               [1, 2, 3]                 (   1)[Li]
0002 leave

なるほどー。 なんとなくアセンブラみたいに変換されたものとか、ポインタとかが入るのかな?って想像していました。 いやまあ、参照と言う意味でポインタはポインタなのでしょうが、Rubyで扱っているオブジェクトと別の何かなのかなあ?って思っていました。

あと、面白かったのはputstringのすぶりで次のようにやりました。

ledsun@MSI:/m/c/U/led_l►ruby --dump=insns -e 'abc'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,3)> (catch: false)
0000 putself                                                          (   1)[Li]
0001 opt_send_without_block                 <calldata!mid:abc, argc:0, FCALL|VCALL|ARGS_SIMPLE>
0003 leave

pushstringではなくてputselfという違うインストラクションが表示されました。 不思議です。 記事のサンプルコードをコピペで実行してみました。

ledsun@MSI:/m/c/U/led_l►ruby --dump=insns -e '"foo"'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,5)> (catch: false)
0000 putstring                              "foo"                     (   1)[Li]
0002 leave

pushstringがでるんですよー。 不思議ですね。

両者を見比べてみて気がつきました。 そりゃ、そうです。

JS::Object.await ふたたび

JS::Object.await - @ledsun blog に対してフォローをもらいました。

なんかちょっと上手く使えていないようです。 https://github.com/ruby/ruby.wasm/blob/2ef290bca9df55a9c7c64e9c03b8d4dec54f7b44/ext/js/lib/js.rb#L97-L121 のコメントを参考に、小さいコードで試してみます。

JavaScript

まずはJavaScriptにて

<html>
<script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@0.5.0/dist/browser.script.iife.js"></script>
<script>
  function slowFunction() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(123)
      }, 1000)
    })
  }

  async function main() {
    const response = window.rubyVM.evalAsync(`
      puts "step 1"
      JS.global.slowFunction().await
      puts "step 3"
    `)
    console.log("step 2")
    await response
    console.log("step 4")
  }

  const id = setInterval(() => {
    if (window.rubyVM) {
      clearInterval(id)
      main()
    }
  }, 300)
</script>

</html>

実行するとちゃんと、1, 2, 3, 4の順で動きます。 とくに4が最後に動くのが印象的です。 await responseをすると、Promiseを待っているRubyスクリプトの完了を待てるようです。

Ruby

<html>
<script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@0.5.0/dist/browser.script.iife.js"></script>
<script type="text/ruby">
  require 'js'

  async_func = <<-"JS"
  return new Promise((ok) => {
    setTimeout(() => {
      console.log('step 3')
      ok(42)
    }, 1)
  })
  JS

  puts 'step 1'
  promise = JS.eval(async_func)
  puts 'step 2'
  promise.await
  puts 'step 4'

</script>

</html>

これは 1, 2, 4, 3の順で動きます。 Promiseの終了を待っていません。 現在の<script type="text/ruby">は自動的にrubyVM.evalRubyスクリプトを実行します。 もしかしてRubyスクリプトrubyVM.evalAsyncで実行しないといけないのでしょうか?

<html>
<script src="https://cdn.jsdelivr.net/npm/ruby-head-wasm-wasi@0.5.0/dist/browser.script.iife.js"></script>
<script>
  function slowFunction() {
    return new Promise((ok) => {
      setTimeout(() => {
        console.log('step 3')
        ok(42)
      }, 1)
    })
  }

  async function main() {
    const response = window.rubyVM.evalAsync(`
      puts "step 1"
      puts 'step 2'
      JS.global.slowFunction().await
      puts "step 4"
    `)
  }

  const id = setInterval(() => {
    if (window.rubyVM) {
      clearInterval(id)
      main()
    }
  }, 300)
</script>

</html>

これは 1, 2, 3, 4の順で動きます。 なるほどー。 そらそうですね。 最初の呼び出しを非同期関数にしなかったら、非同期関数を待てるわけないですね。 PromiseやAsync/Awaitはコールバックヘルを縦に並べ直したものです。 最初の呼び出しでコールバック関数が渡せなければ、待ちようがありません。

なるほど、そういうつもりで書き直さないといけないのですね。

C#をデバッグ実行するとfinallyブロックに入らない

こういうC#ソースコードがあります。

using System;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var hoge = 0;
                var fuga = 1;
                var piyo = fuga / hoge;
            }
            finally
            {
                Console.WriteLine("finally");
            }
        }
    }
}

デバッグ無しで実行します。

デバッグ無しで実行

例外がおきたあと、 finnallyブロックの処理が実行されて文字列finnallyが出力されます。

finallyブロックにブレークポイントをおいてデバッグ実行します。

tryブロックで例外が起きます。 ブレークポイントには到達しません。

デバッグかどうかで挙動が変わります。 デバッガが例外を捕まえるためになにかしているのでしょうか?

Vladimir MakarovさんのRubyKaigi 2022での発表を記事にしたもの?

developers.redhat.com

ruby-jp slackで教えてもらいました。 A Faster CRuby interpreter with dynamically specialized IR - RubyKaigi 2022 の内容を記事にしたものらしいです。 この発表をみた僕の感想は A Faster CRuby interpreter with dynamically specialized IR - @ledsun blog によると

圧倒的にわかりませんでした。

今度は記事なので、DeepLで翻訳したうえで自分のペースでゆっくり読めます。

25日間で理解するRubyVMインストラクション

kddnewton.com

ruby-jp slackで教えてもらいました。 Syntax Tree - RubyKaigi 2022 を発表したケビン・ニュートンさんが25日間でRubyVMについて解説してくれるようです。 プロローグの本記事によるとRubyVMのインストラクションを表示する方法が挙げられています。

例えば、2 +3の命令をダンプするには、ruby --dump=insns -e '2 + 3' と実行すればよいでしょう。

ledsun@MSI:~►ruby --dump=insns -e '2+3'                                    12:14
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,3)> (catch: false)
0000 putobject                              2                         (   1)[Li]
0002 putobject                              3
0004 opt_plus                               <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
0006 leave

第二の方法は、RubyVM::InstructionSequenceクラスを用いて、ソースから YARV バイトコードコンパイルする方法である。

irb(main):005:0> puts RubyVM::InstructionSequence.compile('2+3').disasm
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,3)> (catch: false)
0000 putobject                              2                         (   1)[Li]
0002 putobject                              3
0004 opt_plus                               <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
0006 leave
=> nil
irb(main):007:0> pp RubyVM::InstructionSequence.compile('2+3').to_a
["YARVInstructionSequence/SimpleDataFormat",
 3,
 2,
 1,
 {:arg_size=>0,
  :local_size=>0,
  :stack_max=>2,
  :node_id=>4,
  :code_location=>[1, 0, 1, 3],
  :node_ids=>[0, 1, 3, -1]},
 "<compiled>",
 "<compiled>",
 "<compiled>",
 1,
 :top,
 [],
 {},
 [],
 [1,
  :RUBY_EVENT_LINE,
  [:putobject, 2],
  [:putobject, 3],
  [:opt_plus, {:mid=>:+, :flag=>16, :orig_argc=>1}],
  [:leave]]]

この配列の各要素が何を意味するかは、シリーズで解説していきます。

RubyKaigiの発表では当たり前のように「インストラクション」という言葉が出てきていました。 例えば Stories from developing YJIT - RubyKaigi 2022 では、「初期のYJITではインストラクションを1対1で機械語に翻訳した」という話がでてきました。 このシリーズを読むとRubyKaigiの発表がよりスムーズに理解できるようになりそうです。 楽しみです。

ふと「20年前に、こういうインストラクションを実行するバーチャルマシンを作るとRubyが動くって考えて実装した、笹田さんの脳みその中身どうなっているんだろう?」と不思議になりました。

コア数を増やすと並列数は伸びるか?

8並列で3倍速 - @ledsun blog で、8並列で動くことがわかりました。 コア数を変えて計測してみました。 つかったEC2インスタンスは次の通りです。

インスタンスタイプ vCPU数 コア数
m6i.2xlarge 8 4
m6i.4xlarge 16 8
m6i.8xlarge 32 16

計測結果

4コアだと4並列で止まります。 8コアだと8並列で止まります。 ここまでは予想通りです。 16コアあっても8並列で頭打ちになります。 ということはm6i.32xlargeを使って64並列!とかは意味がなさそうです。

8並列で頭打ちになるのは、なぜでしょうか?

ちなみに16コアの8並列までは、雑に次のような感じになるようです。

  • 2並列で2倍
  • 4並列で3倍
  • 8並列で4倍

8コアで8並列するよりは速いです。 ここは期待通りです。

8並列で3倍速

EC2のvCPU数はハイパースレッディングを含む - @ledsun blog にて計測に使っているEC2インスタンスタイプが不適切であることがわかりました。 今度はm6i.4xlargeを使って計測します。 m6i.4xlargeは16vCPUあります。 コア数は8のはずです。

m6i.4xlargeの計測結果

4並列より8並列の方が高速なことがわかります。 比較のためにローカルPCでの計測結果を載せます。

Core i7-1185G7 での計測結果

8並列は4並列より高速ではありません。 むしろ悪化しています。

4コアで2.3倍、8コアで3.1倍です。 もうちょっとほしいっちゃ欲しいですが、こんなもんでしょうか? 16コア使ったら4倍になるのでしょうか?

ruby.wasmに対する思いとか、企んでいることとか

ruby.wasmについて、現時点で僕が思っていることを記録しておきます。

思っていること

主にonブラウザ

僕がruby.wasmに興味があるのは、ブラウザで動かす方です。 エッジワーカーで動かす方は今のところあまり興味がありません。

また、ruby.wasmがプロダクションで使えることは期待していません。 つまり「Reactをひっぺがして、ruby.wasm + Railsで大統一Ruby Webアプリケーション開発だ!」みたいな線はあんまり考えていません。 というか次の2点で大統一理論に勝ち目はないと思っています。

  1. 言語を統一したところで、ブラウザとサーバーはプログラミングモデルが違いすぎる
  2. JavaScriptエコシステムの規模はRubyエコシステムの規模を圧倒している

わざわざJavaScriptの資産を捨てて、イベントプログラミングに向いているわけでもないRubyを選択するメリットはないですよね。

FlashRubyが置き換えること

一方で、ブラウザでRubyが動くのは楽しいです。 wordle-searchという小さなアプリケーションを作った経験上、謎の楽しさがあります。 アプリケーションでシンプルで小さいから楽しいという面もあるのかもしれません。 Rubyという言語が楽しいのかもしれません。 その辺は未知です。

とはいえ、ブラウザでRubyが動くと、Rubyのプログラミング入門言語としての価値が上がるんじゃないかと思います。 よく「初心者が最初に学ぶとよいプログラミング言語は?」という問いに対して、「Rubyが比較的お約束が少なくプログラムを書ける」ので向いているという回答や、「JavaScriptはブラウザで実行できるので、短期間でGUIをあつかったプログラムが書ける」ので向いているという回答を見受けます。 これはつまりRubyがブラウザで動けば、入門プログラミング言語No.1になります。 まあ、実際はPythonなんかもwasmで動かせば良いだけなので、同じように競争相手になってくると思います。またruby.wasmを使うためのお約束も増えると思います。

勝算はともかく、Rubyが「Flashのような入門的なプログラミング環境」の位置を目指せるということに夢を感じます。Flashはアニメーションが作りやすかった点が大きいし、コンパイル言語だし、ruby.wasmとあんまり対応しないだろうという気はします。 まあまあ、それは置いておきます。 大事なのは夢やロマンを僕が感じるかどうかです。

企んでいること

ruby.wasmがFlashになるためには、Gemの資産が生かせないのは難しいんじゃないかと思います。 少なくともRubyGems.orgでGemを公開したり、公開されているGemをつかうエコシステムがないと厳しそうです。 この辺はnpmを意識しています。 プログラミング初心者が、ブラウザネイティブで動くJavaScriptを選択せずにRubyを選択するときに、公開したり再利用したりするエコシステムがないのは厳しいと思います。

require_relative

まずはreqire_relativeをruby.wasm上で動かそうと思っています。 本命はrequireなのですが、これは難しいので最初にreqiure_relativeを対象にします。 作戦としてはファイルシステムをURLにそのまま置き換えます。 require_relativeはファイルシステム上の相対パスで読み込むRubyスクリプトを指定します。 これをURL上の相対パスで置き換えるのは実現できそうです。

require

つぎにrequireをruby.wasm上で動かそうと思います。 問題はrequireで指定する名前とファイルシステム上の位置が対応していないことです。 この問題はすでにESモジュールがぶつかった問題です。 そしてimportmapsを使うという1つの回答がでています。 僕もこれにならってimportmapsを使うつもりです。 サーバーでは例えばvende/bundleディレクトリにGemをインストールします。 このパスをimportmapsに記述すれば、requireで指定する名前と実際のファイルのURLを解決できるはずです。

これが実現できればimportmap-railsと連携して、Railsアプリケーションでruby.wasmを動かす敷居がさげれるんじゃなかろうかと期待しています。 前述のように、これはプロダクションで使えるものではないと思います。 が、「importmap-railsruby.wasmが動く」ってメッセージはインパクトが強そうです。

UNPKG

サーバーに配置したGemをrequire出来れば、RubyGem.orgのGemを一定のルールで配置した静的なHTTPサーバーがあれば、ruby.wasmアプリケーションの開発者は、サーバーを用意しなくてもGemが使えるようになるはずです。 npmの世界でUNPKGとして実現されているものです。 ここまでくるとどうやって実現するのかは検討がついていません。

ですが、これが実現できるとruby.wasmアプリケーションの開発者は作ったアプリケーションを静的なホームページで公開できるようになります。 例えばGitHub Pageをつくればそれだけでアプリケーションが公開できます。 Herokuのような、PaaSを使うハードルを取り除くことが出来ます。 これはちょっとFlashっぽいんじゃないかな?って ワクワクしています。

打算

Rubyコミュニティにはブラウザからのモジュールローディングに興味を持っている人がすくないようです。 DHHがimportmap-railsを打ち出したときの、反応の低さから僕は思いました。 本当にそうであれば、僕がいまからちまちま作っていっても、Rubyコミュニティに一定以上のインパクトを与えられるんじゃないかな?と予想しています。 で、RubyKaigi 2023のCFPに応募したら、通るんじゃなかろうか?と期待しています。

RubyKaigi 2023までに出来るのはせいぜいrequireまでだろうと思っています。 requrie_relativeですら、想定外に苦戦して2ヶ月掛かっています。 requireが作れれば、UNPKGの要件が詰められるようになるはずです。 本当にいけそうならRuby Association Grantに応募するのが良さそうだと思っています。 Ruby Association Grantは所属企業で応募できるので「通れば業務時間で開発できるじゃん。」という打算です。