大字符串正则表达式替换 C# 中的性能
Large string regex replace performance in C#
我在 C# 中遇到正则表达式性能问题。
我需要替换一个非常大的字符串(27 万个字符,不要问为什么..)。正则表达式匹配了大约 3k 次。
private static Regex emptyCSSRulesetRegex = new Regex(@"[^\};\{]+\{\s*\}", RegexOptions.Compiled | RegexOptions.Singleline);
public string ReplaceEmptyCSSRulesets(string css) {
return emptyCSSRulesetRegex.Replace(css, string.Empty);
}
我传递给方法的字符串看起来像这样:
.selector-with-statements{border:none;}.selector-without-statements{}.etc{}
目前替换过程在 C# 中占用 1500ms,但是当我在 Javascript 中完全相同时它只需要 100ms.
我用来计时的Javascript代码:
console.time('reg replace');
myLargeString.replace(/[^\};\{]+\{\s*\}/g,'');
console.timeEnd('reg replace');
我还尝试通过以相反顺序循环匹配并替换 StringBuilder 中的字符串来进行替换。那没有帮助。
在这种情况下,我对 C# 和 Javascript 之间的性能差异感到惊讶,我认为我做错了什么,但我想不出任何东西。
我真的无法解释 Javascript 和 C#(*) 之间的时间差异。但是您可以尝试提高模式的性能(产生大量回溯):
private static Regex emptyCSSRulesetRegex = new Regex(@"(?<keep>[^};{]+)(?:{\s*}(?<keep>))?", RegexOptions.Compiled);
public string ReplaceEmptyCSSRulesets(string css) {
return emptyCSSRulesetRegex.Replace(css, @"${keep}");
}
原始模式的一个问题是,当大括号不为空(或未填充空格)时,正则表达式引擎将继续测试左大括号之前的每个位置(结果始终相同) .示例:对于字符串 abcd{1234}
,您的模式将从 a
开始测试,然后是 b
...
我建议的模式将消耗 abcd
即使它后面没有空花括号,所以 bcd
的位置没有测试。
abcd
在名为 keep
的组中被捕获,但是当发现空的花括号时,捕获组将被一个空的捕获组覆盖。
您可以了解这两种模式所需的步骤数(检查调试器):
注意:如果将 [^}{;]+
括在原子组中,可以改进您的原始模式。此更改会将所需的步骤数除以 2(与原来的相比),但即使如此,由于前面解释的原因,步骤数仍然很高。
(*) javascript 正则表达式引擎可能足够聪明,不会重试所有这些位置,但这只是一个假设。
我在 C# 中遇到正则表达式性能问题。
我需要替换一个非常大的字符串(27 万个字符,不要问为什么..)。正则表达式匹配了大约 3k 次。
private static Regex emptyCSSRulesetRegex = new Regex(@"[^\};\{]+\{\s*\}", RegexOptions.Compiled | RegexOptions.Singleline);
public string ReplaceEmptyCSSRulesets(string css) {
return emptyCSSRulesetRegex.Replace(css, string.Empty);
}
我传递给方法的字符串看起来像这样:
.selector-with-statements{border:none;}.selector-without-statements{}.etc{}
目前替换过程在 C# 中占用 1500ms,但是当我在 Javascript 中完全相同时它只需要 100ms.
我用来计时的Javascript代码:
console.time('reg replace');
myLargeString.replace(/[^\};\{]+\{\s*\}/g,'');
console.timeEnd('reg replace');
我还尝试通过以相反顺序循环匹配并替换 StringBuilder 中的字符串来进行替换。那没有帮助。
在这种情况下,我对 C# 和 Javascript 之间的性能差异感到惊讶,我认为我做错了什么,但我想不出任何东西。
我真的无法解释 Javascript 和 C#(*) 之间的时间差异。但是您可以尝试提高模式的性能(产生大量回溯):
private static Regex emptyCSSRulesetRegex = new Regex(@"(?<keep>[^};{]+)(?:{\s*}(?<keep>))?", RegexOptions.Compiled);
public string ReplaceEmptyCSSRulesets(string css) {
return emptyCSSRulesetRegex.Replace(css, @"${keep}");
}
原始模式的一个问题是,当大括号不为空(或未填充空格)时,正则表达式引擎将继续测试左大括号之前的每个位置(结果始终相同) .示例:对于字符串 abcd{1234}
,您的模式将从 a
开始测试,然后是 b
...
我建议的模式将消耗 abcd
即使它后面没有空花括号,所以 bcd
的位置没有测试。
abcd
在名为 keep
的组中被捕获,但是当发现空的花括号时,捕获组将被一个空的捕获组覆盖。
您可以了解这两种模式所需的步骤数(检查调试器):
注意:如果将 [^}{;]+
括在原子组中,可以改进您的原始模式。此更改会将所需的步骤数除以 2(与原来的相比),但即使如此,由于前面解释的原因,步骤数仍然很高。
(*) javascript 正则表达式引擎可能足够聪明,不会重试所有这些位置,但这只是一个假设。