C# NET中的链式字符串替换

C# NET中的链式字符串替换,c#,regex,string,replace,stringbuilder,C#,Regex,String,Replace,Stringbuilder,给定:base64字符串 调用时:Foo 然后:Foo返回带有以下字符替换的输入字符串('A'=>'、'B'=>'-'、'C'=>'+'),并尽可能快地执行该操作 我比较了几种算法,以确定哪个版本的Foo更快。结果指向普通的字符串。Replace,这非常令人惊讶。我本以为正则表达式在编译时会受到初步的影响,但随后会突破并超越string.Replace,它会在每次调用Foo时创建三个字符串副本 我想看看是否有其他人可以证实这些发现,或者解释为什么获胜者的表现优于其他人 我使用这些算法运行了Foo

给定:base64字符串

调用时:
Foo

然后:
Foo
返回带有以下字符替换的输入字符串
('A'=>'、'B'=>'-'、'C'=>'+')
,并尽可能快地执行该操作

我比较了几种算法,以确定哪个版本的
Foo
更快。结果指向普通的
字符串。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]
[2] :

[3] :

[4] :


在我看来,正则表达式要复杂得多


Replace直接调用Win32,可能会执行基于指针的字符串操作,以防止碎片化等(很难确定-但在托管代码中不会这样做)。如果我启动ILSpy,我会看到RegEx.Replace执行大量边界检查,然后进行匹配,然后使用StringBuilder执行对您的代理的调用结果。

在我看来,RegEx要复杂得多


Replace直接调用Win32,可能会执行基于指针的字符串操作,以防止碎片化等(很难确定-但在托管代码中不会这样做)。如果我启动ILSpy,我会看到RegEx.Replace进行了大量的边界检查,然后进行匹配,然后使用StringBuilder执行对您的委托的调用结果。

如果我们检查您指定的方法的实现,我们将不会感到意外


包括模式匹配和大量字符串连接,这会导致开销。虽然普通的C++使用C++实现(COMSTROP.CPP文件),它是低级别的,并且最可能是非常优化的。

< P>如果我们检查您指定的方法的实现,我们将不会有什么意外。p>
包括模式匹配和大量字符串连接,这会导致开销。而普通的C++直接实现(<代码> CONSCON.CPP文件),它是低层次的,而且最有可能是非常优化的。

< P>是的,我对你所发现的也感到惊讶…并不是说Regex是最慢的,这是意料之中的。。。但是链式的
字符串。Replace
StringBuilder
一样执行。 我自己做了一些检查,比较了与您相同的[1],但我修改了[2],使其尽可能接近裸骨实现O(n)

[1]

[2]

静态字符串Foo2(字符串输入)
{
变量长度=输入长度;
var sb=新字符[长度];
for(int i=0;i
我的测试字符串长度超过700万个字符(7230872,Lorem Ipsum)。 因此,我们可以注意到以下几点:

  • 这两种方法的执行速度非常快(大约55毫秒对51毫秒),因为其他进程的活动和CPU可用性在最终结果中起着重要作用。我将这两个方法执行了100次,并将所有执行时间相加,得到了大约5500ms(对于Foo)和5100(对于Foo2)
  • Foo方法至少遍历整个字符串3次(可能更多,不确定)。另一方面,Foo2方法看起来好像只遍历字符串一次。。。但事实并非如此。。。它至少经历了两次<代码>输入。长度也会经过它(计算字符数)。谢谢菲利普
  • 当您尝试替换越来越多的字符时,
    Foo
    Foo2
    之间的差异变得越来越明显。使用15个字符替换时,
    Foo
    大约在240毫秒内执行,而
    Foo2
    大约在60毫秒内执行

  • 所以。。。总之。。。这里没有魔法,它只是执行得非常快…:)

    是的,你的发现也让我很惊讶。。。并不是说Regex是最慢的,这是意料之中的。。。但是链式的
    字符串。Replace
    StringBuilder
    一样执行。 我自己做了一些检查,比较了与您相同的[1],但我修改了[2],使其尽可能接近裸骨实现O(n)

    [1]

    [2]

    静态字符串Foo2(字符串输入)
    {
    变量长度=输入长度;
    var sb=新字符[长度];
    for(int i=0;i
    我的测试字符串长度超过700万个字符(7230872,Lorem Ipsum)。 因此,我们可以注意到以下几点:

  • 这两种方法的执行速度非常快(大约55毫秒对51毫秒),因为其他进程的活动和CPU可用性在最终结果中起着重要作用。我100次执行了这两种方法
    Foo(string input) 
    { 
      return input.Replace("A", "_").Replace("B", "-").Replace("C", "+"); 
    }
    
    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();
    }
    
    Foo(string input) 
    {
      return new StringBuilder(input, input.Length).Replace("A", "_").Replace("B", "-").Replace("C", "+").ToString()
    }
    
    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;
        });
    }
    
        static string Foo(string input)
        {
            string result = input.Replace("A", "_");
            result = result.Replace("B", "-");
            result = result.Replace("C", "+");
            return result;
        }
    
        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();
        }
    
    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);
    }
    
    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);
    }