読者です 読者をやめる 読者になる 読者になる

Vim - 文字列連結で再代入する場合はjoinを使う方が早そう

vim Vim script

概要

自作プラグインのコードに以下のようなTODOが残っていました。

  " TODO リストじゃなくて普通に文字列連結にする(テスト書く)
  for line in s:do_external_parse(a:lines)
    let escaped = 適当な処理
    call add(converted_lines, escaped)
  endfor
  return join(converted_lines, "\\n")

ここでは文字列の配列をぐるぐる回して処理を行い、最終的に改行区切りの文字列としています。
joinで書いたものの文字列連結の方が早いのではと思って上記のようなTODOコメントを残していました。

今日このTODOを消化しようと思い、本当に文字列連結の方が早いのか計測しました。

先に結論

  • 10回程度の連結なら大差ない
  • 3000回ともなると圧倒的にjoinが早い
    • joinの方が実装もシンプル

環境

  • Mac OS X 10.9.5
  • MacVim Custom Version 7.4 (KaoriYa 20150707)

ベンチマーク

h1mesuke/vim-benchmarkを使用します。
テストデータは1行85文字が3000行です。(3000回連結される)

let s:lines = []
for s:n in range(3000)
  call add(s:lines, "    * 注意:拡張子が`.md`の場合は`markdown`ではなく`modula2`として認識されてしまいます。その場合は以下の設定を.vimrcに記述してください")
endfor

let s:bm = benchmark#new("concat string")

function! s:bm.operator_for()
  let result = ""
  let delim  = ""
  for line in s:lines
    let result = result . delim . line
    let delim  = "\\n"
  endfor
endfunction

function! s:bm.operator_while()
  let result = ""
  let delim  = ""
  let n = 0
  let length = len(s:lines)
  while n < length
    let result = result . delim . s:lines[n]
    let delim  = "\\n"
    let n += 1
  endwhile
endfunction

function! s:bm.join()
  let tmp = []
  for line in s:lines
    call add(tmp, line)
  endfor
  let result = join(tmp, "\\n")
endfunction

call s:bm.run(3)

結果は以下の通り。

Benchmark: concat string

Trial #1
  join           : 0.008425
  operator_for   : 0.348224
  operator_while : 0.356583

Trial #2
  join           : 0.010402
  operator_for   : 0.353079
  operator_while : 0.359111

Trial #3
  join           : 0.008463
  operator_while : 0.347276
  operator_for   : 0.359592

joinが圧倒的に早かった。
Java+連結すると遅いように、Vim Scriptでも気をつけた方がいいのかな。

ちなみに10回程度の連結なら大差ない。

Benchmark: concat string

Trial #1
  join           : 0.000046
  operator_for   : 0.000063
  operator_while : 0.000076

Trial #2
  join           : 0.000051
  operator_for   : 0.000080
  operator_while : 0.000083

Trial #3
  join           : 0.000048
  operator_for   : 0.000066
  operator_while : 0.000082

おわりに

「推測するな、計測せよ」の大事さ。