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