C# String.Replace.NET Framework的内存效率和性能

C# String.Replace.NET Framework的内存效率和性能,c#,.net,string,C#,.net,String,我继承了一些与上面代码片段相同的代码。它接受一个大字符串,并从大字符串中替换(删除)常量较小的字符串 我相信这是一个非常内存密集的过程,因为每次替换都会在内存中分配新的大型不可变字符串,通过GC等待死亡 1。忽略内存问题,替换这些值的最快方法是什么? 2。实现相同结果的最有效内存方法是什么? 我希望这些都是相同的答案 同时,我们也赞赏适合这些目标之间的实际解决方案 假设: 所有替换都是固定的,并且提前知道 基础字符确实包含一些unicode[非ascii]字符 StringBuilder: 替

我继承了一些与上面代码片段相同的代码。它接受一个大字符串,并从大字符串中替换(删除)常量较小的字符串

我相信这是一个非常内存密集的过程,因为每次替换都会在内存中分配新的大型不可变字符串,通过GC等待死亡


1。忽略内存问题,替换这些值的最快方法是什么?

2。实现相同结果的最有效内存方法是什么?

我希望这些都是相同的答案

同时,我们也赞赏适合这些目标之间的实际解决方案

假设:

  • 所有替换都是固定的,并且提前知道
  • 基础字符确实包含一些unicode[非ascii]字符
StringBuilder:

替换操作本身的性能应与string.Replace大致相同,根据Microsoft的规定,不应产生垃圾。

一个.NET字符串中的所有字符都是“unicode字符”。你是说它们不是ascii码?这应该不会有什么影响——除非你遇到作文问题,例如,当你试图替换“e急音”时,“e+急音”没有被替换

您可以尝试将正则表达式与或一起使用。下面的示例代码对这两者都做了相同的事情:

 string str1 = "12345ABC...\\...ABC100000"; 
 // Hypothetically huge string of 100000 + Unicode Chars
 str1 = str1.Replace("1", string.Empty);
 str1 = str1.Replace("22", string.Empty);
 str1 = str1.Replace("656", string.Empty);
 str1 = str1.Replace("77ABC", string.Empty);

 // ...  this replace anti-pattern might happen with upto 50 consecutive lines of code.

 str1 = str1.Replace("ABCDEFGHIJD", string.Empty);
我不想猜测哪一个更有效-您必须使用特定的应用程序进行基准测试。regex方法可能能够在一个过程中完成所有操作,但与StringBuilder中的许多替换相比,该过程将相对占用CPU

using System;
using System.Text;
using System.Text.RegularExpressions;

class Test
{
    static void Main(string[] args)
    {
        string original = "abcdefghijkl";

        Regex regex = new Regex("a|c|e|g|i|k", RegexOptions.Compiled);

        string removedByRegex = regex.Replace(original, "");
        string removedByStringBuilder = new StringBuilder(original)
            .Replace("a", "")
            .Replace("c", "")
            .Replace("e", "")
            .Replace("g", "")
            .Replace("i", "")
            .Replace("k", "")
            .ToString();

        Console.WriteLine(removedByRegex);
        Console.WriteLine(removedByStringBuilder);
    }
}

,一个可变字符串。

如果你想在dotnet中使用内置类,我认为StringBuilder是最好的。
要手动执行,您可以使用带有char*的不安全代码,并根据您的条件迭代字符串和替换

因为您在一个字符串上有多个替换,我建议您使用正则表达式而不是StringBuilder。

如果您想非常快,我的意思是,很快你就必须超越StringBuilder,只需编写优化良好的代码

using System;
using System.Text;
using System.Text.RegularExpressions;

class Test
{
    static void Main(string[] args)
    {
        string original = "abcdefghijkl";

        Regex regex = new Regex("a|c|e|g|i|k", RegexOptions.Compiled);

        string removedByRegex = regex.Replace(original, "");
        string removedByStringBuilder = new StringBuilder(original)
            .Replace("a", "")
            .Replace("c", "")
            .Replace("e", "")
            .Replace("g", "")
            .Replace("i", "")
            .Replace("k", "")
            .ToString();

        Console.WriteLine(removedByRegex);
        Console.WriteLine(removedByStringBuilder);
    }
}
您的计算机不喜欢做的一件事是分支,如果您可以编写一个在固定数组(char*)上操作且不进行分支的替换方法,那么您的性能会很好

