習作としてFIR (Finite Impulse Response) フィルタの可視化をつくってみた。
FIRフィルタのcoefficient(係数)をJSONで張りつけると、係数のグラフと、その周波数特性を表示する。複素数対応
習作としてFIR (Finite Impulse Response) フィルタの可視化をつくってみた。
FIRフィルタのcoefficient(係数)をJSONで張りつけると、係数のグラフと、その周波数特性を表示する。複素数対応
https://developer.mozilla.org/ja/docs/WebAssembly/Rust_to_wasm に書いてある通りで便利。alert 出しても面白くないのでFFT のベンチをとってみるというのをやってみた。
タスクは N=4096 の複素数のFFTをやることとした。rust 側のコードは rustfftを呼ぶだけ。
以下のような組合せで実行
N = 4096 [wasm] instance pointer x 8,361 ops/sec ±0.23% (97 runs sampled) [wasm] instance wasm_bindgen x 7,869 ops/sec ±0.23% (99 runs sampled) [wasm] one func pointer x 2,730 ops/sec ±0.48% (95 runs sampled) [wasm] one func wasm_bindgen x 2,753 ops/sec ±0.34% (97 runs sampled) [js] instance dsp.js x 3,722 ops/sec ±0.74% (92 runs sampled) Fastest is [wasm] instance pointer +------------------------------+--------------------------+----------------------------+---------------------------------+-------------------------+---------------------------------+----------------------------+ | name | ops | vs [wasm] instance pointer | vs [wasm] instance wasm_bindgen | vs [js] instance dsp.js | vs [wasm] one func wasm_bindgen | vs [wasm] one func pointer | +------------------------------+--------------------------+----------------------------+---------------------------------+-------------------------+---------------------------------+----------------------------+ | [wasm] instance pointer | 8360.9ops/sec (+/-0.23%) | - | 6% | 125% | 204% | 206% | +------------------------------+--------------------------+----------------------------+---------------------------------+-------------------------+---------------------------------+----------------------------+ | [wasm] instance wasm_bindgen | 7869.3ops/sec (+/-0.23%) | -6% | - | 111% | 186% | 188% | +------------------------------+--------------------------+----------------------------+---------------------------------+-------------------------+---------------------------------+----------------------------+ | [js] instance dsp.js | 3721.9ops/sec (+/-0.74%) | -55% | -53% | - | 35% | 36% | +------------------------------+--------------------------+----------------------------+---------------------------------+-------------------------+---------------------------------+----------------------------+ | [wasm] one func wasm_bindgen | 2752.8ops/sec (+/-0.34%) | -67% | -65% | -26% | - | 1% | +------------------------------+--------------------------+----------------------------+---------------------------------+-------------------------+---------------------------------+----------------------------+ | [wasm] one func pointer | 2730.0ops/sec (+/-0.48%) | -67% | -65% | -27% | -1% | - | +------------------------------+--------------------------+----------------------------+---------------------------------+-------------------------+---------------------------------+----------------------------+
https://github.com/cho45/wasm-fft-sketch/blob/master/sketch.js#L174
とりあえず何も考えずに pure rust のライブラリをコンパイルして呼んでいるだけなのに、wasm 版が早い (rustfft が良いのかもしれないが)。
計算の比重が高いからか、意外とメモリコピーしてていても差がでない。
wasm_bindgen と wasm-pack が大変使い勝手が良く、ほぼ悩むことなく即 Rust のコードを書きはじめて、またそれをすぐに JS から呼ぶことができる。内部的には Rust 側のブリッジ関数と JS 側のブリッジ関数を同時に作ってくれている。
ただ、wasm はメモリ空間が JS のメモリ空間と分かれているため、wasm_bindgenが生成するJS側のブリッジ関数は (便利ではあるが) 若干非効率な実装になっており、TypedArray の受け渡しではコピーが多くなる。
これを防ぐには、やはり自力で wasm 側のメモリ空間からメモリを確保して TypedArray をインスタンス化して使用し、必要なくなったら free するという、メモリ管理を自分でやる必要がある。
これはまぁまぁ面倒くさいが、生成されたJSコードを読めばどう呼べば適切かは容易にわかるので、とりあえずは難しいことではない。
生成コードをただ使う場合、関数呼び出しだけなら生成コード内でメモリのfreeが行われるので、あまり気にする必要はないが、struct に関しては生成コードをただ使っている場合でも、明示的にオブジェクトの free を呼ぶ必要がある。JS にはデストラクタがないので仕方ないが、注意がいる。
Benchmark.js ちゃんと使えるので良いのですが、計測を頑張っている割に結果表示が貧弱というのが悲しいところです。
なので Perl の Benchmark.pm 風に表示する complete の関数を書いてみました。cli-table に依存します。
for x 115,102,309 ops/sec ±0.43% (95 runs sampled) for of x 62,020,029 ops/sec ±0.23% (96 runs sampled) Fastest is for +--------+-------------------------------+--------+-----------+ | name | ops | vs for | vs for of | +--------+-------------------------------+--------+-----------+ | for | 115102308.6ops/sec (+/-0.43%) | - | 86% | +--------+-------------------------------+--------+-----------+ | for of | 62020028.7ops/sec (+/-0.23%) | -46% | - | +--------+-------------------------------+--------+-----------+
//#!/usr/bin/env node
"use strict";
const Benchmark = require('benchmark');
const Table = require('cli-table');
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
new Benchmark.Suite().
add('for', () => {
let sum = 0;
for (let i = 0, len = array.length; i < len; i++) {
sum += array[i];
}
return sum;
}).
add('for of', () => {
let sum = 0;
for (let i of array) {
sum += i;
}
return sum;
}).
on('cycle', function(event) {
console.log(String(event.target));
}).
on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').map('name'));
const array = this.slice(0).sort( (a, b) => b.hz - a.hz);
const table = new Table({
chars: {
'top': '-' ,
'top-mid': '+' ,
'top-left': '+' ,
'top-right': '+',
'bottom': '-' ,
'bottom-mid': '+' ,
'bottom-left': '+' ,
'bottom-right': '+',
'left': '|' ,
'left-mid': '+' ,
'mid': '-' ,
'mid-mid': '+',
'right': '|' ,
'right-mid': '+' ,
'middle': '|'
},
head: ['name', 'ops'].concat( array.map( b => 'vs ' + b.name ) )
});
const comparison = array.map( (a, ia) => array.map( (b, ib) => {
if (ia === ib) return "-";
return Math.round((a.hz / b.hz - 1) * 100) + '%';
}));
array.forEach( (bench, i) => {
table.push([
bench.name,
`${bench.hz.toFixed(1)}ops/sec (+/-${bench.stats.rme.toFixed(2)}%)`
].concat(comparison[i]))
});
console.log(table.toString());
}).
run({});