特集・集中企画

飽くなき高速化への挑戦! 「EmEditor」はマルチスレッド・SIMD命令・仮想メモリをフルに使って進化

最強テキストエディター「EmEditor」開発者 江村豊氏インタビュー

「EmEditor」

 テキストを入力したり編集したりするツールは、テキストエディターやワードプロセッサー、Webブラウザーのフォーム、スマートフォンなどさまざまなものがある。中でも、PCに慣れたユーザーにとっては、高機能なテキストエディターはなくてはならないツールといえるだろう。

 テキストエディターの中でも「EmEditor」は、1997年の最初の公開以来、速度の速さや機能の豊富さで多くのユーザーに支持されているソフトの1つだ。「窓の杜」編集部でも長年の愛用者が多い

 「EmEditor」の開発者であるEmurasoft, Inc.の江村豊氏は、日本出身の個人開発者で商業的に成功を収めている数少ない一人といえるだろう。江村氏がこだわる「EmEditor」のパフォーマンスについては、動作と起動の速さからCSVやログファイルといった大容量テキストデータの処理のパフォーマンスまで、プロフェッショナルなユーザーから特に高い評価を受けている。なお、江村氏は現在、マイクロソフトの本社などもある米国ワシントン州のレドモンド在住だ。

 その江村氏に、最近の「EmEditor」の開発トピックや高速化で活躍するテクニックの話をお伺いした。インタビューしたのは、2023年2月、「EmEditor」v22.2が最新版の時期。

 「EmEditor」といえば、大容量ファイルへの対応や高速性へのこだわりをイメージする人も多いと思うが、そうしたこだわりの一方、江村氏が最近の機能強化で「特に重要性が高かった」と感じたのは、v22.1で追加したクラッシュレポートの送信機能なのだという。

 そこで今回は、最近のバージョンアップでも続く高速化への飽くなき挑戦や大容量ファイルを余裕で扱える秘密を前編で、高い安定性を維持するための取り組みや、JavaScript対応の改善やGitへの対応。さらには、AIを含めた将来の機能改良についての計画を後編としてお届けしたい。

 前編となる今回は、SIMDやマルチスレッド、メモリマップドファイルといった手法を積極的に使った高速化への飽くなき挑戦と、そのために常にプロファイルを取ってボトルネックを探す日々の取り組みについての話だ。

江村豊氏

SIMD命令からループ内の分岐まで、常にプロファイルして高速化し続ける

――「EmEditor」は速度、特に巨大データを扱うときの処理速度に定評がありますね。「EmEditor」の高速化でやっていることについて教えてください。

[江村氏]高速化にはいろいろなアプローチがあります。たとえば処理をマルチスレッド化しており、効果があります。

 また、CPUのSIMD命令を使っているのも一つです。SSE2、AVX2、AVX-512などですね。現在の「EmEditor」では、対応するCPUごとに最適化したee128.dll、ee256.dll、ee512.dllの3種類のバイナリがあって、CPUの機能に応じて自動的に選ばれるようになっています。おそらくee256.dllを使っている方が多いと思います。

 たとえばSSE2では、128ビットずつメモリのコピーや検索などができるので、1文字ずつ処理するより速くなります。UTF-16の文字だと16ビットずつ処理するので、8文字ずつ処理できるわけです。さらにAVX-256だと256ビットずつ、AVX-512だと512ビットずつになり、順に速くなります。

 ただし、128ビットで処理するSSE2からAVX-256では大いに速くなるのですが、AVX-512は思ったほどは速くないんですね。CPUのスロットリングに引っかかるためだと言われています。Linux(カーネル)ではそのためAVX-512を使っていません。Linuxの生みの親のLinus Torvalds氏もAVX-512を批判していましたし。とはいえ、「EmEditor」では将来的にはもっと速くなるかもと思って対応しています。

――内部的な文字コードをUTF-16からUTF-8にすることで並べ替え(ソート)が高速化したという話もありましたね。

[江村氏]はい。開くファイルの種類にもよりますが、英数字中心のデータなどでは、UTF-16よりもUTF-8の方が、使用するメモリが約半分になります。使用するメモリが少なければ、メモリ割り当てや比較処理の時間が削減できます。

 また、多くのファイルでエンコードとしてUTF-8が標準的になっていて、UTF-16への変換にはコストがかかるので、直接UTF-8で扱ったほうが速くなります。

 UTF-8にしたことは並べ替えが高速化した要因の1つですね。それだけが速くなった理由ではありませんが。

