Phase Vocoderの最適化、といってもSSE向けのみですが、おおかた終わりました。
gcc 3.4.5 (mingw-special) で C++版だと CPU 使用率で15%ほどだった物がSSE最適化で7%ほどになります。おおむね2倍ほど高速な様子。最終的にはアルゴリズムの改良により2~4%ほどになっています
gcc のベクトル演算機能(自動ベクトル化ではない)を使ってみましたが、なかなかイイですね(iccやVC++も持ってるはず)。a = b*c+d; とか、普通の式で指定しても、ちゃんとmulps, addps, movapsなどのSSE命令を生成してくれます。
ちょっと落とし穴だったのが、スタックのアラインメントの調整です。
gccではなんだったかのヘッダファイルをインクルードすると__m128型がXMMレジスタ(128bitレジスタ)に対応する型として使えるようになりますが、レジスタが足りなくなるとスタック上に置かれることもあります。この場合はスタックにアクセスするためにmovaps命令などが使用されますが、これらの命令はアクセス先のアドレスが128bit境界にアラインメントされてないとそこでクラッシュします。
gccはデフォルトでスタックを128bit境界にアラインメントするようですが、それは「呼び出し元スタックポインタが128bit境界でアラインメントされていれば」という前提がつきます。呼び出し元のスタックポインタがズレていた場合は、呼び出された関数でもずっとズレたままになります。gccだけで完結しているプログラムならばいいのですが、どこでズレるのかというと、他の言語やOS、他のライブラリからコールバックされる場合にズレます。gccのように必ず128bit境界にアラインメントを行ってから次の関数を呼び出すならば良いのですが、他の言語やOS、ライブラリなどでは必ずしもそうはなってません。
なので、どこかでスタックを調整しなければなりません。現時点での開発版のgccにはforce_align_arg_pointerという、スタックのアラインメントを動的に調整する属性があるようですが、今使っている gcc 3.4.5 にも 4.1 にも無いようです。
仕方がないので以下のようなコードを呼び出しの途中に挟んで対処しています。
__attribute__((noinline)) void call_cushion()
{
// calleeがinline展開されるとたぶん困るのでわざと
// この関数を挟む
return callee();
}
__attribute__((noinline)) void call_align()
{
// alloca を呼び出すと、コンパイラは -fomit-frame-pointer を指定されていても
// フレームポインタを作らざるを得なくなる
alloca(1);
// スタックをalign
asm __volatile__ ("andl $-16, %%esp" : : : "%esp" );
// call_cushion を呼ぶ (この場にcalleeがインライン展開されるとたぶん困る)
return call_cushion();
}
これはだいたい以下のようなアセンブリコードになります。
_call_cushion: jmp _callee ; callee にジャンプ _call_align: push ebp mov ebp, esp ; フレームポインタを作成 sub esp, 24 ; これがallocaに相当(ホントは意味無い) and esp, 0fffffff0h ; ここで128bit境界にアラインメント call _call_cushion leave ; フレームを破棄 ret
もっとスマートな方法がないのかしら。
上記のコードはSSEを使う関数ごとに挟まないとならないわけではなく、原理的には、他の言語やOS、ライブラリなどからコールバックされた直後に挟めば良い物です。
吉里吉里3にはすでに実装されているPhase Vocoderですが、■ボイスのn倍速再生をやるために吉里吉里2にバックポートしようかと思っています。
吉里吉里2が今使っているBorland C++ Builder 5(古い)は浮動小数点演算がきわめて不得意で、最適化をほとんど望めません。ただでさえ演算量が半端でないPhase Vocoderですから、実用的にするには、相当高速化しなければなりません。
で、今日あたりは、吉里吉里3で使うことを前提に、実数離散フーリエ変換のSSE化をやっていました。といってもほとんどはOgg Vorbis 高速化プロジェクトから拝借したコードです。これだけの最適化されたコードを公開していらっしゃる作者様に感謝。
ただ、それは吉里吉里3で使用しているgcc用に今のところ書いているもので、gccが吐くオブジェクトファイルはCOFFなので、OMFを期待しているbccのリンカではリンクできません。なんらかの変換をする必要があります。
COFF→OMF変換ツールはどうも入手可能なのが見つからないようなので、gccで吐かせたアセンブリ言語のソースを編集して、それをnasmかなにかでアセンブルして、そのオブジェクトファイルをbccに喰わせるなんて手順になりそうです。結構面倒くさい。
SSEもそうですがSSE使えない環境用にもgccからコードを持ってきたいなぁ。いや、bccの最適化は浮動小数点数に関しては悲惨です。gccのほうがかなりマシ。新しいバージョンはどうなってるか知りませんけど。
これは何かというと、CPUのいろんな所を駆動してCPUを加熱するプログラムです。
単純な無限ループを動作させてもCPU使用率は100%になりますが、これではCPUのごく一部分しか使われません。CPUには他にも浮動小数点演算部分や各種マルチメディア用命令部分などがありますから、それらも駆動しなければ真にCPUを100%使い切ることはできません。
といっても実際にはCPUを100%使い切ることはまずできないのですが、上記のプログラムは「できる限りCPUを全部使い切る」という物です。
実際の所、吉里吉里でも、とくに画像演算部分などではCPUをかなり酷使すると見られるコードがありますが、エンドユーザのパソコンにはCPUの冷却不足が原因で不安定になっていると見受けられる症状がまれにあり、エラーログを見ながらなんども頭をひねった経験があります。
自分のマシンがどれほど安定して動作するかを検査するためにmemtest86やmemtest86+などと併用するといいかもしれません。ちょっと古いですけど。他に似たようなのあるのかな。
あとは寒い冬の暖房用に。
KAGEXのダウンロードの仕方を簡単に書いておきます。
KAGEXは現在、インターネット上の「Subversionリポジトリ」という場所にあります。ここからダウンロードを行う必要があるのですが、ここではTortoiseSVNというソフトウェアを使います。
まず、TortoiseSVNをダウンロードしてインストールしてください。Gentoo Side- TortoiseSVNのインストールと設定 がわかりやすい説明だと思います。
KAGEXをダウンロードしたい場所に空のフォルダを作成してください。名前は何でも良いです。フォルダを作成したら、エクスプローラでそのフォルダを右クリックをして「SVN チェックアウト...」を選択します。
「リポジトリのURL」は https://sv.kikyou.info/svn/kirikiri2/trunk/kag3ex を指定してください。そのほかはデフォルトのままにして「OK」ボタンを押すとチェックアウト(ダウンロード)が始まります。
KAGEXはときどき更新されます。更新情報は吉里吉里開発サイトで確認できます(RSSも利用できます)。更新をしたい場合は、先ほどのフォルダをエクスプローラで右クリックして「SVN 更新」を選択してください。最新版の状態になります。
ちなみに TortoiseSVN でチェックアウトすると、チェックアウトした各ディレクトリに .svn という名前の隠しフォルダが作成されますが、TortoiseSVN が、このフォルダが TortoiseSVN(正確にはSubversion) の管理下にあると認識するために必要ですので、このフォルダの中身を変更したりしないでください。逆に言うと、このフォルダを消すと、そのフォルダはTortoiseSVN の管理下ではなくなります。また、Releaserはこのフォルダは無視します(アーカイブには含みません)から、Releaserを実行する前にこのフォルダをいちいち削除するような必要はありません。
吉里吉里2ですが、LayerクラスにhasImageプロパティが追加されました。
このプロパティを偽にするとレイヤが画像を持たなくなります。レイヤが画像を持たなくなると、typeがltOpaqueの場合は全面が単一色で塗りつぶし、それ以外の場合は完全に透明なレイヤとなります。また、画像を持ちませんから、画像を読み込んだり描画をしたりはできません。
これができて何がうれしいかと言うと、「完全に透明なレイヤや完全に不透明で単一色で塗りつぶされたレイヤを作りたいときにメモリと演算量を節約できる」ということになります。とくに、このレイヤはそれ自身は画像を持っていなくても、画像を持っている子レイヤを持つことはできますから、複数の子レイヤを一つの親レイヤにまとめて置きたいけど、親レイヤは完全に透明でよい、あるいは親レイヤは見えなくて良い、といった用途に対応します。
たとえば、KAGのプライマリレイヤは現在は以下のようになっています(下に行けば行くほど手前に表示される)
+-表-背景 +-裏-背景(非表示) | +-裏前景レイヤ0 | +-裏メッセージレイヤ0 +-表前景レイヤ0 +-表メッセージレイヤ0 +-メッセージ履歴レイヤ +-行クリック待ち記号 +-ページ末クリック待ち記号
これを、たとえばプライマリレイヤにtype=ltOpaque, hasImage=falseのレイヤを置くと(下図では"rootレイヤ")
+-rootレイヤ +-表-背景 | +-裏-背景(非表示) | | +-裏前景レイヤ0 | | +-裏メッセージレイヤ0 | +-表前景レイヤ0 | +-表メッセージレイヤ0 | +-メッセージ履歴レイヤ | +-行クリック待ち記号 | +-ページ末クリック待ち記号 +-ボタンなどのUI
ボタンなどのUIを常に手前に表示することができます。背景レイヤのトランジションとはレイヤツリーが異なるので、トランジションに巻き込まれることもありません。今まででもこれはできたのですが、hasImageが導入される事によってより効率的な処理ができるようになりました。
この場合はrootレイヤは完全に表-背景レイヤに隠されていて見てないので画像は必要ありません。また完全に隠されているので重ねあわせの演算をする必要もありません(実際に吉里吉里2はこういう状況を認識して、重ね合わせ演算をスキップします)。
このプロパティが実装された吉里吉里は近日中に2.27-develとして公開します。2.28 RC1 として公開しました。これ以降に公開されるバイナリにはすべて含まれます
KAGにこの機能を利用した機構を組み込むかどうかという話もあるのですが、これを組み込むと大がかりな変更になりますし、既存のKAGのアプリケーションやプラグインの大半が動作しなくなる可能性があります。KAGEXはごうさんのお話だとこのような構造になるかもしれません。
KAGEXも利用しやすいように整備しないとなぁ。
吉里吉里2の2.28のRC版をもうそろそろ出します。
吉里吉里2の2.27代で追加された機能はそれほど大きな物はないですが、強いてあげれば
といったところでしょうか。
2.28のRC版になってしまうと、(2.28に対しては)新規機能の追加を基本的にはしなくなりますので、2.28安定版に入れた方がいいんじゃないかという機能のご提案をお持ちの方はお早めにお教えください。