kanejaku.org

RustのSend

25 Dec 2018

疑問: RustのライブラリAPIの定義を見ていたら制約にT: Sendと書いてあった。なぜこのAPIはSendを要求するのか。

短い答え: そのAPIはスレッドをまたいで値を渡す。その値の所有権をスレッド越しに渡してもデータ競合を起こさない、ということを要求するため。Sendでない型はたいていRc<T>かポインタ。

これはAPIドキュメントを見て疑問の思ったことを調べて、使う側からどう解釈すべきか、という視点から理解しようとするシリーズの二番目の記事となる。前回に引き続き、お題はthread::spawn。自分の浅い理解から書いているので間違いを含んでいるかもしれない。

thread::spawn()に渡すクロージャの制約はFnOnce() + Send +'staticなので前回の記事で言及したFnOnce()に加えてSendを実装していないといけない。APIドキュメントには値を安全に別スレッドに送るためにSendが必要だ、と書いてある。でもRustには所有権の概念があるから値を渡せばそれで安全になるんじゃないのか。逆に言うとスレッド越しに渡すと安全じゃない値ってなんなんだろうか。調べた感じだと、大体二つに分類される。

ひとつはRc<T>Rc<T>な値は参照カウンタとともにヒープ上に確保される。レイアウトはこんな感じ:

+--------------+------------+-------+
|strong ref cnt|weak ref cnt| value |
+--------------+------------+-------+

Rc::clone()を呼ぶと値を複製できる。つまり所有権を持つ変数を複数作れる。Rc::clone()はヒープ上の参照カウントを増やすのだけど、この参照カウンタはアトミック変数じゃないし、排他制御も行わない。これらの値を異なるスレッドで生成したりドロップしたりしたら参照カウンタのデータ競合が起こる。なのでRc<T>Sendではない。

一方、Rc<T>と同じように内部可変性(interior mutability)を使っているCell<T>RefCell<T>は、TSendであればCell/RefCellもSendになる。Cell/RefCellがスレッド安全でなくなるのは、Cell/RefCellへの参照をスレッド間で渡したときであって、所有権を渡すこと自体は安全な操作となる。

もうひとつのSendでない型はポインタ。*const u8とか*mut u8とか。これらがSendではない理由はRc<T>ほど自明ではない。ポインタをスレッド間で渡すこと自体はデータ競合を起こさない。安全でなくなるのはポインタをdereferenceするときだけど、そのときはどのみちunsafeでくくらなければいけない。だったらポインタをSendである、としても安全性は損なわないような…などと思いつつ調べていたらNomiconに言及があった。主に書き手に注意喚起する目的らしい。言語設計上の選択だったんだろう。

ちなみに参照&TのほうはSendを実装している。理由はコンパイラが借用ルールを使って安全でない参照の利用を検知できるから。

自分で定義した型はどうか。型がSendであるかどうかは基本的にコンパイラがよしなに判断してくれる。内包する型が全部Sendを実装していれば、その型もSendになる。内包する型のうち一つでもSendでない型があれば、定義した型もSendを満たすことができない。

コンパイラにお任せできないこともある。メソッドをスレッド安全でない方法で実装したときなどがこれに該当する。この場合はimpl !Send for Tみたいにして明示的にSendを実装しない、と表明する必要がある。

thread::spawn()に話を戻す。thread::spawn()はクロージャを受け取る。API定義はそのクロージャがSendを満たしていないといけない、と言っている。ではクロージャがSendかどうかはどう決まるのか。それはクロージャの作り方によって決まる。クロージャがSendでない値をキャプチャしていたら、そのクロージャはSendではない。ただし、Sendではない値をクロージャの内部で使っていても、クロージャ自体はSendになることができる。

// Error
fn f1() {
    let v = Rc::new(42);
    thread::spawn(move || {
        let _v2 = Rc::clone(&v);
    });
}

// OK
fn f2() {
    thread::spawn(|| {
        let _v = Rc::new(42);
    })
}