您将要做的是,replace操作将搜索一个字符序列,如果它找到任何这样的子字符串,它将替换它。实际上,您将复制字符串,并且在执行此操作时,执行查找和替换

您将依赖这些函数来选择要读/写的某个缓冲区的索引。我们的目标是预先定义replace方法,以便在不需要任何更改的情况下编写垃圾而不是分支

您应该能够在不使用单个if语句的情况下完成此操作,并记住使用不安全的代码。否则,您将为每个元素访问的索引检查付费

StringBuilder sb = new StringBuilder("Hello string");
sb.Replace("string", String.Empty);
Console.WriteLine(sb);  
为了好玩,我在C#中编写了这样的代码,并看到了显著的性能改进,查找和替换的速度提高了近300%。虽然.NETBCL(基类库)的性能相当好,但它充满了分支结构和异常处理,如果使用内置的东西,这将减慢代码的速度。此外,这些优化虽然完美,但不是由JIT编译器执行的,您必须在没有任何调试器的情况下以发布版本的形式运行代码,才能观察到巨大的性能提升


我可以为您提供更完整的代码,但这是一个巨大的工作量。不过,我可以向您保证,它将比目前建议的任何其他方法都要快。

这里有一个快速的基准

unsafe
{
    fixed( char * p = myStringBuffer )
    {
        // Do fancy string manipulation here
    }
}

我在我的机器上没有看到太大的差异(string.replace是85毫秒,stringbuilder.replace是80毫秒),这与“source”中大约8MB的文本相比。忽略内存问题,替换这些值的最快方法是什么?

最快的方法是构建特定于您的用例的自定义组件。从.NET4.6开始,BCL中没有设计用于多字符串替换的类


如果您需要BCL的快速功能,StringBuilder是用于简单字符串替换的最快BCL组件。可以找到源代码:替换单个字符串非常有效。只有在您真正需要正则表达式的模式匹配功能时才使用正则表达式。即使在编译时,它也会变得更慢、更麻烦

2。实现相同结果的最有效内存方法是什么?

内存效率最高的方法是执行从源到目标的过滤流复制(解释如下)。内存消耗将被限制在您的缓冲区内,但这将更加占用CPU;根据经验,您将以CPU性能换取内存消耗

技术细节

字符串替换很棘手。即使是在可变内存空间(如使用)中执行字符串替换,代价也很高。如果替换字符串的长度不同于原始字符串,则需要重新定位替换字符串后面的每个字符,以保持整个字符串的连续性。这会导致大量的内存写入,甚至在的情况下,也会导致在每次调用Replace时重写内存中的大部分字符串

那么,替换字符串的最快方法是什么呢?使用单次传递编写新字符串:不要让代码返回,而必须重新编写任何内容。写比读更贵。您必须自己编写代码以获得最佳结果

高内存解决方案

我编写的类基于模板生成字符串。我将令牌($ReplaceMe$)放在一个模板中,该模板标记了我以后要插入字符串的位置。我在XmlWriter对于XML来说过于繁重的情况下使用它
<DataTable source='Users'>
  <Rows>
    <Row id='25' name='Administrator' />
    <Row id='29' name='Robert' />
    <Row id='55' name='Amanda' />
  </Rows>
</DataTable>
<DataTable source='$TableName$'>
  <Rows>
    <Row id='$0$' name='$1$'/>
  </Rows>
</DataTable>
class Program
{
  static string[,] _users =
  {
    { "25", "Administrator" },
    { "29", "Robert" },
    { "55", "Amanda" },
  };

  static StringTemplate _documentTemplate = new StringTemplate(@"<DataTable source='$TableName$'><Rows>$Rows$</Rows></DataTable>");
  static StringTemplate _rowTemplate = new StringTemplate(@"<Row id='$0$' name='$1$' />");
  static void Main(string[] args)
  {
    _documentTemplate.SetParameter("TableName", "Users");
    _documentTemplate.SetParameter("Rows", GenerateRows);

    Console.WriteLine(_documentTemplate.GenerateString(4096));
    Console.ReadLine();
  }