――そのほかの速くなった理由にはどのようなものがあるでしょうか。

[江村氏]さまざまな最適化を行っています。たとえば、ループ中の条件分岐を減らすといったことです。こうしたものを、常に「Visual Studio」のプロファイラーでボトルネックを調べて、最適化しています。

――高速化は、ループ中の分岐の除外のようなミクロなものが中心でしょうか、それともアルゴリズムのようなマクロなものが中心でしょうか?

[江村氏]いろいろあります。アルゴリズムの変更は高速化に大きく効きますね。また、ミクロなものでは、関数をインラインにしたり、文字列比較関数を標準ライブラリのものから自前のSIMDを使うものに変えたりといった改善を加えています。

メモリマップドファイル採用で高速化、ただしWindowsの問題には注意を

[江村氏]高速化の要因にはメモリ管理の変更もあります。「EmEditor」はテキストエディターなので、何Gバイトなど巨大なデータを扱うことがあります。従来はこれを固定サイズのバッファに入れて、入れ替えて使っていました。これをファイルを仮想メモリ空間に直接マップする、メモリマップドファイルというOSの機能によって全部読むようにすると、おさまった場合は非常に速くなります。

 問題はそれにおさまらない場合です。そこで新しいバージョンでは、たとえば10Gバイトは無理だとしても、2Gバイトなどできるだけ大きなメモリマップドファイルを使って、ビューをずらしていって最後まで読むようにしました。それでかなり速くなり、特に並べ替えに大きな効果がありました。

 メモリマップドファイルへの変更は、マルチスレッドでの処理にも影響しています。それまでスレッドごとにバッファを用意していたので、読み込みが重複して遅くなっていました。それをやめて、スレッド共通の1つのメモリマップドファイルの領域に読み込んで、すべてのスレッドがそこで処理するようになって、だいぶ高速化しました。

――メモリマップドファイルへの変更は、いつごろからでしょうか?

[江村氏]実は少しずつ入っているんです。今回のv22.2で、並べ替えなどかなりの機能で対応しました。[変換]メニューにあるような大文字・小文字変換などの各種変換機能も、v22.2でメモリマップドファイルを使った方式に変わっています。重複文字列の削除については、ちょっと前から入っていました。

 メモリマップドファイルのサイズの上限も、いままでは物理メモリのサイズだけを元に決めていました。それに対してv22.2では、[すべてのメモリ サイズを自動的に管理する]という設定を追加して、これが有効になっていれば仮想メモリの量も考慮して、より大きなサイズを扱えるようになりました。この設定はデフォルトで有効になっています。

[カスタマイズ]ダイアログの[高度]画面に[すべてのメモリ サイズを自動的に管理する]の設定が追加された

――ちなみに、大きなサイズのファイルを扱うときには、Windowsの仮想メモリの設定を変えたほうがいいというお話ですね。

[江村氏]はい。Windowsでは、より多くの仮想メモリを使うときに、ページングファイルのサイズを増やします。ただしこのとき、Windowsの仮想メモリの仕組みには問題があって、急に大きく割り当てようとすると、ページングファイルの拡張が追いつかなくてエラーが発生する可能性があります。これは、マイクロソフトでも問題だと認識しているので、将来は改良されるかもしれません。

 ページングファイルのサイズは、Windowsのデフォルトの設定では自動設定になっています。そこで、大きなファイルを扱うのであれば、これを手動で指定してしまうのがおすすめです。仮想メモリの設定で、デフォルトでは「すべてのドライブのページングファイルのサイズを自動的に管理する」にチェックが付いていると思います。これを外して「カスタムサイズ」にして、「初期サイズ」と「最大サイズ」に大きなサイズを指定します。例えば、マイクロソフトの推奨は物理メモリサイズの1.5倍ですが、ストレージの状況が許すなら、さらに大きく「100GB以上」にしてしまってもよいです。

