我正在构建一个压力测试客户端,它使用客户端可以召集的尽可能多的线程来锤击服务器并分析响应。我经常发现自己受到垃圾收集(和/或缺乏垃圾收集)的限制,在大多数情况下,它归结为我实例化的字符串,只是为了将它们传递给正则表达式或 Xml 解析例程。
如果你反编译 Regex 类,你会在内部看到,它使用 StringBuilders 来完成几乎所有事情,但你不能将它传递给字符串生成器;它有助于在开始使用私有方法之前深入研究它们,因此扩展方法也无法解决它。如果您想从 System.Xml.Linq 中的解析器中获取对象图,您会遇到类似的情况。
这不是迂腐的提前过度优化的情况。我查看了 StringBuilder问题和其他问题中的正则表达式替换。我还分析了我的应用程序以查看天花板的来源,并使用Regex.Replace()
now 确实在方法链中引入了大量开销,在该方法链中,我试图以每小时数百万个请求访问服务器,并检查 XML 响应中的错误和嵌入式诊断代码。我已经摆脱了几乎所有其他限制吞吐量的低效率,我什至通过扩展 StringBuilder 来在不需要捕获组或反向引用时进行通配符查找/替换,从而减少了很多正则表达式开销,但在我看来,现在有人已经完成了一个基于 Regex 和 Xml 解析实用程序的自定义 StringBuilder(或者更好的是 Stream)。
好的,所以咆哮,但我必须自己做这个吗?
更新:我找到了一种解决方法,可以将峰值内存消耗从数 GB 降低到数百兆,因此我将其发布在下面。我没有将其添加为答案,因为 a)我通常讨厌这样做,并且 b)我仍然想知道是否有人在我做之前花时间自定义 StringBuilder 来执行正则表达式(反之亦然)。
就我而言,我无法使用 XmlReader,因为我正在摄取的流在某些元素中包含一些无效的二进制内容。为了解析 XML,我必须清空这些元素。我以前使用单个静态编译的 Regex 实例来进行替换,这会像疯了一样消耗内存(我正在尝试处理 ~300 10KB 文档/秒)。大幅减少消费的变化是:
- 我在 CodeProject 上添加了这篇 StringBuilder Extensions 文章中的代码,以获得方便的
IndexOf
方法。 - 我添加了一个(非常)粗略的
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;
}