C# 使用.Net中的Span将ID从输入字符串解析为长数字
我试图从一个包含不同数字的字符串中解析不同的IDslong,我需要最小化内存分配以提高性能 下面是使用Split提取id的代码,但是我发现我可以使用AsSpan和Splice来做同样的事情,而无需分配内存。但不幸的是,即使在网上查找之后,我也不太熟悉这个Span概念。有谁能插话告诉我怎样才能做到这一点 正如您在下面看到的,输入字符串有3个不同的ID,但我只需要其中的2个,并解析为长类型C# 使用.Net中的Span将ID从输入字符串解析为长数字,c#,.net,parsing,memory,C#,.net,Parsing,Memory,我试图从一个包含不同数字的字符串中解析不同的IDslong,我需要最小化内存分配以提高性能 下面是使用Split提取id的代码,但是我发现我可以使用AsSpan和Splice来做同样的事情,而无需分配内存。但不幸的是,即使在网上查找之后,我也不太熟悉这个Span概念。有谁能插话告诉我怎样才能做到这一点 正如您在下面看到的,输入字符串有3个不同的ID,但我只需要其中的2个,并解析为长类型 string[] machineIdPart; string[] employ
string[] machineIdPart;
string[] employeeIdPart;
long machineId;
long employeeId;
//Input String
var description = "machineId: 276744, engineId: 59440, employeeId: 4619825";
Console.Out.Write(description);
Console.Out.WriteLine();
var infoList = description.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var info in infoList)
{
if (info.TrimStart().StartsWith("machineId", StringComparison.OrdinalIgnoreCase))
{
machineIdPart = info.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
if (machineIdPart.Count() > 1)
{
long.TryParse(machineIdPart[1].Trim(), out machineId);
}
}
if (info.TrimStart().StartsWith("employeeId", StringComparison.OrdinalIgnoreCase))
{
employeeIdPart = info.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
if (employeeIdPart.Count() > 1)
{
long.TryParse(employeeIdPart[1].Trim(), out employeeId);
}
}
}
我想修改此代码以最小化内存分配,因为此方法将非常频繁地运行。解决方案将比当前稍微复杂一些,但不会有更多的字符串分配。适用于.NETCore2.2
long machineId = 0;
long employeeId = 0;
var description = "machineId: 276744, engineId: 59440, employeeId: 4619825";
ReadOnlySpan<char> descriptionSpan = description.AsSpan();
var nameValueBlockStartIndex = 0;
while(nameValueBlockStartIndex < description.Length)
{
var blockEndIndex = description.IndexOf(',', nameValueBlockStartIndex);
if (blockEndIndex == -1)
{
blockEndIndex = description.Length;
}
var namePartEndIndex = description.IndexOf(':', nameValueBlockStartIndex);
var namePartLength = namePartEndIndex - nameValueBlockStartIndex;
var namePart = descriptionSpan.Slice(nameValueBlockStartIndex, namePartLength);
var valuePartStartIndex = namePartEndIndex + 1;
var valuePartLength = blockEndIndex - valuePartStartIndex + 1;
var valuePart = descriptionSpan.Slice(valuePartStartIndex, valuePartLength - 1);
while(namePart[0] == ' ')
{
namePart = namePart.Slice(1);
}
if (namePart.Equals("machineId", StringComparison.OrdinalIgnoreCase))
{
Int64.TryParse(valuePart, out machineId);
}
else if (namePart.Equals("employeeId", StringComparison.OrdinalIgnoreCase))
{
Int64.TryParse(valuePart, out employeeId);
}
nameValueBlockStartIndex = blockEndIndex + 1;
}
此解决方案适用于.NET Core 2.2。它在ReadOnlySpan SplitNext上使用无分配扩展方法 除了字符串处理优化之外,还可以优化实际的解析函数。这将是一个更快的长解析器:
public static long LongParseFast(ReadOnlySpan<char> value) {
long result = 0;
for (int i = 0; i < value.Length; i++) {
result = 10 * result + (value[i] - 48);
}
return result;
}
如果在我的示例中使用,在我的基准测试中,它将性能提高一倍,达到216.0 ns。当然,这个函数不能处理负数、逗号、点和其他语言环境的东西。但是,如果您对此没有意见,这可能是您所能达到的最快速度。您现在是否看到任何性能问题?您会发现,从字符串解析long也不太快。考虑一下,通过跳过字符串寻找,并用x= x* 10 +c-’0来解析自己,避免在一个膨胀的FoopP中的所有字符串操作。如果这仍然是一个性能问题,那么也可以从Span中完成,但在另一个层面上,这也意味着以更有效的方式提取描述。然而,根据Matt-establish,这首先是一个实际的瓶颈,即使您知道它将非常频繁地运行。优化错误的东西是浪费时间。@Matt.G我会说是的。上面的代码将被插入到庞大的处理代码中。在进行上述更改之后,它添加了一个性能开销比较。这并不重要,但仍希望进一步改进以将影响降至最低。@Jeroemostert是的,请参阅我的上述评论。如果您不介意的话,还可以详细介绍一下使用Span的“老式”方法吗?编写一个简单的解析器,跟踪索引位置,而不是实际创建新字符串。对于这样简单的字符串,可以重复调用.IndexOf,不过您也可以编写一个小的状态机开关描述[i]{case':':state=state.ParsingNumber;++i;break;}。其主要思想是让字符串保持原样,而不是创建新的字符串。在不分配任何内容的情况下解析这个字符串可以通过多种方式完成;你甚至不需要为这个深入研究Span。不,对不起,我现在懒得写解析器-汉克斯!这确实是我基准测试中最快的。顺便说一句,您介意告诉我您是如何使用Benchmarkdotnet生成分配内存列的吗?再次感谢!当然只需将ad[MemoryDiagnoser]属性添加到要进行基准测试的类。以下是我的完整基准代码:
BenchmarkDotNet=v0.11.4, OS=Windows 10.0.17763.316 (1809/October2018Update/Redstone5)
Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), 1 CPU, 4 logical and 4 physical cores
.NET Core SDK=2.2.104
[Host] : .NET Core 2.2.2 (CoreCLR 4.6.27317.07, CoreFX 4.6.27318.02), 64bit RyuJIT
DefaultJob : .NET Core 2.2.2 (CoreCLR 4.6.27317.07, CoreFX 4.6.27318.02), 64bit RyuJIT
| Method | Mean | Error | StdDev | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
|--------- |-----------:|----------:|----------:|------------:|------------:|------------:|--------------------:|
| Original | 1,164.1 ns | 11.606 ns | 10.289 ns | 0.2937 | - | - | 928 B |
| Answer | 460.5 ns | 4.527 ns | 4.234 ns | - | - | - | - |
| MyAnswer | 445.7 ns | 2.578 ns | 2.412 ns | - | - | - | - |
public static long LongParseFast(ReadOnlySpan<char> value) {
long result = 0;
for (int i = 0; i < value.Length; i++) {
result = 10 * result + (value[i] - 48);
}
return result;
}