Visual Studio 全文検索拡張
はじめに
数週間でとりあえず検索できるだけのVisual Studio用の全文検索拡張ができました. Entrian Source SearchやFastFindといった既存の拡張があるのは知っています.
早期リリースを目標に不具合検証やユーザビリティは後回しです.
- 高速な全文検索をVisual Studio上で行いたい
- ソリューションの情報からインデックスを作るファイルを決定してほしい
- 上に挙げた拡張を使用したことがありませんが, インデックスをつけるディレクトリを指定するのでしょうか
Visual Studio Extensibility
昔と比べればずいぶんとドキュメントが充実しました, Visual Studio Extensibility. ただそれでもドキュメント化されていない仕様に悩まされます, とくにソリューションのアイテムについては扱いが全くわかりません.
たとえばProjectItemクラスのタイプについては, EnvDTE.Constantsで種類を判別できますが, vsProjectItemKindSolutionItemsが実際はどれに対応してどういう動作をするのかわかりません.
ProjectItemクラスはなんでもクラスなので, サポートしていない機能にアクセスすると例外が送出されるかnullが返ります. わたしはCollectionメンバは子オブジェクトを所有していると勘違いしましたが, これはProjectItem自身も含みます.
Projectについても情報がないので, 有志の情報をもとにProjectTypes.csを作りました.
Lucene.Net
研究段階ではgroongaも試してみたのですが, ドキュメントが皆無に等しいのとVisual Studio拡張にC/C++のDLLを組み込むのは少し手間なので断念しました.
サブDB選定
ファイルの更新時刻とインデックスの更新時刻の比較に当初Luceneを利用していましたが, あまりにも遅いため, 別 DBに保存することにしました.
C/C++のDLLが面倒くさい問題のため, LevelDB, RocketDBは候補から外しました. Microsoft FARSTERがよさそうですが, Lucene.Netと依存ライブラリで問題が出ました, これで一日消費しました.
単純なkey-valueでいいのですが, 仕方がないのでLiteDBを選択しました.
索引付け
索引付けは次のようにしました.
- 検索対象をソリューションファイルから収集
- ソリューションのアイテムをロックできないので, 処理中に変更操作があると何が起きるかわかりません
- 最後に調べたファイルの更新時刻と比較して, 索引付け候補を絞り込み
- 別にDBを用意しました
- Luceneの索引を行ごとに更新
未解決の問題はプロジェクトが削除された場合にどうするかです.
パフォーマンス
環境は次のとおり,
| CPU | メモリ |
|---|---|
| Core i7-8700 | 32 GB |
全てデバッグビルドでVisual Studioのデバッガに繋いだ状態です.
インデックス作成
手元にあった, おそらくローカルひとつで, これより巨大なプロジェクトはなかなかない, Unreal Engine 4.27.2で試しました.
| 処理 | 時間(ミリ秒) | 備考 |
|---|---|---|
| インデックス作成候補のファイル収集 | 380 | 34133ファイル |
| 更新日時をチェックしてカリング | 1812 | |
| インデックス作成 | 422517 | 34120ファイル, 5373439行 |
完全に空のファイルはインデックス作成に含まれないので, 少なくなっています.
| ファイル | サイズ |
|---|---|
| Luceneのインデックスファイル群 | 約343MB |
| LiteDBのDB | 約11MB |
Unreal Engineが無駄に消費するストレージサイズに比べればゴミのようなサイズです.
検索
こちらEntrian Source Searchで, r.invalidateCachedShaderが約3秒で検索できるということです.
r.invalidateCachedShaderで検索するとrにヒットしたり, invalidateCachedShaderだとヒットしなかったり, インデックスの付け方と検索クエリが悪いのですが, それでも最大1000件の検索で300ミリ秒をきるぐらいなので, ここから使い勝手を磨いていきたいところです.
まとめ
とりあえず動いて早期リリース, というより, 事情により使いにくくても自分のために実践投入したいので, 不具合は後回しです.
初回起動時は完全に固まってしまうのは, なんなのですかね. awaitの仕様を勘違いしてるのかな.