Schemeで信号処理

先日、1日でCODECを書けるか(Schemeで)というネタをやって、VorbisとSBC*1を実装してみた。細かい報告は後で(修論に)書くけど、結局実時間の50倍とか掛かるのでリアルタイムデコードは出来なかった。
パフォーマンス劣化の基本的な要因はバッファを共有できずにメモリを大量に確保することにあるように観測された。つまり、プロファイラを使うと殆どBoehm GCの中で時間を使っているように見える。
SBCのデコーダのうち、"波形の合成"はこんな感じになる。

たった130行! 実は、音声CODECのデコーダを実装するにあたっては、面倒な仕事の殆どはエントロピ圧縮のデコードとパケットのパースに有って、音声圧縮のコアであるところの周波数変換とか音声符号化はそれほど面倒でも無い。
このコードは非常に大量のメモリを"使い捨て"にしている。ここで実装しているSBCは8バンドなので、8サンプルをデコードするたびに中間バッファを全部mallocしてGCで回収させていることをイメージすれば良い。
以前のAO Benchの一件のように( http://practical-scheme.net/wiliki/wiliki.cgi?Gauche%3AAOBench )、プログラムを副作用バリバリに書き換えてバッファの確保を減らすということは考えられる。
↑のコードでも長いベクタは毎回確保するのではなく、副作用を伴うvector-mul!を定義して使っている。アロケーションを避けるようにプログラムを書き換えるというのは、人間がメモリを管理していることになるのでなるべく避けたいが。。
個人的には、C言語とシェーダ言語の関係のように、この手のtightなループのためにも専用の言語があったほうが良いと思っていて、DSP処理のための(静的型付けされた)合成処理記述言語を装備したい。
必要なのは :

  • ベクタ、行列の演算が組み込みとなっていること - Schemeは一般にBignumとか複素数を扱えるけど、この手の領域ではより複雑な数を処理したいことが多い。
  • SIMD演算に必要なバッファを確保できること - アラインメントなどに関して多少の制限がある。
  • 並列性への配慮 - SIMDのような短い並列性はLLVMのようなコンパイラによって自動的に抽出できるので、どちらかというとイベントハンドリングなどのThread Level Parallelismへの配慮が必要。

今はmoshにサブ言語を追加するための逃げ道を掘っている最中で、言語レベルの改造をしてもちゃんとVM本体は本流に追従できるような対策をしている。
僕個人は、moshを"良いグルー言語"(R6RS)と"1時間程度で習得できる高速で小規模な専門言語"(パケット処理とかDSP)の組み合わせにするべきだと考えている。要するに、Schemeのセマンティクスを保ったままユーザの要求する速度で動かすのは容易なことでは無いので、異なるセマンティクスを持つ言語を複数組み合わせる良い方法を考えるべき。
世間的には、LispとかScheme言語の良い特徴としてマルチパラダイムであることが挙げられているが、実用性の評価は難しい。
もちろん、Scheme処理系自身が高速化することに越したことは無いし、Uniform Vectorsをサポートするべきとか、floatはスタックにアロケーションできるようにするべきとか色々とあるけれども、その辺は既にGaucheがやっているので置いておく。
もうコンピューティングの階層はimplicitでない。
大昔はメモリアクセスノーウエイトのような用語があり、計算機から参照できるメモリはすべてフラットだった。これが次第に(キャッシュなり何なりの仕組みで)階層化され、今やコンピューティングの階層はCPUとGPUの組み合わせとか、ネットワーク接続された計算機のようなexplicitな階層化が当然になってしまっている。
このとき、言語処理系を考えるときの態度は2つ考えられる。

  1. 1つの階層がLispを収めるのに十分に広がるまで待つ
  2. 言語そのものを階層化する

個人的には後者を取るべきだと考えている。

*1:Bluetoothヘッドセットで使われる非可逆音声圧縮で、MPEG Audio Layer IIに似た(しかし簡略化された)サブバンド符号化。