Vim scriptを取り巻く問題など存在しない (領土問題風に)

現在模索中のVim scriptの高速化手法について解説しときます。

Vim scriptを取り巻く問題について というエントリへの反論というか返答というか補足です。このエントリは極端に書くとこう言っています。

Luaトランスレータなんてできっこねーんだから無駄なことやめろよバーカバーカ

キャッチーに要約してますがコレ一行で済むでしょう。

ただ正直なにもわかっちゃいないで書かれたようなので、足りてないところを書いておきます。

Vim scriptの高速化方法の比較

Vim script を高速化する方法は、現時点で2つ提案されています。

あのエントリでは前者は実現可能であるが後者は不可能であると述べていますが、私の見かたは違っていて「どちらも実現可能である」です。ただしそこに至るステップ、歩幅の大きさと予想される最大効果がぜんぜん違います。

歩幅の大きさ

歩幅の大きさとは、マイルストーンと言い換えてよいでしょう。

バイトコード化は全部をいっぺんに実施ないと効果が確認できません。これを乱暴に言うと 2万4千行ある eval.c を全部リファクタリングし、解釈部と実行部の2つにわけなおす作業に相当します。もちろん可能な作業ではありますが、はてコレを仕上げるのにどれだけの時間がかかることやら…想像したくもありません。まぁ、ここにあげた数字は極論ですが、最初の効果を見るためには相当量の作業が必要になることに変わりはありません。

対してトランスレータではもとより全部をトランスレートするつもりはありません。関数単位です。なので、できる関数だけトランスレートし、できないものはVimスクリプトのまま実行します。しかも最大のネックだと考えていたVim scriptのパース処理がもうVim scriptで書かれています(ynkdirさんスゴイ!)。ゆえに最初の効果を確認するのに比較的時間がかかりません。

つまりバイトコード化の最初のマイルストーンはどうやっても遠くなってしまうのに対し、トランスレータのほうは非常に近くに設定できる&しているのです。

最大効果

この2つの高速化手法でそれぞれどのくらい速くなりうるか、その予測を述べておきましょう。すでにこれは if_python (Python) と if_lua (Lua) を使って実験済み です。

まずバイトコード化については似たような方式である Python が Vim script よりも 約30倍も速くなっていたことから同等の30倍程度まではいけると推測しています(Pythonを開発するのに近い手間ですがw)。また JIT を使わない Lua は 100倍ほどの性能を示したことから、バイトコード化を相当に頑張れば最大100倍までは速くできることになりますが…私はそこまでは頑張れないだろうと考えています。

ただし Lua については LuaJIT を使った場合 Vim script よりも1000倍も速くなりました。これはインパクトのある数字です。トランスレータをうまく作れば、労せずして1000倍も速くなるのです。バイトコード化でこの域に達するのは、JITコンパイラを書く労力に匹敵しますから…私はやろうとは考えません。

つまり推測される最大効果が、トランスレータ方式のほうがはるかに高いのです。LuaJITによる1000倍の速度を目の当たりにしなければ、私もトランスレータをやろうとは考えませんでした。

まとめ

以上が バイトコード化方式と トランスレータ方式に対する私の展望であり、これらの比較からトランスレータ方式ならやる価値があるだろうと判断したわけです。

とは言えこれだけではなんですから、トランスレータの動作イメージをフィボナッチ数の計算という簡単な例で示しておきましょう。

トランスレータの動作イメージ

トランスレート前の関数

簡単なフィボナッチ数を求める関数です。

function! Fib(n)
  if a:n < 2
    return a:n
  else
    return Fib(a:n - 1) + Fib(a:n - 2)
  endif
endfunction

トランスレート後の関数

変換後はLuaの関数とそれを呼び出すVim scriptの関数にわかれます。

Lua部分

function Fib(n)
  if n < 2
    return n
  else
    return Fib(n - 1) + Fib(n - 2)
  end
end

Vim script部分

function! Fib(a:n)
  let r = [ a:n ]
  lua r = vim.eval('r') ; r[0] = Fib(r[0])
  return r[0]
endfunction

この書き換えなら難しくはないと思えるのではないでしょうか? 簡単なものなら手動でやってもできるレベルであり、それを自動化しようというのがトランスレータ計画です。

ここでVim側とLua側に同名の関数を持つため、トランスレート済みの関数はLua側にとってオーバーヘッド無しで呼び出せることに留意してください。なおVim側の関数の書き方はもうちょっと工夫がいると思っています。

トランスレートのタイミング

トランスレートはVim scriptをVimに読み込んだあとで行います。予め変換するわけではありません。擬似コードで書くとこんなかんじです。

function! GetFuncDef(name)
  redir => str
  silent execute "function " . a:name
  redir END
  return str
endfunction

function! RedefineVimFunc(name, funcstr)
  call eval("function! " . a:name . "\n" . a:funcstr . "\n" . "endfunc")
endfunction

function! Translate(name)
  let raw = GetFuncDef(a:name)
  let translated = TranslateToLuaAndVim(raw)
  call DefineLuaFunc(a:name, translated.lua)
  call RedefineVimFunc(a:name, translated.vim)
endfunction

Translate 関数に関数名を指定すると、その関数を変換&再定義してくれるという寸法です。なお TranslateToLuaAndVim 関数が vim-vimlparser を使ってがんばるところですね。


以上「Luaトランスレータなんてできっこねーんだから無駄なことやめろよバーカバーカ」に対する反論と、現時点のトランスレータ計画の詳細の解説でした。