在 float-parsing 方面,根据您调用的 float.Parse() 的重载以及传递给它的内容,可以获得一些收益。我运行了一些比较这些重载的基准测试(请注意,我将小数点分隔符从 ',' 更改为 '.' 只是为了指定 CultureInfo.InvariantCulture)。
例如,调用采用IFormatProvider 的重载可以将性能提高约 10%。为NumberStyles 参数指定NumberStyles.Float(“lax”)会影响性能的变化在任一方向,并且对我们的输入数据进行一些假设,仅指定@987654340 @("strict") 网络性能提高了几分。 (float.Parse(string) overload 使用 NumberStyles.Float | NumberStyles.AllowThousands。)
关于对输入数据进行假设的主题,如果您知道您正在使用的文本具有某些特征(单字节字符编码,没有无效数字,没有负数,没有指数,不需要处理 @987654326 @ 或 positive/negative infinity 等)您最好直接从 bytes 解析并放弃任何不需要的特殊情况处理和错误检查。我在我的基准测试中包含了一个非常简单的实现,它能够从 byte[] 获得 float 比 float.Parse(string) 获得 float 的速度比 float.Parse(string) 快 16 倍 可以从 string 获得 float !
这是我的基准测试结果...
BenchmarkDotNet=v0.11.0, OS=Windows 10.0.17134.165 (1803/April2018Update/Redstone4)
Intel Core i7 CPU 860 2.80GHz (Max: 2.79GHz) (Nehalem), 1 CPU, 8 logical and 4 physical cores
Frequency=2732436 Hz, Resolution=365.9738 ns, Timer=TSC
.NET Core SDK=2.1.202
[Host] : .NET Core 2.0.9 (CoreCLR 4.6.26614.01, CoreFX 4.6.26614.01), 64bit RyuJIT
Clr : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3131.0
Core : .NET Core 2.0.9 (CoreCLR 4.6.26614.01, CoreFX 4.6.26614.01), 64bit RyuJIT
Method | Runtime | Mean | Scaled |
-------------------------------------------------------------- |-------- |-----------:|-------:|
float.Parse(string) | Clr | 145.098 ns | 1.00 |
'float.Parse(string, IFormatProvider)' | Clr | 134.191 ns | 0.92 |
'float.Parse(string, NumberStyles) [Lax]' | Clr | 145.884 ns | 1.01 |
'float.Parse(string, NumberStyles) [Strict]' | Clr | 139.417 ns | 0.96 |
'float.Parse(string, NumberStyles, IFormatProvider) [Lax]' | Clr | 133.800 ns | 0.92 |
'float.Parse(string, NumberStyles, IFormatProvider) [Strict]' | Clr | 127.413 ns | 0.88 |
'Custom byte-to-float parser [Indexer]' | Clr | 7.657 ns | 0.05 |
'Custom byte-to-float parser [Enumerator]' | Clr | 566.440 ns | 3.90 |
| | | |
float.Parse(string) | Core | 154.369 ns | 1.00 |
'float.Parse(string, IFormatProvider)' | Core | 138.668 ns | 0.90 |
'float.Parse(string, NumberStyles) [Lax]' | Core | 155.644 ns | 1.01 |
'float.Parse(string, NumberStyles) [Strict]' | Core | 150.221 ns | 0.97 |
'float.Parse(string, NumberStyles, IFormatProvider) [Lax]' | Core | 142.591 ns | 0.92 |
'float.Parse(string, NumberStyles, IFormatProvider) [Strict]' | Core | 135.000 ns | 0.87 |
'Custom byte-to-float parser [Indexer]' | Core | 12.673 ns | 0.08 |
'Custom byte-to-float parser [Enumerator]' | Core | 584.236 ns | 3.78 |
...从运行此代码(需要BenchmarkDotNet assembly)...
using System;
using System.Globalization;
using BenchmarkDotNet.Attributes;
namespace StackOverflow_51584129
{
[ClrJob()]
[CoreJob()]
public class FloatParsingBenchmarks
{
private const string InputString = "132.29";
private static readonly byte[] InputBytes = System.Text.Encoding.ASCII.GetBytes(InputString);
private static readonly IFormatProvider ParsingFormatProvider = CultureInfo.InvariantCulture;
private const NumberStyles LaxParsingNumberStyles = NumberStyles.Float;
private const NumberStyles StrictParsingNumberStyles = NumberStyles.AllowDecimalPoint;
private const char DecimalSeparator = '.';
[Benchmark(Baseline = true, Description = "float.Parse(string)")]
public float SystemFloatParse()
{
return float.Parse(InputString);
}
[Benchmark(Description = "float.Parse(string, IFormatProvider)")]
public float SystemFloatParseWithProvider()
{
return float.Parse(InputString, CultureInfo.InvariantCulture);
}
[Benchmark(Description = "float.Parse(string, NumberStyles) [Lax]")]
public float SystemFloatParseWithLaxNumberStyles()
{
return float.Parse(InputString, LaxParsingNumberStyles);
}
[Benchmark(Description = "float.Parse(string, NumberStyles) [Strict]")]
public float SystemFloatParseWithStrictNumberStyles()
{
return float.Parse(InputString, StrictParsingNumberStyles);
}
[Benchmark(Description = "float.Parse(string, NumberStyles, IFormatProvider) [Lax]")]
public float SystemFloatParseWithLaxNumberStylesAndProvider()
{
return float.Parse(InputString, LaxParsingNumberStyles, ParsingFormatProvider);
}
[Benchmark(Description = "float.Parse(string, NumberStyles, IFormatProvider) [Strict]")]
public float SystemFloatParseWithStrictNumberStylesAndProvider()
{
return float.Parse(InputString, StrictParsingNumberStyles, ParsingFormatProvider);
}
[Benchmark(Description = "Custom byte-to-float parser [Indexer]")]
public float CustomFloatParseByIndexing()
{
// FOR DEMONSTRATION PURPOSES ONLY!
// This code has been written for and only tested with
// parsing the ASCII string "132.29" in byte form
var currentIndex = 0;
var boundaryIndex = InputBytes.Length;
char currentChar;
var wholePart = 0;
while (currentIndex < boundaryIndex && (currentChar = (char) InputBytes[currentIndex++]) != DecimalSeparator)
{
var currentDigit = currentChar - '0';
wholePart = 10 * wholePart + currentDigit;
}
var fractionalPart = 0F;
var nextFractionalDigitScale = 0.1F;
while (currentIndex < boundaryIndex)
{
currentChar = (char) InputBytes[currentIndex++];
var currentDigit = currentChar - '0';
fractionalPart += currentDigit * nextFractionalDigitScale;
nextFractionalDigitScale *= 0.1F;
}
return wholePart + fractionalPart;
}
[Benchmark(Description = "Custom byte-to-float parser [Enumerator]")]
public float CustomFloatParseByEnumerating()
{
// FOR DEMONSTRATION PURPOSES ONLY!
// This code has been written for and only tested with
// parsing the ASCII string "132.29" in byte form
var wholePart = 0;
var enumerator = InputBytes.GetEnumerator();
while (enumerator.MoveNext())
{
var currentChar = (char) (byte) enumerator.Current;
if (currentChar == DecimalSeparator)
break;
var currentDigit = currentChar - '0';
wholePart = 10 * wholePart + currentDigit;
}
var fractionalPart = 0F;
var nextFractionalDigitScale = 0.1F;
while (enumerator.MoveNext())
{
var currentChar = (char) (byte) enumerator.Current;
var currentDigit = currentChar - '0';
fractionalPart += currentDigit * nextFractionalDigitScale;
nextFractionalDigitScale *= 0.1F;
}
return wholePart + fractionalPart;
}
public static void Main()
{
BenchmarkDotNet.Running.BenchmarkRunner.Run<FloatParsingBenchmarks>();
}
}
}