私は、サーバーをハンマーで叩き、クライアントが集めることができる限り多くのスレッドを使用して応答を分析するストレステストクライアントを構築しています。私は常にガベージコレクション(および/またはその欠如)によって抑制されていることに気づいています。ほとんどの場合、インスタンス化するのは文字列であり、正規表現またはXml解析ルーチンに渡すだけです。
Regexクラスを逆コンパイルすると、内部的にはStringBuildersを使用してほぼすべてのことを実行しますが、文字列ビルダーを渡すことはできません。プライベートメソッドを使い始める前にそれらを掘り下げると便利なので、拡張メソッドもそれを解決しません。System.Xml.Linqのパーサーからオブジェクトグラフを取得する場合も、同様の状況になります。
これは、事前の衒学的な過剰最適化の場合ではありません。StringBuilderの質問などで正規表現の置き換えを確認しました。また、アプリのプロファイルを作成して、天井がどこから来ているかを確認し、Regex.Replace()
現在、メソッドチェーンにかなりのオーバーヘッドが発生しており、1時間あたり数百万のリクエストでサーバーにアクセスし、XML応答でエラーや埋め込まれた診断コードを調べようとしています。スループットを制限している他のほぼすべての非効率性をすでに取り除きました。また、キャプチャグループや後方参照が必要ない場合にワイルドカードの検索/置換を行うようにStringBuilderを拡張することで、正規表現のオーバーヘッドを大幅に削減しました。しかし、今では誰かがカスタムのStringBuilder(またはもっと良いのはStream)ベースのRegexおよびXml解析ユーティリティをラップしているように思われます。
さて、それで暴言を吐きます、しかし私はこれを自分でしなければならないのでしょうか?
更新:ピークメモリ消費量を数ギガバイトから数百メガバイトに下げる回避策を見つけたので、以下に投稿します。a)一般的にそうするのは嫌いであり、b)誰かがStringBuilderをカスタマイズして正規表現を実行する(またはその逆)のに時間がかかるかどうかを確認したいので、答えとして追加しません。
私の場合、取り込みているストリームの特定の要素に無効なバイナリコンテンツが含まれているため、XmlReaderを使用できませんでした。XMLを解析するには、これらの要素を空にする必要があります。以前は、静的にコンパイルされた単一の正規表現インスタンスを使用して置換を行っていましたが、これは狂ったようにメモリを消費しました(最大300の10KBドキュメント/秒を処理しようとしています)。消費量を大幅に削減した変更は次のとおりです。
- 便利なメソッドのために、CodeProjectに関するこのStringBuilder拡張機能の記事からコードを追加しました
IndexOf
。 - 呼び出しごとに1つのワイルドカード文字(*または?) を許可する(非常に)大まかな
WildcardReplace
メソッドを追加しました WildcardReplace()
正規表現の使用法を、問題のある要素の内容を空にする呼び出しに置き換えました
これは非常にきれいではなく、私自身の目的が必要とする範囲でのみテストされています。もっとエレガントでパワフルにできたらよかったのですが、YAGNIなどで急いでいます。コードは次のとおりです。
/// <summary>
/// Performs basic wildcard find and replace on a string builder, observing one of two
/// wildcard characters: * matches any number of characters, or ? matches a single character.
/// Operates on only one wildcard per invocation; 2 or more wildcards in <paramref name="find"/>
/// will cause an exception.
/// All characters in <paramref name="replaceWith"/> are treated as literal parts of
/// the replacement text.
/// </summary>
/// <param name="find"></param>
/// <param name="replaceWith"></param>
/// <returns></returns>
public static StringBuilder WildcardReplace(this StringBuilder sb, string find, string replaceWith) {
if (find.Split(new char[] { '*' }).Length > 2 || find.Split(new char[] { '?' }).Length > 2 || (find.Contains("*") && find.Contains("?"))) {
throw new ArgumentException("Only one wildcard is supported, but more than one was supplied.", "find");
}
// are we matching one character, or any number?
bool matchOneCharacter = find.Contains("?");
string[] parts = matchOneCharacter ?
find.Split(new char[] { '?' }, StringSplitOptions.RemoveEmptyEntries)
: find.Split(new char[] { '*' }, StringSplitOptions.RemoveEmptyEntries);
int startItemIdx;
int endItemIdx;
int newStartIdx = 0;
int length;
while ((startItemIdx = sb.IndexOf(parts[0], newStartIdx)) > 0
&& (endItemIdx = sb.IndexOf(parts[1], startItemIdx + parts[0].Length)) > 0) {
length = (endItemIdx + parts[1].Length) - startItemIdx;
newStartIdx = startItemIdx + replaceWith.Length;
// With "?" wildcard, find parameter length should equal the length of its match:
if (matchOneCharacter && length > find.Length)
break;
sb.Remove(startItemIdx, length);
sb.Insert(startItemIdx, replaceWith);
}
return sb;
}