【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

VBAでJITからコールバック

VBAJITから文字出力するため、Debug.Printのラッパーを定義してコールバックさせてみました。ラッパーのアドレスを引数で渡しています。

簡単に見えますが、結構ハマりました。

  • pushでespがずれたのを忘れて[esp+8]で引数を取ろうとした。
  • PutCharの引数にByValを付け忘れた。ポインタ渡しと認識されるため、[65]にアクセスしようとして落ちた。

Brainf*ckをJITで動かすのに使えないかと思い実験してみました。

VBAでJIT

VBAJITをしてみました。

  • 生成したマシン語を直接呼び出すことができないため、EnumWindows()のコールバックとして呼び出しています。
  • コールバックには1つだけ引数を指定できます。配列のポインタを渡して、その配列で引数と戻り値をやり取りしています。
  • コールバックから0を返すことで、列挙処理を中止してすぐに終了しています。

対話的にAPIで遊べるのは面白いです。しかしちょっと間違えるとExcelごと落ちてしまうため、かなり危険な遊びです。もし対話的にネイティブを取り扱う環境を作るなら、他のプロセスをデバッグするのが良いかも、というようなことを妄想しました。

それにしてもBASICからマシン語を呼び出すなんて、なんて懐かしいことをやっているのでしょう!

コールバックで文字列を受け取る

DLLの関数からVBAにコールバックさせて、ANSIUnicodeの文字列を受け取ってみました。

引数を手動でマーシャリングしています。ANSI文字列はByte配列に入れてStrConv()で変換、Unicode文字列はStrPtr()で取得したバッファにコピーしています。

ちなみに文字列が介在しなければもっと簡単です。比較としてEnumWindows()の例を挙げておきます。

VBAからWin32APIを操作する機会が少なかったため、VBAでもサンクのサポートがあるのに今更ながら気付きました。

色々な言語でDeflate

以前、Deflateの圧縮アルゴリズムを変えながらヒッパルコス星表の圧縮時間を計測しました。

最低圧縮率の方式はVBAへの移植を考えて実装しました。そこで今回、実際にVBAに移植しました。結論から言うとインタプリタ方式のVBAは、JIT方式のF#の25倍ほど遅かったです。

今回計測に使用したコードは以下にあります。

チューニング

まだ改善の余地があると感じたため、以下の手順でチューニングを試みました。

  1. チューニング前
  2. CRC削除
  3. 他の方式を削除
  4. ハッシュの読み出しバグを修正して圧縮率を向上
  5. バッファ移動時の再ハッシュを抑制
  6. 長さ・距離をテーブル化
  7. WriteBitの引数をboolに変更
  8. WriteBitsをテーブルで高速化

処理速度は倍程度に向上しています。(単位:秒)

f:id:n7shi:20120418005724p:plain

他言語移植

以下の言語に移植しました。

処理速度を計測しました。Visual Studio系は2010のExpress Editionです。g++のReleaseは-O3、Debugは-O0です。比較対象として.NET Framework標準のDeflateStreamも計測しました。(単位:秒)

f:id:n7shi:20120418011625p:plain

.NET言語同士だとあまり違いはありませんでしたが、ネイティブのC++は速かったです。Andromedaという独自言語もネイティブではあるのですが、最適化がまったくない上、出力するコードの効率も悪く、最適化しないC++の3倍近くも遅いという結果になりました。