API定義でSendを見かけたときの心構えとしてはどう考えればいいか。ほとんどの型がSendであることを考えれば、スレッド越しに値を渡すよということぐらいで特に気にしなくても良さそう、というのが今の理解。

余談だけど、スレッド安全に関するもう一つのトレイトとしてSyncがある。こちらはthread::spawn()と直接関係がなかったのであまり追っておらず、理解があやふやなので言及を避けた。

Rust の Fn, FnMut, FnOnce の使い分け

8 Dec 2018

Rustでスレッドを起動するにはthread::spawn()を使う。この関数は引数にクロージャをとるのだけど、引数の定義を見ると、 F: FnOnce() -> T + Send + 'static となっていて初見では理解するのが難しい。ここではFnOnce()、つまりクロージャの型について、どういう風に使い分けるのか、といった視点で見ていこうと思う。

Rustにはクロージャを表す型が3つある。

  • Fn()
  • FnMut()
  • FnOnce()

型が3つ必要なのはRustが値の所有権や借用、生存期間をきちんと把握するためである。

Fn()は使う側、引数として受け取る側としては使い勝手が一番よい。何度でも呼び出せるし、クロージャ自体が可変じゃなくてもいい。これは立場を変えると、クロージャを渡す側としてはFn()は制約が一番厳しいことを意味する。Fn()として渡されるクロージャは、何度実行されても所有権や借用のルールを破らないようにしなければならない。例えば、値の所有権をとってそれを消費するクロージャはFn()としては渡せない。一度値を消費してしまったら二度は使えないから。以下のコードはコンパイルエラーで通らない。

fn do_something<F: Fn()>(f: F) {
  f();
  f();
}

fn main() {
  let nums = vec![1,2,3];
  do_something(move || {
    for n in nums { ... } // Consume `nums`
  });
}

FnOnce()の使い勝手はFn()の逆と言える。使う側としての制約は一番厳しい。一度しか実行できないし、実行にはそのクロージャの(借用ではなく)所有権が必要となる。一方、渡すクロージャの自由度は大きい。クロージャは一度しか呼ばれないことが保証されているので、そのクロージャ内で値を消費してもいいし、生存期間さえ守っていれば可変参照を取って値を変更することもできる。

fn do_something<F: FnOnce()>(f: F) {
    f();
    // Can't use `f` more than once
}

fn main() {
    let nums = vec![0,1,2];
    let mut greeting = "Hello".to_string();
    let greeting_ref = &mut greeting;
    do_something(move || {
        for n in nums { ... } // Consume `nums`
        greeting_ref.push_str(", World"); // Mutate value via mutable ref
    });
    println!("{}", greeting);
}

この流れで行くとFnMut()Fn()FnOnce()の中間的な立ち位置になる。値の消費はしないけれど可変参照経由で変更はする、みたいなクロージャはFnMut()として取り扱うことができる。FnMut()は何度も呼べるけれど、内部に可変参照をもっているのでそのクロージャを実行するにはmutを必要とする。

fn do_something<F: FnMut()>(mut f: F) {
    f();
    f();
}

fn main() {
    let mut greeting = "Hello".to_string();
    let greeting_ref = &mut greeting;
    do_something(move || {
        greeting_ref.push_str(", World");
    });
    println!("{}", greeting);
}

ここでthread::spawn()に立ち戻ってみよう。ライブラリ関数としては利用方法にできるだけ制限をかけたくない。ライブラリ利用者側、つまりクロージャを渡す側にとって一番制約がゆるいのはFnOnce()である。おそらくこれがspawn()FnOnce()なクロージャを受け付けるようになっている理由だろう。spawn()は渡されたクロージャを一度実行すればよいだけなので、利用者側の制約が厳しいFn()を要求する必要はない。

自分がAPIを提供する立場だとしたら、引数として受け取るクロージャの型には注意しておきたい。ライブラリの中身を実装している最中に、コンパイラを通すためだけに安易にFn()を要求するAPIを作ってしまうと、あとで利用する側で困る、みたいなことになりかねない。

