Windows の Vim で system() を高速化してみた

WindowsVim では system() が遅いと言われている(?)んですがそれを vimproc を使用して高速化してみました。
まだ簡易版なんですが system() と比べてだいたい 3〜15倍ほど早くなりました。

[ソース]

let s:cmd = {}

function! s:cmd.open()
    let cmd = "cmd.exe"
    let self.vimproc = vimproc#popen3(cmd)
    while 1
        let output = self.vimproc.stdout.read()
        if output =~ '.\+>$'
            break
        endif
    endwhile
endfunction

function! s:cmd.close()
    call self.vimproc.stdout.close()
    call self.vimproc.stderr.close()
    call self.vimproc.waitpid()
endfunction

function! s:cmd.system(cmd)
    let result = ""
    call self.vimproc.stdin.write('(cd '. shellescape(getcwd()) .' & ' . a:cmd.")\n")
    while 1
        let output = self.vimproc.stdout.read()
        if empty(result) && empty(output)
            continue
        endif
        if output =~ '.\+>$'
            let result .= output
            break
        endif
        let result .= output
    endwhile
    let result = substitute(result, "\r\n", "\n", "g")
    return join(split(result, "\n")[1:-2], "\n")
endfunction


call s:cmd.open()


echo s:cmd.system("ls")
echo s:cmd.system("echo %date%")


call s:cmd.close()

[出力]

foo.txt
hoge.vim
main.vim
test.vim

2013/06/11


これ、何をやっているのかというと cmd.exe を外部プロセスとして常駐させて置いてそのプロセスに対して ls や echo といったコマンドを投げつけて実行させていま。
こうすることで cmd.exe を起動させることなくコマンドを実行することが出来るので高速化を図ることが出来ます。
(これに関する詳しい説明は VAC で ujihisa さんが解説してくれるはず…!
で、実際に実行時間を比較するとこんな感じです。

[比較コード]

command! -bar TimerStart let start_time = reltime()
command! -bar TimerEnd   echom reltimestr(reltime(start_time)) | unlet start_time


function! s:test1(num, cmd)
    echo "cmd.exe"
    call s:cmd.open()
    TimerStart
    for i in range(a:num)
        call s:cmd.system(a:cmd)
    endfor
    TimerEnd
    call s:cmd.close()
endfunction


function! s:test2(num, cmd)
    echo "system()"
    TimerStart
    for i in range(a:num)
        call system(a:cmd)
    endfor
    TimerEnd
endfunction


function! s:test3(num, cmd)
    echo "vimproc#system()"
    TimerStart
    for i in range(a:num)
        call vimproc#system(a:cmd)
    endfor
    TimerEnd
endfunction


function! Test(...)
    let cmd = get(a:, 1, "ls")
    let num = get(a:, 2, 10)
    echo "------------------"
    echo "== '" . cmd . "' x " . num . " =="
    call s:test1(num, cmd)
    call s:test2(num, cmd)
    call s:test3(num, cmd)
    echo ""
endfunction
call Test("dir", 5)
call Test("path", 5)
call Test("echo %date%", 5)
call Test("attrib main.vim", 5)

[出力]

------------------
== 'dir' x 5 ==
cmd.exe
  0.046523
system()
  0.613175
vimproc#system()
  0.390333

------------------
== 'path' x 5 ==
cmd.exe
  0.039159
system()
  0.490228
vimproc#system()
  0.357809

------------------
== 'echo %date%' x 5 ==
cmd.exe
  0.030154
system()
  0.502048
vimproc#system()
  0.349304

------------------
== 'attrib main.vim' x 5 ==
cmd.exe
  0.252042
system()
  0.697622
vimproc#system()
  1.131706


こんな感じ。
コード自体はまだ簡易版なんですがだいぶ早くなっていますね。
実際に使用する場合はもうちょっと工夫(タイムアウトとか)する必要がありそう。
あとコマンドの実行が終了した時の判定はどうするのがいいのだろうか。