(編集部注:仮想メモリの設定を表示するには、まず「設定」アプリの[システム]-[バージョン情報]画面にある[システムの詳細設定]をクリック。[システムのプロパティ]が開くので、[詳細設定]タブの[パフォーマンス]エリアにある[設定]ボタンを押す。あとは表示された[パフォーマンス オプション]ダイアログを[詳細設定]タブに切り替えて[仮想メモリ]エリアの[変更]ボタンを押せばよい。)

 「100GB」というと、なかなかびっくりしてしまうかと思いますが、私は、(ストレージに余裕がある限り)この設定に変更するデメリットを感じていませんし、実は自分でもそのようにしています。実際、大きなページングファイルを割り当ててないと、「Visual Studio」でビルドするときに問題が出てしまう場合もありますし。

Windowsの仮想メモリの設定

C標準ライブラリの文字列比較関数をSIMDで書き換えて高速化

――さきほど、文字列比較関数を自前のSIMDを使うものに変える話がありました。これはいつからでしょうか。

[江村氏]これは次のバージョンのためのもので、いまベータ版です。C言語に用意されている文字列比較関数のstrcmp()やwcscmp()は、普通に先頭からループで回して1文字ずつ比較しています。これを、SSE4.2で比較する_mm_cmpistrc()関数を使ったものに変えれば、128バイトずつ比較するため、UTF-8なら16文字ずつ比較できます。実際には16倍にはなりませんが、1.5~2倍ぐらいにはなります。

 ただし、難しい問題もあります。SIMDで比較するにはメモリをアラインしていないといけないので、アラインされていればSIMDで、されていなければ他の条件によっては従来の方法で比較するようにしました。その条件分岐を入れても、SIMDを使ったほうが速くなることがわかりました。

――ほかにSIMD命令をどのようなことに使っているのでしょうか?

[江村氏]いろいろな処理で使っています。たとえば、次の改行や文字列を検索するといった処理があります。また、大文字を小文字に変換したり、UTF-16をUTF-8に変換したりといった変換でも使っています。

――新しい文字列比較関数は、そのままC標準の文字列比較関数と差し替えるだけで使えるのでしょうか?

[江村氏]はい、差し替えるだけで使えるようにしています。

 そのほかにもいくつか標準関数を置き換えています。たとえば、memcpy()をマルチスレッドで高速化しています。

マルチスレッドの並列処理を多用、ただしPコアとEコアへの対応は課題

――最初にもマルチスレッド化の話がありましたね。マルチスレッド処理はどのようなところで使っているのでしょうか?

[江村氏]あらゆるところで使っています。たとえば検索するときに、シングルスレッドだと上から順番に処理しますが、マルチスレッドなら複数の行ごとにスレッドを分けて、並列に処理できます。同様に、文字列の変換でも、並べ替えでも、あらゆるところでマルチスレッドを使って並列処理しています。

――memcpy()をマルチスレッド化するというお話でしたが、メモリコピー速度はCPUよりメモリのバス幅の影響が大きいということはないでしょうか?

[江村氏]私も最初そう思ったのですが、やってみたら1.5倍ぐらいに高速化されました。

――それは面白いですね。

[江村氏]ただし、マルチスレッド処理で問題になるのが、第12世代のIntel Coreシリーズから導入された、Pコア(パフォーマンスコア)とEコア(高電力効率コア)という異なる種類のコアからなる構成です。

 私の作ったマルチスレッドのアルゴリズムでは、すべてのスレッドが同じ速度で動作すると仮定しています。そのため、同じだけの処理をそれぞれのコアに与えると、Pコアで処理が終わってもEコアの処理が終わるのを待つことになってしまいます。そこで、速く動作するスレッドはより多くの作業を行うというようなアルゴリズムの見直しを行いたいと考えています。

 C/C++の標準ライブラリの関数では、自動的にバランスをとって振り分けてくれ、あるスレッドが終わっているときはほかのスレッドを助けるといったことをして、全体が効率よく動くアルゴリズムになっているはずです。

 そもそも、私のいままでのマルチスレッドのやりかたが効率悪かったのかもしれません。各コアに同じだけ処理を与えているのですが、そのコアがほかのアプリに使われているかもしれないので、PコアやEコアに分かれていなくても差が出て当然です。プロファイラーでCPU使用率を見ても、一度に終わらずになだらかに落ちていくので、改善の余地があると思います。

(後編に続く)

[制作協力:Emurasoft, Inc.]