ただ、thread::spawn()と似たようなAPIを自作して提供する場合にはひとつ落とし穴がある。公式ドキュメントで言及されているので、自分で作ってみようとする前に目を通しておくとよい。自分はこれに悩んで時間を浪費してしまった。

追記

Sendについて記事を書いた。

参考文献

Secure Shell App でウェブフォントを使う

2 Dec 2018

Windows では ssh クライアントに Secure Shell App を使っているのだが、デフォルトの設定だと自分の4Kディスプレイではフォントの見栄えがよくない。ウェブフォントを使って自分の好みに変更してみる。

Secure Shell App のオプションページを開くと、user-css-textという項目があって、ここに任意の CSS を書くことができる。ここで Google Fonts で提供されているフォントを@importを使って取り込むようにする。今回は Roboto Mono を使うように設定した。

@import url("https://fonts.googleapis.com/css?family=Roboto+Mono:400");

さらに Secure Shell App の設定項目のfont-familyも更新しておく。

"Roboto Mono", Consolas, monospace

少し前に日本語の Noto Sans が Google Fonts で使えるようになったのでこちらも試してみたが、なんだか jaggedly であった。日本語に関してはメイリオの方ががよさそう。

Chromeのメモリ割り当てフック機構

29 Sep 2018

Misreading Chat ep. 30はAddressSanitizer (ASAN)の話。operator newの差し替えとかどうしているんだろうね、という話題に反応してみる。

Chromeのコードにもoperator newを差し替えている場所はいくつかある。例えばUSING_FAST_MALLOCというマクロでアノテートされたクラスはPartitionAllocという独自アロケータを使ってoperator newを実装している。このPartitionAlloc、レンダリングエンジンの内部では結構使っていて文字列やベクタ、ArrayBufferなんかにも使われている。PartitionAllocは内部でmmap使っているんだけど、ASANがこの辺をチェックしてくれるのかどうか自分はよくわかっていない。

mallocとかを差し替えたい動機としては、PartitionAllocみたいな最適化したメモリアロケータを使いたい、っていうのの他に、メモリの割り当てと解放をフックしたい、というのがある。これらをフックすればどういう風にメモリが使われているのかを調べることができる。例えば、よく作られるオブジェクト(Stringとか)の一部がすごく大きいのだけど、どこで作られているのか実行時でないと分からないとする。この場合はそのオブジェクトのnewをフックしてスタックトレースを調べれば、どういうパスを通ってそのオブジェクトが作られたのかが分かる。

Chromeにはこのフックを入れる汎用的な仕組みがあって、Allocator shimと呼ばれている。この仕組を使ってヒーププロファイラメモリトレースの機能が実装されている。Allocator shimはmallocoperator newを差し替えるので、ASANと共存することは残念ながらできない。

というわけで、ASANをネタにChromeのAllocator shimの紹介をしてみた。このあたりはドキュメントが充実しているので読んでみると面白いかもしれない。

夕立

17 Sep 2018

午後六時半。九月も半ばを過ぎて辺りはもう真っ暗になっている。

先ほどから雷音と一緒に強い雨が降ってきた。あ、夕立だ、と思った。

夕立という言葉は季節感があって好きだ。最近よく耳にするゲリラ豪雨という言葉には無い含意がある。

夕立というには陽もとっくに暮れているけれど、自分にとってはこれは夕立。

Splatoon2 の漢字フォント

29 Jul 2018

スプラトゥーン2は1に引き続きフォントを自作しているそうだ。

スプラトゥーンのイカしたUIはこうして作られた─担当デザイナーが語る秘話 | 超ゲームウォーカー!

ゲームによくマッチしているし、特徴的でかっこいいフォントだと思う。

自作といっても全部作るのはさすがに無理っぽくて、漢字は既存のフォントを使っているそう。

採用情報:仕事を読み解くキーワード - 世の中にないフォントを作る

じゃあ漢字部分のフォントは何だろう、と思って調べてみた。

