-
Notifications
You must be signed in to change notification settings - Fork 162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
grep 処理速度改善 #77
grep 処理速度改善 #77
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
はじめまして。PRありがとうございます。
修正内容を拝見したところ、かなりの速度改善を見込める修正と判断しました。
個人的には是非取り込みたいと思ってますが、
このコミットについてもう少し詳しく教えていただけませんか・
以下の3点について追加情報をいただけるとありがたいです。
- 何をどう改善したかについて
PR本文の説明から画面更新の頻度を落とす修正だと思って見てます。
理解の祖語があるといけないので、もう少し詳しく説明していただけませんでしょうか? - DoGrepFileメソッドへの引数追加理由について
元々引数が多いメソッドです。
サクラエディタをより良くしていくために引数はむしろ削りたいです。
引数を追加せざるを得ない理由があれば、ご教授いただけませんでしょうか? - 更新間隔 200ticks の設定根拠について
個人的には「なんとなく」でも構わないと思っています。
根拠のある値なのか、感覚で決めた値なのかで変わるものがあるとおもいます。
あとで見たときに分からなくなりそうなので、根拠があれば教えていただけませんか?
このレビューコメントは一度書き直ししています。
GitHubに不慣れなものでレビューコメントを書きなおししました。
初回に書いた「GetTickCout 関数を使用しているのは問題」については取り下げさせていただきます。
@kobakeさんが指摘されているとおり、@beru さんの利用方法では問題がおきないためです。
ぜひ取り込みしたいと考えておりますので、ご対応のほどよろしくお願いします。
PR ありがとうございます!
これについては差分計算での利用であれば問題ないと考えています。本 PR での利用方法も差分計算による利用であったので問題なさそうです。
これを使うとすると Win8 以降に動作環境を制限することになります。動作環境のサポート範囲については要検討ですね。本 PR とは別件としてどこかでお話しましょう。 |
@berryzplus さんレビューありがとうございます。
Grep結果文字列バッファを使いまわしてメモリの確保と解放が行われる頻度を下げる為にそうしました。
200ミリ秒というのは感覚で決めた値で明確な根拠はありません。なお後続のコミットでは50ミリ秒に変えました。モニターのリフレッシュレートと同じぐらいにはしたいですが、そうすると体感できるくらいにGrep処理が遅くなってしまいます。 感覚的なものなので出来ればユーザー側で調整出来るようにした方が良いかもしれません。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ご対応ありがとうございます。
LGTMです。
Grep結果文字列バッファを使いまわしてメモリの確保と解放が行われる頻度を下げる為にそうしました。
これは仕方ない種類の理由だとおもいます。
DoGrepFileの引数が多い件については別途で対応することにします。
インターバル感覚に m_dwTickInterval という名前が付いたことにより文意はつかみやすくなったと思います。
たぶんここをチューニングできるようなインターフェースを提供しても、いじる人ほとんどいない気がします。極力調整可能なパラメータは少なく絞っておいたほうが煩雑にならなくて良いかと。 プログラム的にもインターバルのためだけにインスタンスの状態を増やすのは煩雑なので、 m_dwTickInterval はメンバ変数よりも const 定数であったほうが好ましいと思いました (定数にする場合はそれが分かるように命名も変える)。インターバル間隔は今の 50ms の 決め打ちで良いと思います。 |
sakura_core/mem/CMemory.cpp
Outdated
@@ -312,6 +312,8 @@ void CMemory::AllocBuffer( int nNewDataLen ) | |||
}else{ | |||
/* ���݂̃o�b�t�@�T�C�Y���傫���Ȃ���ꍇ�̂ݍĊm�ۂ��� */ | |||
if( m_nDataBufSize < nWorkLen ){ | |||
// �p�ɂȍĊm�ۂ�s��Ȃ��悤�ɂ���ׁA�K�v�ʂ̔{�̃T�C�Y��m�ۂ��� | |||
nWorkLen <<= 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これは何かしら実際に計測してみた結果からの変更判断でしょうか。
変数値を2倍にする操作については <<= 1
よりも *= 2
のほうが文意がつかみやすくて好ましいです。昔は性能をカバーする意図でこういう書き方をした時代もありましたが、今はコンパイラが自動的に最も効率の良いバイナリコードに最適化してくれるはずなので、人間にとっての読みやすさを優先するのが良いと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
測定結果は残っていませんが過去に別ブランチでGrepの最適化を行っていた際にメモリ確保のコストがかなり重かったです。特に今回のような同じバッファに追記していくケースだと元の記述だとかなりの頻度でメモリの解放と確保が行われてしまうかと思います。
変数値を2倍にする操作はビットシフトではなく乗算を使うように記述を更新しました。念のためにオーバーフローしないように判定も追加しました。そもそも極端に大きいバッファサイズが指定されると色々なところで問題が出るはずなのでこれだけでは意味がないかもしれませんが。
あと本来は一度に大きいバッファを扱うべきでは無いのでデータ構造を変えた方が良いですが(例:VS Code)それはまた別の話なので話題を広げないでおきます。
sakura_core/CGrepAgent.cpp
Outdated
@@ -524,7 +529,7 @@ DWORD CGrepAgent::DoGrep( | |||
if( 0 < nWork && sGrepOption.bGrepHeader ){ | |||
AddTail( pcViewDst, cmemMessage, sGrepOption.bGrepStdout ); | |||
} | |||
cmemMessage.Clear(); // ������Ȃ� | |||
cmemMessage._SetStringLength(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clear と _SetStringLength(0) って何が違うんでしたっけ?(昔触ったことありますが忘れちゃいました…)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clear()はメモリ解放あり(解放→再確保のコストがかかる)
_SetStringLength(0)はメモリ解放なし(解放しないので再確保もしない)
という認識です。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
なるほど、ご説明ありがとうございます
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@berryzplus 代わりに説明していただいてありがとうございます。コメントで書こうか迷ったのですが、いちいち書いているとコメントばかりになってしまう気がして書きませんでした。
気になって調べてみたのですが、標準ライブラリの std::vector::clear()
の場合はメモリ解放はしないみたいで、C++11 から追加された std::vector::shrink_to_fit()
で無駄な領域の切り詰めを行うみたいですね。昔は空のインスタンスとswapするのがstd::vectorのインスタンスのメモリ解放の常套手段だったみたいですが…。
sakura_core/CGrepAgent.cpp
Outdated
if( 0 == nLine % 32 ) { | ||
DWORD dwNow = ::GetTickCount(); | ||
if ( dwNow - m_dwTickUICheck > m_dwTickInterval ) { | ||
m_dwTickUICheck = dwNow; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ざっくり眺めた感じ、全体的に処理の割り込みタイミングを
「処理行数によるタイミングではなく」
「経過時間によるタイミングで」判定する方向性のように見えました。
が、この箇所だけ「処理行数によるタイミング判定」と「経過時間によるタイミング判定」の組み合わせになっているようですが、これは意図的なものですか?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1行のサイズが小さくてたくさん行数があるようなファイルの場合、毎行読み取り毎に時間チェックするのは無駄かもしれないので(とはいっても判定コストは微々たるものだと思いますが)残しておいても良いかもと思います。
1行のサイズがとてつもなく長くて(>100MBとか…)行数が少ないファイルの場合はこの判定の場合は不適切かもしれませんが、そんなファイルをサクラエディタで扱ってもしょうがない気もしますね。
UI確認・結果出力の時間間隔をメンバー変数から定数に変更 CMemory::AllocBuffer() における確保バッファサイズの調整記述を更新
sakura_core/mem/CMemory.cpp
Outdated
// �p�ɂȍĊm�ۂ�s��Ȃ��悤�ɂ���ׁA�K�v�ʂ̔{�̃T�C�Y��m�ۂ��� | ||
if (nWorkLen < std::numeric_limits<int>::max() / 2) { | ||
nWorkLen *= 2; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ご対応ありがとうございます。
他部分については問題ないと思うのですが、ここの影響が割と広範囲で、懸念ではあります。他の方の意見も伺いたいです。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
お久しぶりです。ROMるのでいっぱいいっぱいの ds14050 です。少しだけ失礼します。
CMemory は std::vector に相当するものかなと最初は思ったのですが、より低レベルの allocator に当たる役割をアプリケーションの広範囲に提供しているように見受けられました。その根っこの部分に「2倍確保」などの判断を入れると道具としての使い勝手が悪くなり、下手をすると「AllocBuffer((exactSizeDesired+1)/2);」のような使い方を誘発する懸念があります。メモリについてまじめに考えコントロールしたい人ほどそうしかねません。
CMemory::AllocBuffer を呼ぶ場所を調べる一環で CNativeW::AllocStringBuffer の呼び出し箇所を調べましたが、当然ながら要求するサイズには根拠があります。明確な根拠なく CMemory がそこに手心を加えるのは乱暴です。
AllocBuffer や Set* 系の関数には命じられた仕事を粛々とこなしてもらい、Append 系の便利関数で2倍確保策を講じるのが落し所ではないでしょうか。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ds14050 さん、お久しぶりです。
AllocBuffer や Set* 系の関数には命じられた仕事を粛々とこなしてもらい、Append 系の便利関数で2倍確保策を講じるのが落し所ではないでしょうか。
分かったような、分からないようなんですが、Append系のメモリ確保サイズを倍にしたらいい、という提案と理解してよいでしょうか?
つまりNativeW::AppendStringとかを修正しましょう、という提案?
そう言われればもっともな気がします。
呼出側コードで要求サイズを2倍にする対応はできないんでしたっけ?
できれば今回のGrepでボトルネックになりうる箇所だけをピンポイントで変えたいです。
ご検討いただけませんか?m(。 。)m > @beru さん
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
つまりNativeW::AppendStringとかを修正しましょう、という提案?
そうですが、CMemory の _AppendSz と AppendRawData でやってもいいと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
レビューありがとうございます。頂いたコメントを元に手を加えました。再度確認お願いします。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
頂いたコメントを元に手を加えました。再度確認お願いします。
すみません。ぼくが話についていけてないかも知れません。
読み返してみると、ぼく誤解させてそうなコメントしてますね・・・。
呼出側コードで要求サイズを2倍にする対応はできないんでしたっけ?
できれば今回のGrepでボトルネックになりうる箇所だけをピンポイントで変えたいです。
個人的には後半に言ってることのがポイント高めでした。
CMemoryやCNativeWを変更すると影響範囲が読めなくなるので、できれば触りたくないです。
「ボトルネックになりそうな箇所」をピンポイントで狙えないなら、
ループに入る前にドカンと4KB(約2000文字)確保して拡張の可能性を減らすとか、
少し消極的にはなるんですが、影響範囲を抑えたうえで最大限の効果を狙える手法もとれるのかな、と考えています。
size_t/ptrdiff_tの話を出しましたが、あれは気にしないでください。
ここのスコープに限定すると、オーバーフローについては深く考えなくて良いと思っています。
CNativeWは主に1行を表すバッファとして使われるものなので、
単体で1MB(100万文字)を超えるのはレアケースないし誤用(バイナリファイル読込など)だと思っています。
CMemory/CNativeについては別の機会にあり方を考えていきたいです。
すみません、そういうわけでCMemoryに手を加えない形でもう一度ご検討いただけませんでしょうか?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
個人的にはそういう根拠のない不安には「やっちゃえよ」と反発するのですが、水掛け論になるので主張はしないことにしています。std::vector で普通にある挙動なので、やりすぎだとも予想外の結果になるとも思わないのですが。
@beru さんを迷わせてしまいましたね。承認する準備はできていますが円満迅速な結果を求めるなら berryzplus さんの指摘に従うのが手です。
CMemory の _AppendSz と AppendRawData でメモリ再確保サイズを倍々にして再確保頻度を減らす対応
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ゆくゆくは size_t/ptrdiff_t で扱うとして、符号の有無でオーバーフローの定義/未定義が変わるようです。_ReallocIfNeeded
は一部考慮していますが現実問題としてオーバーフローを無視してコードをシンプルにする選択もあります。現在のコードは無視しています。
_ReallocIfNeeded
が考慮していないのは newSize
の上限を std::numeric_limits<int>::max()
として切り詰めた場合に AllocBuffer
で起こるオーバーフローです。典型的には負のサイズを確保しようとして確保しない&(AllocBufferが)バッファを無効化もしないので、つづく _AddData
が破滅への一歩です。これを AllocBuffer
の可能な上限 (std::numeric_limits<int>::max()&~7) - 7 - 2
に切り詰めるとしても、newSize1
を切り詰めた場合には AllocBuffer
の成否にかかわらずやはり破滅です。危険な _AddData
呼び出しは各所にあるのでここでは問題にしませんが、問題を解決しない対処を埋め込んでも益がありません。また int
より大きい型として int64_t
を採用する対処法は、size_t/ptrdiff_t の採用や x64 コンパイルによって近い将来に有効ではなくなりそうです。int64_t
を使わずに他と同じ型(現在は int
)で書けませんか? GREP の改善が目的ですから CMemory の問題には深入りせず割り切っていいと思います。具体的には
m_nRawLen + appendLength + 2
はオーバーフローするに任せる。(どうせ _AddData が……)- 余分に確保するサイズは AllocBuffer をオーバーフローさせない値まで切り詰める。
berryzplus さんの意味が変わっているという指摘は確保サイズに関することでしょうか。連結データサイズの2倍だったものがバッファサイズの2倍になっていると。確保済みバッファのサイズと連結するデータのサイズの大小によって効果が減ってしまうと。連結したデータサイズの2倍(というか求めやすい値)でいいと思います。
3点目。newSize1
に2を加える必要はないと思います。ターミネータのために AllocBuffer
が確保する2ですよね?
sakura_core/mem/CMemory.cpp
Outdated
// �p�ɂȍĊm�ۂ�s��Ȃ��悤�ɂ���ׁA�K�v�ʂ̔{�̃T�C�Y��m�ۂ��� | ||
if (nWorkLen < std::numeric_limits<int>::max() / 2) { | ||
nWorkLen *= 2; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
頂いたコメントを元に手を加えました。再度確認お願いします。
すみません。ぼくが話についていけてないかも知れません。
読み返してみると、ぼく誤解させてそうなコメントしてますね・・・。
呼出側コードで要求サイズを2倍にする対応はできないんでしたっけ?
できれば今回のGrepでボトルネックになりうる箇所だけをピンポイントで変えたいです。
個人的には後半に言ってることのがポイント高めでした。
CMemoryやCNativeWを変更すると影響範囲が読めなくなるので、できれば触りたくないです。
「ボトルネックになりそうな箇所」をピンポイントで狙えないなら、
ループに入る前にドカンと4KB(約2000文字)確保して拡張の可能性を減らすとか、
少し消極的にはなるんですが、影響範囲を抑えたうえで最大限の効果を狙える手法もとれるのかな、と考えています。
size_t/ptrdiff_tの話を出しましたが、あれは気にしないでください。
ここのスコープに限定すると、オーバーフローについては深く考えなくて良いと思っています。
CNativeWは主に1行を表すバッファとして使われるものなので、
単体で1MB(100万文字)を超えるのはレアケースないし誤用(バイナリファイル読込など)だと思っています。
CMemory/CNativeについては別の機会にあり方を考えていきたいです。
すみません、そういうわけでCMemoryに手を加えない形でもう一度ご検討いただけませんでしょうか?
CMemory は変更しないように元に戻す CGrepAgent の変更コードのインデントスタイルを周りに合わせる
@berryzplus @ds14050 コメントありがとうございます。CMemory 側には変更を加えないようにしました。 |
ご指摘通りケアを完全に出来ないなら、他に合わせて無視する方が記述がシンプルになって良いですね。
おっしゃるとおり
int64_t を使わないで書く場合の分岐を考えるのが大変で手抜きしました。
どういう記述にすればシンプルな倍々再確保処理になるのか悩んで結局手抜きして int64_t を使いましたが、32bit整数演算だけで分岐使わないで書けたら良いですね。ビット演算だらけで読みにくくなりそうですが…。
等が良いのでしょうか…。あっ、
それもそうですね。 |
beru さんのもともとのコミットでは AllocBuffer への要求サイズ(nWorkLen = 連結データサイズ)の2倍を確保するものだったではありませんか。バッファサイズの2倍は考えずに連結データサイズを2倍にするのが「求めやすい値」です。 済んだことなので、的外れなコメントをしないために自分が考えたものも書いておきます。AllocBuffer の呼び出しをしないことを除けば _ReallocIfNeeded と処理内容はほぼ同じです(そのはずっ)。
|
それはそうなのですが倍々にしていく方がバッファの増え方としては綺麗な気がしてそう書きました。
|
歴史的経緯ですね。これは後々見直しても良いと思います。 |
10年くらい前に CNativeW は自分の手で作られました。 構造としては既存のメモリ確保挙動を壊さないように(挙動変えたい気持ちもありましたが、ここで余計な反発喰らって Unicode 対応の足並みが遅くなるのは嫌だったので挙動はそのままにした)、かつ、既存コードが以前よりは(Unicode 対応前よりは)読みやすくなるように苦肉の策でこうなってる感じです。 |
そういう意図を語れる人がいるのはいいですね。でないとどうしても「現状が正義」になりがちなので。 |
確認ですが、メモリ確保のロジックを変更しないとこの PR による速度改善は望めない感じでしょうか?メモリ確保部分変更しなくても十分高速化されるのであれば一旦メモリ確保部分は取り除いてレビューに回してしまったほうがレビューは通りやすいと思います。 Grep速度のボトルネックの多くがメモリ確保部分にあるとするならば、自分がやるとしたらメモリ確保をStrategyパターン的に切り出してGrep時にだけ別のアロケータ使うような実装にすると思います。 もちろん将来的にはサクラエディタ全体に関わるメモリ確保ロジックの改善が必要な雰囲気は伝わったのですが、簡単に判断できる問題ではないのでGrep改善と一緒に話を進めようとすると難航しそうな気がします。 |
@kobake 時間を計ってみました。
処理時間の単位はミリ秒で、サクラエディタのリポジトリ以下を CMemoryでメモリの倍々再確保をしたらGrep検索の速度が速くなると思ったのは私の勘違いでした。すみません。 秀丸はマルチスレッドも使って高速化したみたいですね。あとRust言語で書かれている |
@kobake このPRでは |
いえいえ、いろいろ検討するキッカケになって良かったです。 ちなみにちゃんと調べたわけじゃないので言うの迷ってましたが、おそらく 明日以降でまた時間のあるときに再レビューしてみますね。 |
と思いましたがコミットメッセージの詳細部分に細かい内容書いてあったのですね。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
以下理由により LGTM と判断しました。
@berryzplus さんの request change が入っているので現時点ではマージがブロックされている状態です。 個人的にはこの PR での変更以前の問題として、CGrepAgent のコードがあまりにも肥大化しているため、どこかのタイミングでがっつりリファクタリングしたい気持ちが強いです(これは10年前の時点でも既に気になっていましたが、既存挙動を極力変更しないためにスルーしてました)。 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTMです。
対応ありがとうございました。
grep 処理速度改善 マージします。
共通設定の検索タブの Grep
リアルタイムで表示する
のチェックが付いていると、Grep処理中に画面の更新処理を頻繁に行う為処理に時間が掛かっているので対策を行いました。