给定:base64字符串
何时:Foo
被称为
然后:Foo
返回带有以下字符替换('A' => '_', 'B' => '-', 'C' => '+')
的输入字符串,并尽可能快地执行
我比较了几种算法来确定哪个版本的Foo
更快。结果指向普通的string.Replace
,这是非常令人惊讶的。我原本期望正则表达式能够在编译时获得最初的打击,但随后会在string.Replace
的每次调用中创建三个字符串副本,并且优于Foo
。
我想检查是否有其他人可以证实这些调查结果,或者想出为什么胜利者胜过其他人的原因。
我使用这些算法运行了Foo
100k次,结果是TimeSpan
,在调试版本中使用StopWatch
测量完成后执行:
00:00:00.0500790 <=== string.Replace [1]
00:00:00.0699696 <=== StringBuilder.Append [2]
00:00:00.0988960 <=== StringBuilder.Replace [3]
00:00:00.7440135 <=== Regex [4]
[1]:
Foo(string input)
{
return input.Replace("A", "_").Replace("B", "-").Replace("C", "+");
}
[2]:
Foo(string input)
{
var sb = new StringBuilder(input.Length);
foreach (var x in input)
{
if (x == 'A')
{
sb.Append('_');
}
else if (x == 'B')
{
sb.Append('-');
}
else if (x == 'C')
{
sb.Append('+');
}
else
{
sb.Append(x);
}
}
return sb.ToString();
}
[3]:
Foo(string input)
{
return new StringBuilder(input, input.Length).Replace("A", "_").Replace("B", "-").Replace("C", "+").ToString()
}
[4]:
static readonly Regex charsRegex = new Regex(@"[ABC]", RegexOptions.Compiled);
Foo(string input)
{
charsRegex.Replace(input, delegate (Match m)
{
var value = m.Value;
if (value == "A")
{
return "_";
}
else if (value == "B")
{
return "-";
}
else if (value == "C")
{
return "+";
}
return value;
});
}
答案 0 :(得分:2)
我想建议其他实现。
public /*unsafe*/ static string Foo(string text)
{
char[] a = text.ToCharArray();
for(int i = 0; i < a.Length; i++)
switch(a[i])
{
case 'A': a[i] = '_'; break;
case 'B': a[i] = '-'; break;
case 'C': a[i] = '+'; break;
}
return new string(a);
}
OR
public /*unsafe*/ static string Foo(string text)
{
char[] a = new char[text.Length];
for(int i = 0; i < text.Length; i++)
{
char c=text[i];
switch(c)
{
case 'A': a[i] = '_'; break;
case 'B': a[i] = '-'; break;
case 'C': a[i] = '+'; break;
default: a[i] = c; break;
}
}
return new string(a);
}
如果您允许不安全的代码并取消注释不安全,则可能会比[1]更快。
[1]获胜,因为它全部是原生的,尽管通过数据有3个循环 [2]许多指数检查和当前指数增加 [3]多个循环通过相同的数据,许多索引检查,但可以进行原位替换) [4]最后,因为状态机的开销,并调用替换方法。加上字符串比较,但不是字符比较。
答案 1 :(得分:1)
在我看来,正则表达式简直复杂得多。
String.Replace直接调用Win32,并且可能会执行基于指针的字符串操作,以防止碎片等(很难确定 - 但它不会在托管代码中执行此操作)。如果我启动ILSpy,我会看到RegEx.Replace会执行大量的边界检查,然后执行匹配,然后使用StringBuilder执行对您的委托的调用结果。
答案 2 :(得分:1)
如果我们检查您指定的方法的实现,我们最终不会感到惊讶。
Regex.Replace包括模式匹配和大量字符串连接,这会导致开销。普通的旧String.Replace直接使用C ++实现(comstring.cpp
文件),这是低级的,很可能是非常优化的。
答案 3 :(得分:1)
是的,我对你发现的东西感到惊讶......并不是说正则表达式是最慢的,这是预期的......但链式String.Replace
表现得和StringBuilder
一样。
我做了一些我自己的检查我比较你做的那样[1],但是我修改了[2]以使它接近裸骨实现,因为我可以O(n)。
[1]
static string Foo(string input)
{
string result = input.Replace("A", "_");
result = result.Replace("B", "-");
result = result.Replace("C", "+");
return result;
}
[2]
static string Foo2(string input)
{
var length = input.Length;
var sb = new char[length];
for (int i = 0; i < length; i++)
{
switch (input[i])
{
case 'A':
sb[i] = '_';
break;
case 'B':
sb[i] = '-';
break;
case 'C':
sb[i] = '+';
break;
default:
sb[i] = input[i];
break;
}
}
return sb.ToString();
}
我的测试字符串长度超过700万个字符(7230872,Lorem Ipsum)。 所以我们可以注意到几点:
input.Length
也经历过它(计算字符数)Foo
和Foo2
之间的差异变得更加明显。使用15个字符替换Foo
在aprox 240ms和Foo2
中执行约60ms。所以......总之......这里没有魔法,只是执行得非常快......:)