  private static void GenerateRows(StreamWriter writer)
  {
    for (int i = 0; i <= _users.GetUpperBound(0); i++)
      _rowTemplate.GenerateString(writer, _users[i, 0], _users[i, 1]);
  }
}
public class StringTemplate
{
  private string _template;
  private string[] _parts;
  private int[] _tokens;
  private string[] _parameters;
  private Dictionary<string, int> _parameterIndices;
  private string[] _replaceGraph;
  private Action<StreamWriter>[] _callbackGraph;
  private bool[] _graphTypeIsReplace;

  public string[] Parameters
  {
    get { return _parameters; }
  }

  public StringTemplate(string template)
  {
    _template = template;
    Prepare();
  }

  public void SetParameter(string name, string replacement)
  {
    int index = _parameterIndices[name] + _parts.Length;
    _replaceGraph[index] = replacement;
    _graphTypeIsReplace[index] = true;
  }

  public void SetParameter(string name, Action<StreamWriter> callback)
  {
    int index = _parameterIndices[name] + _parts.Length;
    _callbackGraph[index] = callback;
    _graphTypeIsReplace[index] = false;
  }

  private static Regex _parser = new Regex(@"\$(\w{1,64})\$", RegexOptions.Compiled);
  private void Prepare()
  {
    _parameterIndices = new Dictionary<string, int>(64);
    List<string> parts = new List<string>(64);
    List<object> tokens = new List<object>(64);
    int param_index = 0;
    int part_start = 0;

    foreach (Match match in _parser.Matches(_template))
    {
      if (match.Index > part_start)
      {
        //Add Part
        tokens.Add(parts.Count);
        parts.Add(_template.Substring(part_start, match.Index - part_start));
      }


      //Add Parameter
      var param = _template.Substring(match.Index + 1, match.Length - 2);
      if (!_parameterIndices.TryGetValue(param, out param_index))
        _parameterIndices[param] = param_index = _parameterIndices.Count;
      tokens.Add(param);

      part_start = match.Index + match.Length;
    }

    //Add last part, if it exists.
    if (part_start < _template.Length)
    {
      tokens.Add(parts.Count);
      parts.Add(_template.Substring(part_start, _template.Length - part_start));
    }

    //Set State
    _parts = parts.ToArray();
    _tokens = new int[tokens.Count];

    int index = 0;
    foreach (var token in tokens)
    {
      var parameter = token as string;
      if (parameter == null)
        _tokens[index++] = (int)token;
      else
        _tokens[index++] = _parameterIndices[parameter] + _parts.Length;
    }

    _parameters = _parameterIndices.Keys.ToArray();
    int graphlen = _parts.Length + _parameters.Length;
    _callbackGraph = new Action<StreamWriter>[graphlen];
    _replaceGraph = new string[graphlen];
    _graphTypeIsReplace = new bool[graphlen];

    for (int i = 0; i < _parts.Length; i++)
    {
      _graphTypeIsReplace[i] = true;
      _replaceGraph[i] = _parts[i];
    }
  }

  public void GenerateString(Stream output)
  {
    var writer = new StreamWriter(output);
    GenerateString(writer);
    writer.Flush();
  }

  public void GenerateString(StreamWriter writer)
  {
    //Resolve graph
    foreach(var token in _tokens)
    {
      if (_graphTypeIsReplace[token])
        writer.Write(_replaceGraph[token]);
      else
        _callbackGraph[token](writer);
    }
  }

  public void SetReplacements(params string[] parameters)
  {
    int index;
    for (int i = 0; i < _parameters.Length; i++)
    {
      if (!Int32.TryParse(_parameters[i], out index))
        continue;
      else
        SetParameter(index.ToString(), parameters[i]);
    }
  }

  public string GenerateString(int bufferSize = 1024)
  {
    using (var ms = new MemoryStream(bufferSize))
    {
      GenerateString(ms);
      ms.Position = 0;
      using (var reader = new StreamReader(ms))
        return reader.ReadToEnd();
    }
  }

  public string GenerateString(params string[] parameters)
  {
    SetReplacements(parameters);
    return GenerateString();
  }

  public void GenerateString(StreamWriter writer, params string[] parameters)
  {
    SetReplacements(parameters);
    GenerateString(writer);
  }
}
using System;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

