kanejaku.org

ポッドキャストを聴く日々

9 Oct 2020

朝の散歩中に Misreading #83を、仕事を終えた後の散歩中に Retro Game Audio ep 21 を聴いた。

DTrace が使われている現役 OS といえば MacOS ではないかと思ったけど Mojave でサポートされなくなった、という言及を見つけた。あと、BPF は古の tcpdump のカーネル側の実装を指していて、汎用 Tracing 機構となった実装は eBPF と呼ぶ認識がまだ自分にはあって、色々キャッチアップができていないな、と実感する回であった。

Retro Game Audio の方は少し前に聴き始めて興味のある回をつまみ食いしている。DPCM とコントローラの read が干渉するのは知らなかった。スーパーマリオ 3 がコントローラの read を 3 回やって操作性を担保しているとか、悪魔城伝説のダメージをくらった時の「うっ」っていう効果音を鳴らしている間にキャラクターを操作できないのは干渉を workaround しているんだと思う、とかトリビアがあって面白い。燃えろプロ野球がサウンド再生の観点から面白いとか思いもよらなかった。 Jaleco ってどう発音するんだろう、とか英語話者ならではの疑問も新鮮だった。

Snacking

8 Oct 2020

Snacking なる言い回しを知った。

https://staffeng.com/guides/work-on-what-matters

簡単にできる、でも重要じゃないことをやるのは避けよう、と。やった気になるけど本質的な問題を解いてないよ、と指摘している。耳が痛い。Snacking を encourage する仕組みがあるのも問題。CL/Commit 数を報告させるとか、Github で草を生やすとか。

自分は Preener でもある。自己顕示欲が強いというのとちょっと違うけど、大したことしていないのに人からよく見られたい欲があるというか。文章を書いていて言い回しに凝りがちなのはその表れだと思う。

概念に名前を付けることで意識が向き、改善しようとする姿勢が生まれるので、名前を付けるの大事だなと再認識した。

自己啓発的な記事を紹介するだけのこのブログエントリも Snaking である。

今日もポッドキャスト消化

6 Oct 2020

早速新しいエピソードが出ていたので Misreading chat #82 拝聴。

今回は教養番組みたいなノリで聴けてこういう話題も良い。物理や化学、光学が分かるともっと楽しめるんだろうな。書いたコードがガラスに書かれて北極に埋まっていたら胸熱だなー。


Windows IME の変換アルゴリズムが変わった?意図しない変換が多くなった気がする。

Misreading chat #81 を聞いた

5 Oct 2020

Misreading chat が再開された。めでたい。

強連結成分とりだしてトポロジカルソート、とかいかにもアルゴリズムの授業でありそう、とか思って聞いていたら正にそういう感想を話されていた。

マイナーな SoC でもカーネルツリーからコンパイルすることなく動く perf 的な not-perf というプロファイラをちょっと前に使っていたのだけど、これどうやって動いているんだろう、後で調べようと思って結局調べていないことを思い出した。

追記 (2020-10-06): 森田さんに perf_event_open を使ってあとは自力で頑張っているっぽい、ということを教えてもらった。ありがとうございます。

プロファイラの話題、続編があるといいなあ。最近趣味プロジェクトでプロファイリングをしているので興味がある。

サイトのデザインを少し変更

3 Oct 2020

もうすっかり秋だ。外を歩いていると金木犀の香りが風に乗ってやってくる。爽やか。

観測範囲内でブログへの回帰が進んでいる。このサイトでももうちょっと気軽に、わりとどうでもいいことを日記風につけるのも悪くないかと思い始めた。これまでのトップページはエントリごとにリンクがあって、日記的に運用するのとは相性が悪い。ということで適当に Pagination して最新のエントリを表示するようにした。あとはアナリティクスの利用も明示するように。

ちなみにこのサイトは Hugo で生成して Netlify で運用している。最初は Github pages で運用していたが Netlify だとデプロイが簡単なので気に入っている。ロードはやや遅く感じるけど、速度は重要ではないから特に困ってはいない。

Minimp3 Wasm Without Emscripten

8 Feb 2020

I created a web-based mp3 decoder using minimp3. The decoder is a tiny WebAssembly compiled with clang version 9 (without Emscripten). Applying wasm-opt, the binary size of the decoder is just 21 KB.

Here is a demo page and the repository.

This article describes how I created the decoder.

minimp3 has very few dependencies. It just requires memcpy(3), memmove(3), and memset(3). We can easily find naive implementations of these functions on the Web.

An interesting part is memory allocation. To decode a mp3, we need to pass the mp3 data to minimp3 and allocate memory for decoded PCM data. Both require dynamic memory allocations.

