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

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); 

我inheritance了一些与上面的代码片段相同的代码。 它需要一个巨大的string,并从大string中replace(删除)常量较小的string。

我相信这是一个内存密集型的过程,因为每个replace内存都会分配新的大型不可变string,以便通过GC等待死亡。

1.什么是取代这些价值观的最快方式,忽略了记忆的顾虑?

2.达到相同结果的最有效的内存方式是什么?

我希望这些答案是一样的!

适合这些目标之间的实用解决scheme也是值得赞赏的。

假设:

  • 所有替代品都是不变的,并且事先已知
  • 底层字符包含一些unicode [非ASCII]字符

.NETstring中的所有字符都是“unicode chars”。 你的意思是他们不是ascii? 这不应该有任何的可能性 – 除非你遇到组成问题,例如,当您尝试replace“e急性”时不会被replace的“e +急性口音”。

您可以尝试使用正则expression式与Regex.ReplaceStringBuilder.Replace 。 以下是两个示例代码:

 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); } } 

我不想猜测哪个更有效率 – 你必须用你的具体应用程序进行基准testing。 正则expression式的方式可能能够一次完成,但与StringBuilder中的许多replace相比,这个过程相对CPU密集。

如果你想要真的很快,而且我的意思是非常快,那么你将不得不超越StringBuilder,只编写优化的代码。

有一件事你的电脑不喜欢做的是分支,如果你可以写一个固定的数组(char *)上运行的replace方法,而不是分支你有很好的performance。

你将要做的是,replace操作将search一个字符序列,如果它发现任何这样的子string将取代它。 实际上,你会复制string,当这样做时,执行查找和replace。

你将依靠这些函数来select一些缓冲区的索引来读/写。 目标是预成型replace方法,以便当没有什么必须改变你写垃圾,而不是分支。

你应该能够完成这个没有一个单一的if语句,并记住使用不安全的代码。 否则,你将支付每个元素访问的索引检查。

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

我已经在C#中编写了这样的代码,以获得显着的性能改进,几乎可以加快查找和replace的速度。 虽然.NET BCL(基类库)执行得相当好,但它却充斥着分支结构和exception处理,如果使用内置的东西,这会减慢你的代码。 另外,这些优化虽然完美无缺,但并不是由JIT编译器执行的,您必须将代码作为发布版本运行,而无需附加任何debugging器即可观察到巨大的性能增益。

我可以为您提供更完整的代码,但这是一个相当大的工作量。 不过,我可以向你保证,这将比迄今为止提出的任何其他build议都快。

StringBuilder: http : //msdn.microsoft.com/en-us/library/2839d5h5.aspx

Replace操作本身的性能应该与string大致相同。replace并根据Microsoft,不应该产生垃圾。

这是一个快速的基准…

  Stopwatch s = new Stopwatch(); s.Start(); string replace = source; replace = replace.Replace("$TS$", tsValue); replace = replace.Replace("$DOC$", docValue); s.Stop(); Console.WriteLine("String.Replace:\t\t" + s.ElapsedMilliseconds); s.Reset(); s.Start(); StringBuilder sb = new StringBuilder(source); sb = sb.Replace("$TS$", tsValue); sb = sb.Replace("$DOC$", docValue); string output = sb.ToString(); s.Stop(); Console.WriteLine("StringBuilder.Replace:\t\t" + s.ElapsedMilliseconds); 

我没有看到我的机器(string.replace是85毫秒和stringbuilder.replace是80)有很大的不同,这是对“源”约8MB的文字…

1.什么是取代这些价值观的最快方式,忽略了记忆的顾虑?

最快的方法是构build特定于您的用例的自定义组件。 从.NET 4.6开始,BCL中没有为多stringreplacedevise的类。

如果你需要从BCL中快速出来,StringBuilder是用于简单stringreplace的最快的BCL组件。 源代码可以在这里find:replace单个string非常高效。 如果你确实需要正则expression式的模式匹配能力,只能使用Regex。 即使编译,速度也会更慢,更麻烦一些。

2.达到相同结果的最有效的内存方式是什么?

最具有内存效率的方法是执行从源到目标的过滤stream拷贝(如下所述)。 内存消耗将被限制在您的缓冲区中,但是这会占用更多的CPU资源; 作为一个经验法则,你会交换内存消耗的CPU性能。

技术细节

stringreplace是棘手的。 即使在可变的内存空间(如使用StringBuilder )中执行stringreplace时,也很昂贵。 如果replacestring的长度与原始string不同,那么您将重新定位replacestring后面的每个字符,以保持整个string的连续性。 这导致大量内存写入,即使在StringBuilder的情况下,也会导致您在每次调用Replace时重写大部分内存中的string。

那么做stringreplace的最快方法是什么? 使用单通道编写新的string:不要让代码返回,而必须重新写入任何内容。 写入比读取更昂贵。 你将不得不自己编码,以获得最佳结果。

高内存解决scheme

我写的类生成基于模板的string。 我将标记($ ReplaceMe $)放在一个模板中,标记稍后要插入string的位置。 我在XmlWriter对于大部分是静态和重复的XML过于繁重的情况下使用它,并且需要生成大量的XML(或JSON)数据stream。

该类通过将模板分成几部分并将每个部分放入一个编号的字典中来工作。 参数也被枚举。 将部件和参数插入到新string中的顺序放置在整数数组中。 当生成新的string时,将从字典中选取部分和参数,并用于创build新的string。

它既不是完全优化的,也不是防弹的,但是对于从模板生成非常大的数据stream非常有用。

低内存解决scheme

您需要从源string读取小块到缓冲区,使用优化的searchalgorithmsearch缓冲区,然后将新string写入目标stream/string。 这里有很多潜在的警告,但是对于源数据是dynamic的并且不能被caching的,例如整页翻译或源数据太大而不能合理caching,这将是高效的内存和更好的解决scheme。 我没有这个方便的示例解决scheme。

示例代码

期望的结果

 <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> 

testing用例

 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]); } } 

StringTemplate来源

 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); } } 
 StringBuilder sb = new StringBuilder("Hello string"); sb.Replace("string", String.Empty); Console.WriteLine(sb); 

StringBuilder ,一个可变的string。

如果你想在dotnet中build立一个类,我认为StringBuilder是最好的。 为了使它可以manully你可以使用char *不安全的代码,并遍历你的string,并根据您的条件进行replace

既然你有一个string的多个replace,我wolud推荐你使用RegEx而不是StringBuilder。