internal static class MeasureTime
{
    internal static TimeSpan Run(Action func, uint count = 1)
    {
        if (count <= 0)
        {
            throw new ArgumentOutOfRangeException("count", "Must be greater than zero");
        }

        long[] arr_time = new long[count];
        Stopwatch sw = new Stopwatch();
        for (uint i = 0; i < count; i++)
        {
            sw.Start();
            func();
            sw.Stop();
            arr_time[i] = sw.ElapsedTicks;
            sw.Reset();
        }

        return new TimeSpan(count == 1 ? arr_time.Sum() : Convert.ToInt64(Math.Round(arr_time.Sum() / (double)count)));
    }
}

public class Program
{
    public static string RandomString(int length)
    {
        Random random = new Random();
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        return new String(Enumerable.Range(1, length).Select(_ => chars[random.Next(chars.Length)]).ToArray());
    }

    public static void Main()
    {
        string rnd_str = RandomString(500000);
        Regex regex = new Regex("a|c|e|g|i|k", RegexOptions.Compiled);
        TimeSpan ts1 = MeasureTime.Run(() => regex.Replace(rnd_str, "!!!"), 10);
        Console.WriteLine("Regex time: {0:hh\\:mm\\:ss\\:fff}", ts1);
        
        StringBuilder sb_str = new StringBuilder(rnd_str);
        TimeSpan ts2 = MeasureTime.Run(() => sb_str.Replace("a", "").Replace("c", "").Replace("e", "").Replace("g", "").Replace("i", "").Replace("k", ""), 10);
        Console.WriteLine("StringBuilder time: {0:hh\\:mm\\:ss\\:fff}", ts2);
        
        TimeSpan ts3 = MeasureTime.Run(() => rnd_str.Replace("a", "").Replace("c", "").Replace("e", "").Replace("g", "").Replace("i", "").Replace("k", ""), 10);
        Console.WriteLine("String time: {0:hh\\:mm\\:ss\\:fff}", ts3);

        char[] ch_arr = {'a', 'c', 'e', 'g', 'i', 'k'};
        TimeSpan ts4 = MeasureTime.Run(() => new String((from c in rnd_str where !ch_arr.Contains(c) select c).ToArray()), 10);
        Console.WriteLine("LINQ time: {0:hh\\:mm\\:ss\\:fff}", ts4);
    }

}
|                Method | ItemsToReplace |       Mean |     Error |    StdDev |   Gen 0 |  Gen 1 | Gen 2 | Allocated |
|---------------------- |--------------- |-----------:|----------:|----------:|--------:|-------:|------:|----------:|
|         StringReplace |              3 |  21.493 us | 0.1182 us | 0.1105 us |  3.6926 | 0.0305 |     - |  18.96 KB |
|  StringBuilderReplace |              3 |  35.383 us | 0.1341 us | 0.1119 us |  2.5024 |      - |     - |  13.03 KB |
|          RegExReplace |              3 |  19.620 us | 0.1252 us | 0.1045 us |  3.4485 | 0.0305 |     - |  17.75 KB |
| RegExReplace_Compiled |              3 |   4.573 us | 0.0318 us | 0.0282 us |  2.7084 | 0.0610 |     - |  13.91 KB |
|         StringReplace |             10 |  74.273 us | 0.7900 us | 0.7390 us | 12.2070 | 0.1221 |     - |  62.75 KB |
|  StringBuilderReplace |             10 | 115.322 us | 0.5820 us | 0.5444 us |  2.6855 |      - |     - |  13.84 KB |
|          RegExReplace |             10 |  24.121 us | 0.1130 us | 0.1002 us |  4.4250 | 0.0916 |     - |  22.75 KB |
| RegExReplace_Compiled |             10 |   8.601 us | 0.0298 us | 0.0279 us |  3.6774 | 0.1221 |     - |  18.92 KB |
|         StringReplace |             20 | 150.193 us | 1.4508 us | 1.3571 us | 24.6582 | 0.2441 |     - | 126.89 KB |
|  StringBuilderReplace |             20 | 233.984 us | 1.1707 us | 1.0951 us |  2.9297 |      - |     - |   15.3 KB |
|          RegExReplace |             20 |  28.699 us | 0.1179 us | 0.1045 us |  4.8218 | 0.0916 |     - |  24.79 KB |
| RegExReplace_Compiled |             20 |  12.672 us | 0.0599 us | 0.0560 us |  4.0894 | 0.1221 |     - |  20.95 KB |