MarkdownBuffer の実行時間の計測とパフォーマンスチューニングの余地

昨日作った mdbuf が 100000文字を超える場合、遅いときいたので、色々試してみた。

手元で18万のテキストを用意して編集してみた。それなりに重いが、それでもIMEを完全にブロックしてしまうほどでもない。とはいえイライラはする。

innerHTML で挿入にかかる時間を計測してみた結果、自分の手元では markdown 10000文字あたり、1.6ms の遅延。というわけで、体験を損ねない限界は 100000文字辺りが限界だと思う。とはいえ自分は最新モデルのMackbook Pro の中位ぐらいのモデルなので、平均的にはその半分の5万文字ぐらいだろうか。

ただ、画像がある場合には入力の度にリクエストが走ってるような気がするので、それのリサイズも合わさって、あまり良くない気がする。あとでアクセスがキャッシュに閉じてるか確認しておく。

細かい工夫として、IMEの変換中はプレビューの更新を止めてる。IME 変換中の不整合な状態を見たい人はいないと思う。日本語の入力はこれでだいぶ体験よくサボれる。

経験上、原稿書いてるときは多くても1万から2万ぐらいで別ファイル(章)に切ってるし、100000字はさすがにサポート外と言ってしまったほうが良さそう。基本的には20000文字ぐらいまでではないか。

でも将来的には大容量テキストをサポートしたいし、そのための実験を色々やった。

機能追加

  • textarea 上で Ctrl 押しながらマウスホイールでプレビュー側を更新
  • Tab/Shift+Tab でインデント
  • プレビューが隠れている場合はレンダリングしない

スクロールシンクを実装しない代わりにマウスホイールで移動できるようにしたらめっちゃ便利でこれでいいじゃんとなった。

作図ツールが欲しくて、mermaid.js の組み込みやってみようとしたら remark-mermaid が fs 使ってて node 環境でしか動かないコードだったので、その部分を剥がして直接使えるか確認しに mermaid の SVG レンダラーのコードを読みにいったら、あまりに汚いコードで使う気が失せた。自分でそれっぽいの書くかも。

大規模な markdown 対応で、アウトラインへのジャンプはほしいかもしれない。そうしたら100000超えた際のサポートにも意義が出てくる。

textlint 組み込みをやろうか迷ったが、何入れても不満でそうで、汎用的なルールが思いつかなかったので後回し。

裏側で複雑な実装が増えてきたので、さっくりReact化しておいた。

実験

innerHTML への代入ではなく、差分レンダリングの仕組みを考えていて、手元で remark-react(preact) => worker-dom => MainThread での MutationRecord 適用というフローを試してみた。worker-dom 側での仮想DOM生成はうまくいったが、逆にMainThread から Worker 側にデータを投げる仕組みが見当たらなくて、無理矢理パッチ当ててみたが、よくわからなかった。もうちょいでできるはずなので、後一日ぐらい調べる。

これがうまくいけば、更新された DOM へのスクロールを作れば、スクロールシンクも実装できる。

React でやってない理由として、仮想DOMは実DOMと色々紐付いているせいで Worker で生成/破棄が出来ない。これが理由で worker-dom と、その上で動くことはわかっている preact で調べていた。MutationRecord に対し react-reconciler で自前 renderer を実装するという手がなくはないが、実装量が半端ないのでやりたくない。

これらがうまく言っても、裏のスレッドを専有してる Markdownコンパイル時間は何も解決しない。本当に大きなファイルの場合、適当な位置でテキストを分割して8スレッド並列でMapReduceするみたいなことは思いついたが、実装のだるさの割に報われなさそうで、まだやる気があんまない。

Rust で書いて wasm バイナリで高速化するという手もあるが、remark 相当の拡張性、便利さを再現できなさそう。面白そうなテーマではある。