在解析
float
方面,调用哪个重载的
float.Parse()
以及传递给它什么参数都会带来一些收益。我进行了更多比较这些重载的基准测试(请注意,我将小数分隔符从
','
更改为
'.'
,只是为了能够指定
CultureInfo.InvariantCulture
)。
例如,调用接受
IFormatProvider
的重载能够带来约10%的性能提升。 对于
NumberStyles
参数,指定
NumberStyles.Float
(“放宽”)会使性能改变约百分之一,而仅指定
NumberStyles.AllowDecimalPoint
(“严格”)则可以提高几个百分点的性能。 (
float.Parse(string)
重载 使用
NumberStyles.Float | NumberStyles.AllowThousands
。)
关于对输入数据进行假设,如果您知道正在处理的文本具有某些特征(单字节字符编码,无无效数字,无负数、指数,不需要处理
NaN
或
正无穷/
负无穷等),则可以直接从
byte
解析并避免任何不必要的特殊情况处理和错误检查。 我在基准测试中包括了一个非常简单的实现,它能够比
float.Parse(string)
从
string
中获取到
float
更快16倍以上!
以下是我的基准测试结果...
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
组件库)...
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()
{
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()
{
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>();
}
}
}