パスワードを忘れた? アカウント作成
9993565 journal
ソフトウェア

dodaの日記: vimでの縦分割時のスクロール速度改善

日記 by doda

vim や tmux などで縦分割(*1)を行うと「遅い」とか「がたがたする」などと言われる事がよくあります。
これは、端末エミュレータで基準とされる vt100 や vt220 の仕様が関係しています。

まず、ウィンドウを分割しない状態でのスクロール方法を考えてみます。
vim でスクロールする場合、端末の能力によって幾つかの方法を使い分けたりするのですが、
一番単純な、「最下行にカーソルを移動して改行する」という方法を考えてみます。
今回は端末のサイズを 80x24 として考えます。(以降の例もすべて80x24)
一番単純なスクロール方法を考えると

  1. 最下行にカーソルを移動する -- 7バイト
  2. 改行(CR+LF)を出力する -- 2バイト
  3. カーソルを下から2行目に移動する -- 7バイト
  4. 新しい行の内容を出力する -- 行の内容によるので、とりあえず X バイトとしておく
  5. カーソルを元の位置に移動する -- 6~8バイト

と、だいたい22~24+Xバイトくらいの出力になります。
実際にはもう少し最適化がかかったり、TERMによっては後述のスクロールリージョンが使われたりするので値が変わりますが、大体の目安になります。

次に split コマンドで横分割した場合について考えてみます。
80x24 で split すると、1~11行目が上ウィンドウ、13~22行目が下ウィンドウというように分割されます。
この状況で上ウィンドウをスクロールさせる為に単純にスクロールすると下ウィンドウとして表示した部分まで移動してしまいます。
幸い vt100 にはスクロールする範囲を指定する機能(スクロールリージョン/上下スクロールマージン)があるので、TERM が vt100 や xterm 等の時には vim はこの機能を使います。
例えば上ウィンドウで1行スクロールする場合、

  1. スクロール範囲を1~11行目にする -- 7バイト
  2. カーソルを11行目に移動する -- 7バイト
  3. 改行(CR+LF)を出力する -- 2バイト
  4. スクロール範囲を画面全体(1~24行目)にする -- 7バイト
  5. カーソルを11行目に移動する -- 7バイト
  6. 新しい行の内容を出力する -- X バイト
  7. カーソルを元の位置に移動する -- 6~8バイト

と、だいたい36~38+Xバイトくらいの出力になります。単純に改行してスクロールする場合と比べて14バイト程度の増加ですね。
また、前述したように TERM が vt100, xterm 等の時は非分割時もスクロールリージョンを使ってスクロールするので、実際の増加量は 0 となります。

そして問題の縦分割です。
80x24 で vsplit すると、1~40桁目までが左ウィンドウ、42~80桁目までが右ウィンドウというように分割されます。
縦分割でも単純にスクロールすると関係ないウィンドウの表示位置がずれてしまいますが、vt100 のスクロールリージョンは行単位で指定するため、縦分割では使用できません。
そこでどのようにするかというと、スクロール対象のウィンドウ全体を1行スクロールした状態に書き換えるという事が行われます。

  1. カーソルを1行目1桁目に移動する -- 6バイト
  2. 1行目の内容をスクロールした状態に書き換える -- 40バイト
  3. カーソルを2行目1桁目に移動する -- 6バイト
  4. 2行目の内容をスクロールした状態に書き換える -- 40バイト
  5. カーソルを3行目1桁目に移動する -- 6バイト
  6. 3行目の内容をスクロールした状態に書き換える -- 40バイト
  7. ~略~
  8. カーソルを21行目1桁目に移動する -- 7バイト
  9. 21行目の内容をスクロールした状態に書き換える -- 40バイト
  10. カーソルを22行目1桁目に移動する -- 7バイト
  11. 22行目の内容を空白で埋める -- 40バイト
  12. カーソルを22行目1桁目に移動する -- 7バイト
  13. 新しい行の内容を出力する -- X バイト
  14. カーソルを元の位置に戻す -- 6~8バイト

(40 + 6) * 9 + (40 + 7) * 13 + 7 + 6(~8) + X = 1038~1040+Xバイトと、通常のスクロールに比べて出力が非常に大きくなりました。
カーソル移動に関しては最適化がかかって、例えば左端のウィンドウの場合は CR+LF になったりしますが、画面書き換え部分が大きいので大勢には影響がないでしょう。
この計算では端末のウィンドウサイズを 80x24 と今となっては小さめのサイズで計算していますが、これを例えば自分が普段よく使う 120x84 で計算すると、さらに 4000 バイト程増えます。
また Syntax Hilighting を使っている場合は強調や色付けの出力が増えるのでさらに差が広がります。
縦分割時にスクロールが遅くなるのは、この出力量の違いが大きく影響します。

この問題に対処方法がないのかというと、実はあります。
DEC の vt400 シリーズでは、それまでの行位置でのスクロール範囲の指定の他に、桁位置でのスクロール範囲(左右スクロールマージン)指定が出来るようになりました。
多くの端末エミュレータは vt100 や vt220 の機能を基準に作られていますが、最近では vt400 シリーズ以降の機能をサポートする端末エミュレータも出てきました。
例えば xterm や mlterm, RLogin, tanasinn, pangoterm などが左右スクロールマージンに対応しており、Tera Term でも先日出た 4.79 から対応しています。
これらの端末エミュレータを使っている場合、.vimrc に以下の設定を追加する事によって、縦分割時に左右スクロールマージンを利用してスクロールするようにできます。

let &t_ti .= "\e[?6;69h"
let &t_te .= "\e7\e[?6;69l\e8"
let &t_CV = "\e[%i%p1%d;%p2%ds"
let &t_CS = "y"

この設定を行った状態でスクロールする場合、

  1. スクロール範囲を1~22行目にする -- 7バイト
  2. スクロール範囲を1~40桁目にする -- 7バイト
  3. カーソルを22行目1桁目に移動する -- 7バイト
  4. 改行(CR+LF)を出力する -- 2バイト
  5. スクロール範囲を1~24行目にする -- 7バイト
  6. スクロール範囲を1~80桁目にする -- 7バイト
  7. カーソルを22行目1桁目に移動する -- 7バイト
  8. 新しい行の内容を出力する -- X バイト
  9. カーソルを元の位置に戻す -- 6~8バイト

と、50~52+Xバイトの出力になります。横分割時と比べると14バイト増えていますが、左右スクロールマージンを使わない状態での縦分割から1000バイト近く出力が減っています。
この差は結構大きく、特に途中の回線が遅い時は影響が顕著にでます。
Tera Term での実装中に、シリアル9600bps接続で左右スクロールマージンの利用の有無がどのくらい影響するかを動画に撮ったのですが、これを見ると分かるように大きな効果が出ています。

注意点としては、左右スクロールマージンに非対応の端末でこの設定を行うと、縦分割時のスクロールが正しく表示されなくなります。
この非対応の端末というのには、PuTTY や Gnome Terminal 等の他に GNU Screen や tmux 等のターミナルマルチプレクサも含まれます。
# screen や tmux が非対応なのが地味に痛い
なので使える人は限られますが、対応端末を中心に使っている&vimで縦分割を利用する人は試してみてはどうでしょうか。

*1:
分割線が垂直に入り、ウィンドウが左右に並ぶ状態。
この状態にするのに、vim は vsplit (vertical split) コマンドを使うが、
tmux は split-window -h (horizontal) と縦横が逆になっている。
分割線の入り方を基準にするのか、ウィンドウの並び方を基準にするかの違いだけれど、わかりづらい。

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
typodupeerror

物事のやり方は一つではない -- Perlな人

読み込み中...