One might think of using malloc(3). This is the way I took first, but after reading Surma’s article that describes wasm-ld’s memory layout, I came up an idea – having a fixed memory layout and allowing dynamic memory allocation only for input (MP3 data) and output (PCM data). This approach reduced most of the complexities of memory allocation. I no longer needed a generic malloc(3) implementation. That eliminated the need for Emscripten.

Here is the memory layout.

We put input MP3 data at the beginning of the heap (that is, __heap_base). The size of the input MP3 data is stored in mp3_data_size. Decoded PCM data follows it and its size is stored in pcm_data_size. Assuming that the Wasm is used from a single thread, the decoder information such as minimp3’s context (mp3dec_t), number of samples etc are placed on statically allocated memory.

This memory layout doesn’t allow us to free up input MP3 data after decoding. This is a trade-off not having a generic memory allocator. Practically this wouldn’t be a problem because we need to allocate memory for PCM data before decoding and WebAssembly doesn’t provide a way to shrink memory. If we want to free up input data, what we can do is to copy the decoded PCM data somewhere and discard the Wasm instance.

With this memory layout, decoding a MP3 data would look like the below:

// decoder.js

const { instance } = await WebAssembly.instantiate(...);
const wasm = instance.exports;
const mp3Data = /* Some Uint8Array */;

// Set MP3 data into Wasm memory.
wasm.set_mp3_data_size(mp3Data.byteLength);
const mp3DataInWasm = new Uint8Array(
  wasm.memory.buffer,
  wasm.mp3_data_offset(),
  wasm.mp3_data_size());
mp3DataInWasm.set(mp3Data);

// Decode.
wasm.decode();
const pcm = new Int16Array(
  wasm.memory.buffer,
  wasm.pcm_data_offset(),
  wasm.pcm_data_size() / 2);

// Use `pcm`.
// decoder.c

extern unsigned char __heap_base; // Defined by LLVM.

static size_t mp3_data_size;
static size_t pcm_data_size;

const uint8_t *mp3_data_offset() {
  return &__heap_base;
}

size_t mp3_data_size() {
  return mp3_data_size;
}

void set_mp3_data_size(size_t size) {
  mp3_data_size = size;
  // Grow memory if needed to set MP3 data.
}

const uint8_t *pcm_data_offset() {
  return mp3_data_offset() + mp3_data_size;
}

size_t pcm_data_size() {
  return pcm_data_size;
}

void decode() {
  pcm_data_size = /* Compute PCM size using minimp3 */;
  // Grow memory if needed for PCM data.

  const uint8_t *mp3 = mp3_data_offset();
  const int16_t *pcm = pcm_data_offset();
  // Decode `mp3` using minimp3 and put samples into `pcm`.
}

A tricky part is to use wasm-ld’s __heap_base. By specifying a linker option -Wl,--export-all I was able to use __heap_base in my code, but the option also exported functions which weren’t needed, bloating Wasm binary size slightly.

What I needed was -Wl,--export=__heap_base. --export is a lld’s option which isn’t specific to WebAssembly and that’s why it isn’t listed on the document of wasm-ld.

The remaining parts of the implementation aren’t specific to Wasm. The github repository has the full implementation.

Side notes

We don’t need a custom MP3 decoder if we don’t need to decode large MP3 files. BaseAudioContext.decodeAudioData() is enough for most MP3 files, but decodeAudioData() didn’t work well for my use case. I want to decode large MP3 files like ATP podcast. Decoding such MP3 files consumes a lot of memory which I want to avoid.

WebCodecs may eliminate the demand of this kind of decoder. This is one of APIs I’d like browsers to implement.

黄昏

6 Jan 2020

今の住処は見晴らしがよい。

カタカタとキーボードを叩いていると部屋が暗くなってきて、あ、もう夕方だ、と気づいて窓に視線を送る。夕陽はもうビルの向こう。紅色から蒼色へのグラデーションに魅了され、寒さが堪えるけれど窓を開ける。

冬の澄んだ空気に触れた。はっと子供のころに住んでいた家を思い出す。

雪は降らないけれど早朝には霜が降りる。安普請な家だったから冷え性の自分には堪えた。

近くには電車の駅があった。駅から聞こえてくる笛の音で目が覚めたのを思い出す。冬の時期には良く通る音だった。

身震いしつつそんなことを懐かしむ。

2019年の振り返り

30 Dec 2019

今年はアウトプットを重視した年だった。取り組み始めたら投げ出さずに区切りのいいところまでやる。ある程度達成できたと思う。その一方で集中して取り組まないと解けない問題は避けてしまった印象がある。


趣味プロジェクトを区切りのいいところまで終わらせる、そしてそれらを持続させることを意識した。機能したと思う。以下今年やれたこと。

  • 勉強がてら自分に特化した英語辞書アプリを Rust で書き直して普段使いできるレベルまで作った。
  • Chrome Mojo IDL の Language Server Protocolを実装した。
  • フォントフォーマット変換 Web App をメンテした。
  • NES エミュレータをマリオが動くくらいまでは実装した。