スプラトゥーン2の公式アプリであるイカリング2にアクセスすると、Web フォントが二つロードされる。78 KBのフォントと 587 KBのフォント。サイズから言って後者のフォントが漢字を含んでいるフォントだろう。後者のメタデータを見ればフォント名が分かるんではないか。

サイズの大きいフォントをshowttfで見てみる。NAME テーブルは見づらいので CFF テーブルのほうを見るとこんな感じだった。

...
Dump of top dictionary for KurokaneStd-EB
 Version=0 .notdef
 Notice=393 Copyright 2008-2013 Fontworks Inc. All Rights Reserved.
 fullname=394 KurokaneStd-EB
 familyname=395 KurokaneStd
 weight=396 EB
...

というわけで、漢字の部分はフォントワークスの「くろかね EB」というフォントかな?

くろかね EB|書体見本|FONTWORKS | フォントワークス

ちなみに前者のフォントには「Splatoon2」という名前がついていた。ひねりなしの素直な名前。

最後にお約束事だけど、商用フォントの不正利用はご法度です。

世界時計 Web アプリ

13 Jul 2018

ちょっと前から iPhone / Android みたいな世界時計の Web 版があるといいな、と思って作っている。

https://tokei.kanejaku.org

たまに海外に出ることがある。海外にいると大抵今日本は何時だっけ、ええと、となる。日本にいるときも海外のある都市の時間を知りたくなるときがある。

これまではスマホの世界時計を使って知りたい場所の時間を調べていた。だけど PC 上で作業しているときにスマホを取り出すのは PC 世代の自分には若干かったるい。ぱっとブラウザのタブを開いて興味のある都市の時間を調べたい。

要件定義は次の 3 つ。

  1. メインの時計は現在位置の時間を表示する。
  2. 任意の都市の現地時間を追加/削除できるようにする。追加する際は都市名で検索できるようにする。
  3. 追加した都市の時間は再度タブを開いたときも表示する。

1 は JS の API を使うだけ。3 もブラウザが提供する localStorage / IndexedDB で実装できる。2 に関しては Google Place API を使うことにした。

とりあえず欲しいものはできたけど、もう少しいじろうと思っている。レイアウトの調整と直感的でない操作の改善がひとつ。あとは React や Vue みたいなフレームワークを使ってみる、とか。

Emscripten Modularize

4 Jul 2018

Emscripten で出力した JS/wasm を Web アプリの一部として組み込む場合、これらのリソースの読み込みをできるだけ遅延させたいことがある。例えばユーザーがあるボタンをクリックしたときのみ wasm が提供する機能を使いたいとする。ボタンが押されるまでは wasm の読み込みは避けたい。

Emscripten はデフォルトでは Web アプリに組み込んだり遅延ロードさせるにはあまり適さない JS/wasm を出力する。Module オブジェクトはシングルトンとして生成されるし、wasm の場所もランタイムの JS と同じパスにあると仮定されてしまう。後者はランタイムの JS を読み込む前に適宜 locateFile とかを設定すればよいのだけど読み込み順序に依存するコードはアプリに組み込むには使いづらい。

Emscripten のフロントエンドである emcc に -s MODULARIZE=1 を渡すと Web アプリに組み込みやすい形で JS/wasm を出力してくれるようになる。このオプションを渡すと Module はシングルトンオブジェクトではなくコンストラクタ関数として生成される。これにより JS/wasm の読み込みタイミングと Module オブジェクトの生成タイミングを分離できて、例えばランタイムの JS は他の JS とバンドリングしておいて、wasm のロードは後で行う、といったことが可能になる。

以下は -s MODULARIZE=1 で生成した wasm をボタンが押されたタイミングでロード/実行する例:

function onWasmBinaryReady(wasmBinary) {
  return new Promise(resolve => {
    let mod = null;
    const args = {
      wasmBinary: wasmBinary,
      onRuntimeInitialized: () => {
        resolve(mod);
      }
    };
    mod = new Module(args);
  });
}

const wasmPath = "a.out.wasm";

