ベンチマーク
libsonare と librosa (Python) の音声解析タスクにおける性能比較。
このページは性能の文脈を確認するための資料であり、機能チュートリアルではありません。API を先に学ぶ場合は はじめに、機能マップ、利用する言語の実行環境別ページを読んでください。
ベンチマーク値の読み方
レイテンシが低いほど、この条件では処理が速く終わったという意味です。2倍 の高速化は「このベンチマークでは 2 倍速い」という意味で、すべてのファイルで常に 2 倍速いという意味ではありません。ハードウェア、サンプルレート、クリップ長、デコード時間、中間特徴量を再利用できるかで結果は変わります。
このページで身につくこと
このページを読むと、次のことを判断・説明できるようになります。
- ベンチマーク値を、万能な速度保証ではなく、特定ワークロードの測定値として読める。
- フルパイプラインの高速化と、機能別比較の違いを区別できる。
- 中間結果の共有、ネイティブ実行、パイプライン設計がなぜ性能に効くかを理解できる。
- ハードウェア、入力、実装が変わったときに、ベンチマークソースを見つけて再計測・更新できる。
計測方法
以下の数値はすべて「生音声からのスタンドアロン計測」です。各呼び出しは必要な中間状態(STFT、Mel など)を元のサンプルから毎回再構築します。これは両 API を単発で使うユーザーが体験するのと同じコードパスなので、フェアな比較になっています。ベンチマークのソースと結果 JSON は libsonare リポジトリの benchmarks/ にあります。
ハードウェア
Apple M5 Max (16 コア、128 GB ユニファイドメモリ) で計測。絶対値はハードウェアでスケールしますが、相対差は安定しています。
フルパイプライン解析
完全な楽曲解析: BPM + キー + ビート + コード + セクション + 音色 + ダイナミクス + リズム + メロディ。
テスト音声: 合成 WAV、73 秒、44100 Hz ステレオ。コミット済みの benchmarks/generate_audio.py でローカル生成します(WAV 本体は gitignore 対象でコミットされません)。
Full Analysis Latency (lower is better)
| ライブラリ | 言語 | 時間 | 相対 |
|---|---|---|---|
| libsonare | C++ | 0.67秒 | 1倍 |
bpm-detector --comprehensive (librosaベース) | Python | 36.4秒 | 約54倍遅い |
フルパイプライン値は libsonare の設計が最も活きる場面です: スペクトログラム共有、特徴量計算の並列化、C++ パイプライン内で一度だけ行う 44.1→22.05 kHz への自動ダウンサンプリング(librosa 側のパイプラインもリサンプリングするので、比較はフェアなままです)、そしてパイプライン内に Python 境界がないこと。
機能別比較
同じ 73 秒の音声 (22050 Hz にリサンプリング後) における個別の特徴抽出。librosa は time.perf_counter、libsonare は sonare_bench バイナリ内の chrono::steady_clock で計測。
Per-Feature Latency (lower is better)
| 機能 | librosa | libsonare | 高速化 |
|---|---|---|---|
| STFT (2048, hop 512) | 13.3ms | 13.9ms | 0.96倍 (同等) |
| メルスペクトログラム (128バンド) | 20.4ms | 23.0ms | 0.88倍 (同等) |
| HPSS (カーネルサイズ 31) | 1,762ms | 89ms | 19.7倍 |
| オンセット強度 | 21.5ms | 23.5ms | 0.91倍 (同等) |
| クロマ (STFTベース) | 44.6ms | 15.4ms | 2.9倍 |
| ビートトラック | 35.6ms | 23.8ms | 1.5倍 |
| MFCC (13係数) | 21.6ms | 23.8ms | 0.90倍 (同等) |
| pYIN | 5,825ms | 474ms | 12.3倍 |
| スペクトル重心 | 24.8ms | 16.5ms | 1.5倍 |
WASM Mastering ISP Guard
サンプル間ピーク(ISP)とは、2 つのサンプルのあいだに生じるピークのことです。生のサンプル値の上では見えませんが、DAC が波形を再構成すると実際に現れます。そのためリミッターはこれを捉えるためにオーバーサンプリングする必要があります。このベンチマークは、その検出器がブラウザで動かせる程度に十分速いことを確認するものです。
マスタリングの True Peak 経路も WebAssembly 上で検証しています。48 kHz ステレオの 1 ms ブロックを、4 倍オーバーサンプリングと最終リミッターと同じ sliding-max ガードで処理するベンチマークです。
| ベンチマーク | ランタイム | 1 ms audio あたりの中央値 | 閾値 | 結果 |
|---|---|---|---|---|
mastering_isp_4x_stereo_1ms | WASM / Node | 0.0062ms | 5.0ms | 合格 |
これによって、サンプル間ピーク検出器がブラウザレンダリングに十分な余裕を持つことを確認しています。libsonare リポジトリで cd bindings/wasm && yarn bench:wasm:isp を実行すれば再現できます。
率直な評価
STFTが支配的な軽量な特徴量 (STFT、Mel、Onset、MFCC) では、libsonare は librosa と ほぼ同等 です。librosa は FFT を scipy.fft (高度に最適化された C/Fortran) に委譲しているため、FFT コストを差し引くと一回呼ぶだけでは差を作る余地があまりありません。
明確な勝ち筋は (1) 計算量の重い処理 で、libsonare のマルチスレッドメディアンフィルタ (HPSS) と並列ビタビ復号 (pYIN) が Python パイプラインを大きく引き離します。そして (2) パイプライン全体の解析 で、中間結果の共有と Python 境界の排除が支配的になります。
大きな差がつく場所
フルパイプライン (54倍): 共有中間結果 + Python 境界なし
libsonare の analyze() は STFT と Mel スペクトログラムを 一度だけ 計算し、下流のアナライザで再利用します。
この再利用が効きます。
| アナライザ | 再利用できる中間表現 |
|---|---|
| コード検出 | キー検出と同じクロマグラム |
| ビートトラッキング | セクション検出器が使うオンセットエンベロープ |
独立したパスは CPU コア間で並列実行されます。これらはどれも Python 境界を跨がないため、呼び出しごとのディスパッチオーバーヘッドが消えます。
bpm-detector (および他の librosa ベースのパイプライン) は各アナライザでこれらの中間結果を再構築し、Python から全体をオーケストレーションします。コストが積み重なります。
HPSS (19.7倍): キャッシュフレンドリーなマルチスレッドメディアンフィルタ
librosa の HPSS は scipy.ndimage.median_filter を水平方向と垂直方向に1回ずつ呼びます。これは各ピクセルを逐次処理する汎用 C 実装です。
libsonare はこれをカスタムスライディングメディアンに置き換えています:
- ソート済みフラット配列 (O(log k) の二分探索 + O(k) の memmove) — 一般的なカーネルサイズで L1 キャッシュに収まる、ツリー構造ではなく
- マルチスレッド実行 — 行と列がすべてのコアで並列処理される
- 結果: このハードウェアで scipy 版の約 20 倍高速
メディアンフィルタ(とスライディングメディアン)とは?
メディアンフィルタ は、各値を小さな窓内の近傍の中央値で置き換えます。平均と違ってスパイクや外れ値を取り除きつつエッジを保つため、HPSS で使われます。水平方向のメディアンは定常的な(倍音)ラインを、垂直方向のメディアンは鋭い(打撃)成分を残します。スライディングメディアン は、毎回ソートし直すのではなく窓を動かしながらこれを効率よく計算します。
pYIN (12.3倍): ネイティブのビタビ + 候補並列化
pYIN のボトルネックはフレームごとの候補評価とビタビ復号です。libsonare は両方を C++ でフレーム処理を並列化して実装し、librosa の Numba-JIT 内ループを置き換えています。特に候補評価は C++ の密なループと Eigen3 経由の SIMD ベクトル化の恩恵を受けます。
クロマ (2.9倍): STFT → フィルタバンクの密なパス
クロマはスペクトログラムから 12 ピッチクラス表現を constant-Q 風のフィルタバンク経由で導出します。libsonare の STFT とフィルタバンク乗算は単一の連続バッファ上の Eigen3 ベクトル化行列演算として動くため、librosa の NumPy 演算スタックのディスパッチオーバーヘッドを回避できます。
速くならない箇所 (とその理由)
- STFT 単体: librosa は
scipy.fftに委譲しており、これは C/Fortran 実装。両者の差はノイズの範囲内。 - Mel / MFCC / オンセット強度: 基底の STFT コストに支配される。STFT 後のフレームごとの Mel フィルタバンク乗算や DCT は、別言語で書いても差が出るほど重くない。
- パイプライン内での利用:
analyze()内ではこれらの特徴量は <1ms で動きます。STFT/Mel が一度だけ計算され共有されるためです。上記のスタンドアロン値は「単体で呼んだときのコスト」を示すもので、パイプライン内コストではありません。
自分で再現する
ベンチマークは libsonare/benchmarks/ にあり完全に再現可能です:
# ローカルの libsonare チェックアウト内で
cmake -B build-bench -DCMAKE_BUILD_TYPE=Release \
-DBUILD_BENCH=ON -DBUILD_TESTING=OFF -DBUILD_CLI=OFF
cmake --build build-bench -j
rye sync --pyproject benchmarks/pyproject.toml
rye run --pyproject benchmarks/pyproject.toml python benchmarks/generate_audio.py
./build-bench/bin/sonare_bench \
benchmarks/fixtures/bench_73s_44100.wav \
benchmarks/results_cpp.json
rye run --pyproject benchmarks/pyproject.toml python benchmarks/run_bench.py統合された benchmarks/results.json には C++ 計測の libsonare 値と librosa 値、そして bpm-detector が PATH にあれば bpm-detector のフルパイプライン時間も含まれます。
libsonare を Python から呼ぶ場合
上記の数値は libsonare のネイティブ C++ 性能です。個別の特徴量関数を Python バインディング経由で呼ぶ場合 (例: libsonare.stft(samples, sr))、各呼び出しでサンプルバッファが FFI 境界を跨いでマーシャリングされ、軽量な特徴量ではこれが実行時間を支配します。フルパイプラインの analyze() は影響を受けません — エンドツーエンドで C++ 内で動き、小さな結果構造体のみが境界を越えます。
備考
- 数値はハードウェア依存。ここでは Apple M5 Max。相対差はマシン間で安定しています。
- 合成テスト音声 (決定論的なコード進行 + 打楽器バースト) はリポジトリにコミットせずローカルで生成します。
- WASM ビルドはシングルスレッドです。HPSS と pYIN の倍率は縮みますが、パイプラインレベルの勝ち筋は残ります。