特に年末に自由に使える時間があったのが有難かった。やりたいと思っていたことを消化できた。

  • 仕事の優先度を考えると手を出しづらかったが余暇としてやれたらいいな、と思っていたことができた。
  • アドベントカレンダーに参加できた。
  • 私的に頼まれたツールを二つぐらい作れた。

公開しない前提で日記をつけるようにようにした。森田さんのやり方を真似させてもらった。日々の振り返りをする上でこれもいい感じに働いたと思う。その分、ブログを書く意欲がなくなってしまったが、これは妥当なトレードオフだと思う。余談: 森田さんの昨今の記録が公開されていてうれしい。

leetcode は 2, 3 問解いただけでほとんどやらなかった。試験対策に対する興味が失われた…というのは易いけど地頭を鍛えるのを怠ったというのが実情だろう。

英語について。昨年から英会話のレッスンに通ったことでだいぶん苦手意識がなくなった。コードを書いていてもコメントを書くのに労力がいらなくなったのは大きい。喫緊の案件ではなくなったせいで今年はさぼりがちであり、オフィス移転もあって後半はレッスンに通うのをやめてしまった。デザインドキュメントを書くのには相変わらず苦労しているので引き続き改善点のひとつではある。

やりたかったけど時間を割かなかった趣味もある。自分の優先度は「晩酌>>>>コーディング>(越えられない壁)>その他の趣味」 なので毎晩のように酩酊しつつコードを書いていると時間が溶けてしまう。ある程度しょうがないと割り切っているが来年は読書の時間を増やしたい。

今年聞き始めたポッドキャストで一番気に入ったのが IGN Japan のしゃべりすぎゲーマー。聞き始めのころは「なんだこのイキリオタクは…」とか思っていた今井さんという方がいるのだが、今ではすっかりファンになってしまって配信の時に居ないとちょっとがっかりしてしまう。他の語り手も本当に魅力的で毎回楽しみにしている。

今年使ってよかったものは Windows TerminalVS Code Remote Development の二つ。特にターミナルは Windows で開発する上での一番の pain point を解消してくれた。マイクロソフトが開発ツールに本気を出すとやっぱりすごいなあという印象を新たにしたし、Mac が微妙すぎるのもあって最近はもっぱら Windows ばかり使っている。


入力と出力、あるいは抽象と具現の車輪を回す。どちらを欠いても物事の理解は進まない。今年は具現に時間を割いていろんなことを達成した感はあるが、出がらしの茶葉になった気分もある。行動指針の粒度を細かくして年単位じゃなく四半期ぐらいのスパンで両輪を回すのもいいかもしれない。

Running Wasm on V8 Without JS API

5 Aug 2019

V8 を wasm の実行環境として C++ アプリケーションに組み込んでみよう。

今回使う V8 のバージョンは 7.8.58。samples/hello-world.cc に wasm を実行するがあるけれど、この例は JS 上に定義された API を叩くスクリプトを実行する、という体で書かれている。V8 としては JS から使う前提があるからこういう例になっているのだろうけど、JS を使わずに wasm を実行できないだろうか。

検索すると stackoverflow に同じ思いの先人が質問していて、回答を元にコードを例示してくれていた。ありがたい。ただいくつか API の変更があったようで、v7.8.58 では動かない。V8 の変更履歴を参照しつつ改変したら動くようになった。これで JS スクリプトを経由せずに wasm を実行できるようになった。が、依然として JS API 自体には依存しており処理が減っている感じがしない。

さらに言えば前処理と後処理が冗長なのはいいとしても、値の引き回しに v8::Local<T> を使わないといけないのも釈然としない。今回は JS を使わないのだから、C++ の数値を V8 JS value に変換し、さらにそれから JS value を wasm の数値型に変換する、といったステップを踏むのは無駄に感じる。C++ ⇔ wasm の型変換があればいい。

そう思いつつ V8 のコードを読んでいると c-api.cc というファイルが目についた。冒頭のコメントによると、仕様への提案をベースに書かれたものらしい。wasm を C/C++ に組み込むための提案、だそう。これが求めていたものっぽい。