const button = document.getElementById("button");
button.addEventListener("click", () => {
  fetch(wasmPath)
    .then(res => res.arrayBuffer())
    .then(buf => new Uint8Array(buf))
    .then(wasmBinary => onWasmBinaryReady(wasmBinary))
    .then(mod => {
      // Call mod.ccall(...)
    });
});

WebAssembly でフォントコンバータを作った

24 Jun 2018

Wasm で何かウェブアプリでも作ろうと思い、フォントのコンバータを作った。OpenType, WOFF, WOFF2 を相互に変換できる。

https://kombu.kanejaku.org/

WOFF2 のデコード/エンコードに google/woff2 を Emscripten で wasm 化したものを使っている。

大きめのフォント、例えば NotoSansCJKjp とかを WOFF2 に変換すると手元のマシンだと 2 分くらいかかる。最初は Emscripten のランタイムか wasm のオーバーヘッドが大きいのかと邪推したけど、WSL 上でコンパイルしたネイティブの woff2_compress でも 1 分ぐらいかかることが判明した。google/woff2 のエンコード自体があまり大きいフォントに最適化されていないようだ。

Wasm のサイズは-Ozを指定してコンパイルすると 925KB になった。Emscripten のランタイムを含む JS のほうは 26KB。このくらいならなんとか実用に使えるサイズ感な気がする。ランタイムのほうは使っていない部分が結構あるのでもっと小さくできそう。

こまごまとしたアップデートを今後やっていこうと思う。PWA 化とかしてみたい。

Audio Worklet で遊ぶ

2 Jun 2018

元ネタ:ドラクエで学ぶファミコン音色の時間変化

この動画で解説されている呪文の効果音を AudioWorklet を使って生成してみた。エンベロープの計算など細かいところは省いたがそれっぽく聞こえると思う。

コードは Github に置いた。

Chrome Web Audio Samples に AudioWorklet のサンプルがいくつかあるけど、ソースノードを作る例があまりない。この記事では入力を受け取らない、ソースノードを書くときに調べたことをちょっと紹介してみようと思う。

AudioWorklet

AudioWorklet は JS で書かれた音の生成ロジックをオーディオスレッド上で実行してしまおう、というもの。メインスレッド上で実行される ScriptProcessorNode が持っていた問題点を解決する目的で導入された。最終的に音を生成するのはオーディオスレッドなので、その上で JS 走らせられれば遅延や同期ずれを軽減できるんじゃないか、というのがアイデア。

AudioWorklet を使うには独自の AudioWorkletNode と AudioWorkletProcessor を定義しなければならず、ScriptProcessorNode と比較して書くのがかったるい。だけど、オーディオグラフ (AudioNode たちをつなげたもの) はメインスレッドの実行コンテキスト上で管理されるから、「オーディオスレッド上で動くロジック」をメインスレッド側で表現するノードが必要になる。その、メインスレッド側でオーディオグラフを構成する役割をもつのが AudioWorkletNode であり、その内部処理を担うのが AudioWorkletProcessor となる。

方針

今回作るのはある種のカスタム OscillatorNode みたいなもの。基本は OscillatorNode で作れる矩形波なのだけどファミコンの矩形波が持つ3つのモードを周期的に切り替えるような波形を作りたい。だとしたら OscillatorNode とできるだけ使用感を統一できるとよさそう、という方針を立てた。

OscillatorNode は生成する波の周波数を frequency パラメータで受け取る。frequency パラメータの型は AudioParam であり、これは時間経過とともに変更できるパラメータとなっている。また、OscillatorNode は start() と stop() メソッドを持っていて、開始と終了のタイミングを指定できる。

これらの振る舞いに似せた AudioWorketNode/AudioWorkletProcessor を作る。

周波数を設定する

OscillatorNode に周波数を指定する時のコードはこんな感じになる。

// |osc| is an OscillatorNode
osc.frequency.value = 440; // A4, 440Hz

frequency は AudioParam の一種で時間経過とともに変わりうるパラメータ。例えば frequency.setValueAtTime(880, t) を設定すれば時刻 t が来たタイミングで周波数が440Hzから880Hzに切り替わる。これと似たようなパラメータを AudioWorklet で定義したい。

時刻とともに変化するパラメータを定義するには AudioWorkletProcessor に parameterDescriptors() を指定する。

// processor.js
class FooWorkletProcessor extends AudioWorketProcessor {
  static get parameterDescriptors() {
    return [
      { name: 'frequency', defaultValue: 440 }
    ]
  }
}

上記のように定義するとメインスレッド側から以下のように渡すことができて、

// main.js
node.parameters.get('frequency').value = 220; // A3, 220Hz

プロセッサ側では 音を生成するときに呼ばれる process() の parameters 引数から参照することができる。

// processor.js
class FooWorkletProcessor extends AudioWorkletProcessor {
  process(inputs, outputs, parameters) {
    let output = outputs[0][0];
    for (let i = 0; i < output.length; i++) {
      const freq = parameters.frequency[i];  // <-- 220
    }
  }
}

時間と共に変わるのを表現するため、parameters.frequency は出力バッファのサイズと同じ数を持った配列としてわたってくる。

start() と stop()

Web Audio API で定義されている Audio Node のいくつかは AudioScheduledSourceNode を継承していて、OscillatorNode もその一つ。このインターフェースが start() と stop() を提供しているが、AudioWokrletNode はこれを継承していない。その代わりに MessageChannel を使って同等の機能を提供してね、というのが AudioWorklet の設計者の意図らしい。

MessageChannel は実行コンテキストが異なるオブジェクト間での通信手段を提供するもの。AudioWorkletNode と AudioWorkletProcessor はそれぞれメインスレッドとオーディオスレッド上で動くので、インタラクティブに音の生成を制御する場合には MessageChannel が必要になる。AudioParam も動的に変化する情報を渡すのに使えるけれど、こちらは例えばユーザが停止ボタンを押したら音の生成を止める、みたいな用途には向かない。

AudioWorkletNode/AudioWorkletProcessor はそれぞれ MessageChannel のエンドポイントである MessagePort を持っていて、一方の MessagePort から postMessage() するともう片方の onmessage ハンドラで受け取ることができる。

// main.js
class FooWorkletNode extends AudioWorkletNode {
  start(when) {
    this.port.postMessage({ action: 'start', when: when });
  }
}

// processor.js
class FooWorkletProcessor extends AudioWorkletProcessor {
  this.port.onmessage = (e) => {
    if (e.data.action === 'start') {
      // logic to start
    }
  };
}

上のコード片はメインスレッドからオーディオスレッド側へ情報を送る例。逆も同じように書ける。

時間変化を伴わないパラメータを渡したい

一度設定したら変化しないパラメータを指定したい時はどうしたらいいんだろうか。parameterDescriptors() で指定するパラメータは毎回 process() に渡されるものだけど、今回作った AudioWorkletProcessor ではメインスレッドから渡されるパラメータで時間と共に変わるものは使っていない。だとしたらコンストラクタでパラメータを受け取れば多少効率が良さそうに思える。

AudioWorkletNode のコンストラクタ引数には省略可能な options パラメータがある。その一部に processorOptions というのがあってこれにオブジェクトを渡すと AudioWorkletProcessor の options の processorOptions として受け取れる。具体的にはこう。

// main.js
class FooWorkletNode extends AudioWorkletNode {
  constructor(context) {
    const options = {
      // Passed as |options.processorOptions| in AudioWorkletProcessor.
      processorOptions: {
        barProp: 'bar'
      }
    }
    super(context, 'foo-processor', options);
  }
}

// processor.js
class FooWorkletProcessor {
  constructor(options) {
    options.processorOptions.barProp; // <-- 'bar'
  }
}

今回は OscillatorNode に使い勝手を寄せたのでこのやり方は使わなかったが、AudioWorkletProcessor の生存期間の間に不変のパラメータを定義したい場合はこちらを使うとよいかもしれない。

参考文献