使い方を調べてみよう。V8 は Wasm C API を third_party/wasm-api 配下に組み込んでいる。V8 を組み込む C++アプリケーションはthird_party/wasm-api/wasm.hhをインクルードすることで wasm の関数の引数や戻り値を v8::Local<T> を使うことなくやりとりできるようになる。提案のを見れば使い方はつかめると思う。要点を抜粋すると以下のような感じ。

  wasm::vec<byte_t> binary; // wasm バイナリ、外部からの入力
  // 実行環境の初期化
  auto engine = wasm::Engine::make();
  auto store_ = wasm::Store::make(engine.get());
  auto store = store_.get();
  // コンパイルとインスタンス生成
  auto module = wasm::Module::make(store, binary);
  auto instance = wasm::Instance::make(store, module.get(), nullptr);
  // 関数の取り出し
  auto exports = instance->exports();
  assert(exports.size() == 1 && exports[0]->kind == wasm::EXTERN_FUNC && exports[0]->func());
  auto run_func = exports[0]->func();
  // 実行
  wasm::Val args[] = { wasm::Val::i32(14), wasm::Val::i32(9) };
  wasm::Val results[1];
  run_func->call(args, results);
  printf("%d\n", results[0].i32());

(コード全体はここを参照)

V8 API を使った例に比べるとだいぶんすっきりとする。ただ内部ではv8::Contextやらv8::Isolateなんかを使っているので初期化等にかかる時間はたぶん大差がない。

一方、関数呼び出しの引数と戻り値に関しては V8 JS value への変換がなくなっており、この部分は高速化が見込めるんじゃないだろうか。

今回は V8 を使うのを前提としたけれど、実際のところ wasm を実行したいだけであれば Wasmer を使う、あるいは WASI に則った実行環境を使うほうが良い。JS のオーバヘッドが気になるなら JS をサポートしてない環境を使うのが理にかなっている。

wasm: Copy と Anyref Export の速度比較

29 Jul 2019

AssemblyScript でライブラリコードの高速化をしてみる の Appendix で anyref を使うとコピーのオーバーヘッドを減らせるかもしれないとの記述があった。おおなるほどそうかも、と思ったが少し考えてみると別のオーバーヘッドが生じそうだ。メモリアクセスをエクスポートされた関数呼び出しに変える必要がある。エクスポートされた関数の呼び出しはインライン化できないし、JS <-> wasm の型変換も必要になる。

ということでどちらが早いのかを 2019 年 7 月時点での評価をしてみようと思う。現時点で anyref が使えるのは Firefox nightly と Chrome。Chrome では --js-flags=--experimental-wasm-anyref フラグを渡す必要がある。

やりたいことと比較する対象を整理する。なんらかのデータを JS 側で Uint8Array として保持している。このデータを wasm で効率的に処理したい。比較する手段は次の二つ。

  1. Copy: wasm モジュールのメモリ領域にデータをコピーする。
  2. Export: データにアクセスする関数を anyref を使ってエクスポートする。

1 は従来のやり方。 JS 側で持っているデータを wasm の メモリ領域にコピーして、コピー先のアドレスとデータの長さを wasm で定義した関数に渡す。2 は冒頭のスライドに書いてあるやり方。anyref があればデータへアクセスする関数を軽量に wasm モジュールに提供できるのでは、というアイデアだ。JS にある Uint8Array buf に対して、 buf[offset] と同等の関数 readByteFromUint8Array(arr: anyref, offset: i32): i32 を wasm モジュールにエクスポートする。

それぞれのオーバーヘッドをみるために Uint8Array の各バイトを足し合わせる単純な関数を wasm で定義する。C で書くとこんなふうなやつ。

/* Copy を使った関数 */
int32_t accumulateCopy(uint8_t *arr, int32_t length) {
    int32_t sum = 0;
    for (int32_t i = 0; i < length; i++) {
        sum += arr[i];
    }
    return sum;
}

/* Anyref を使った関数 */
extern struct Uint8Array;
extern uint8_t readByteFromUint8Array(Uint8Array *arr, int32_t length);
int32_t accumulateAnyref(Uint8Array *arr, int32_t length) {
    int32_t sum = 0;
    for (int32_t i = 0; i < length; i++) {
        sum += readByteFromUint8Array(arr, i);
    }
    return sum;
}

現時点では anyref を出力する安定したコンパイラがないので今回は上記の関数を wat で手書きした。

この 2 つの関数の実行にかかる時間をデータのサイズ毎に計測する。データサイズを 1K, 1M, 10M とした時に手元の Chrome でかかった時間(ミリ秒)のグラフを以下に示す。横軸は対数。

複数回試してみたところ、手元の環境ではデータサイズが 1K の場合は大差ないが、大きいデータサイズではコピーするほうが常に 10 倍ぐらい速い、という結果になった。Firefox でも傾向は同じ。多分どの環境でも似たような結果がでるんじゃないかと思う。使ったベンチマークはここに置いた。

結果を簡単に考察すると: anyref を使ってエクスポートした関数を用意するとコピーにかかる時間は省略できるが、別のオーバーヘッド、すなわち wasm と JS を行き来するコストが支配的かつコピーよりも遅い。TypedArray とそのメソッドを JS を経由せずに wasm から直接呼び出せて、かつその呼び出しをインライン化できないと anyref を使っても速度の向上は見込めなさそう。