将一个格式为float的char[]转换成float

4

我有一个包含由 string 提供的数据的 char[] salary。我想把 char[] salary 转换为 float,但是我尝试的方法似乎非常慢,我的做法是:

float ff = float.Parse(new string(salary));

根据Visual Studio的性能分析器显示,这个处理过程占用了太多的处理时间: enter image description here 因此,我想知道是否有更快的方式来完成这个操作,因为性能很重要。这里的char[]格式如下:
[ '1', '3', '2', ',', '2', '9']

这基本上是将像JSON一样的浮点数转换为每个数字(和逗号)放入char[]中。

编辑:

我重新格式化了代码,似乎性能损失实际上是从char[]string的转换,而不是从stringfloat的解析。


它是来自用户还是系统?如果是系统,您可以提供文化信息,这可以加快float.parse的速度。例如:float numFloat = float.Parse(System.Globalization.CultureInfo.InvariantInfo, strFloat); - Glenn Watson
只是想知道为什么它以char数组的形式而不是字符串的形式出现。如果没有其他信息,分析器的值就不会提供太多信息。例如,30%的毫秒并不算太多。这真的是一个性能问题吗? - Sami Kuhmonen
信息来自一个 .json 文件。然后,它被读入一个 byte[],接着从 byte[] 中提取代表 float 的部分到 char[] 中。性能是一个问题,因为我正在处理 3000 万以上的条目。 - Washington A. Ramos
如果你已经费心编写了一个自定义的JSON解析器,为什么不也为你的char[]格式编写一个自定义的解析方法呢? - Chris Rollins
1
你在编译器的_release_模式下测量时间了吗?使用_debug_模式可能会导致巨大的时间差异。 - martinstoeckli
显示剩余3条评论
4个回答

7
自从这个问题从“解析 float 的最快方法是什么?”变成了“从 char[] 中获取 string 的最快方法是什么?”,我使用 BenchmarkDotNet 编写了一些基准测试来比较各种方法。 我的发现是,如果你已经有一个 char[],那么你不能比使用已经在使用的 string(char[]) 构造函数更快。
你说你的输入文件“被读入到一个 byte[] 中,然后该 byte[] 的表示 float 的部分被提取到一个 char[] 中。” 由于你已经将组成 float 文本的 byte 隔离在一个 byte[] 中,也许你可以跳过中间的 char[] 来提高性能。 假设你有类似于...
byte[] floatBytes = new byte[] { 0x31, 0x33, 0x32, 0x2C, 0x32, 0x39 }; // "132,29"

...你可以使用Encoding.GetString()...

string floatString = Encoding.ASCII.GetString(floatBytes);

...这几乎比将 Encoding.GetChars() 的结果传递给 string(char[]) 构造函数快了近两倍...

char[] floatChars = Encoding.ASCII.GetChars(floatBytes);
string floatString = new string(floatChars);

您会发现这些基准测试在我的结果中排在最后...

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 |       Categories |      Mean | Scaled |
----------------------------------------------------- |-------- |----------------- |----------:|-------:|
                         String_Constructor_CharArray |     Clr | char[] => string |  13.51 ns |   1.00 |
                                        String_Concat |     Clr | char[] => string | 192.87 ns |  14.27 |
 StringBuilder_Local_AppendSingleChar_DefaultCapacity |     Clr | char[] => string |  60.74 ns |   4.49 |
   StringBuilder_Local_AppendSingleChar_ExactCapacity |     Clr | char[] => string |  60.26 ns |   4.46 |
   StringBuilder_Local_AppendAllChars_DefaultCapacity |     Clr | char[] => string |  51.27 ns |   3.79 |
     StringBuilder_Local_AppendAllChars_ExactCapacity |     Clr | char[] => string |  49.51 ns |   3.66 |
                 StringBuilder_Field_AppendSingleChar |     Clr | char[] => string |  51.14 ns |   3.78 |
                   StringBuilder_Field_AppendAllChars |     Clr | char[] => string |  32.95 ns |   2.44 |
                                                      |         |                  |           |        |
                       String_Constructor_CharPointer |     Clr |  void* => string |  29.28 ns |   1.00 |
                      String_Constructor_SBytePointer |     Clr |  void* => string |  89.21 ns |   3.05 |
                   UnsafeArrayCopy_String_Constructor |     Clr |  void* => string |  42.82 ns |   1.46 |
                                                      |         |                  |           |        |
                                   Encoding_GetString |     Clr | byte[] => string |  37.33 ns |   1.00 |
                 Encoding_GetChars_String_Constructor |     Clr | byte[] => string |  60.83 ns |   1.63 |
                     SafeArrayCopy_String_Constructor |     Clr | byte[] => string |  27.55 ns |   0.74 |
                                                      |         |                  |           |        |
                         String_Constructor_CharArray |    Core | char[] => string |  13.27 ns |   1.00 |
                                        String_Concat |    Core | char[] => string | 172.17 ns |  12.97 |
 StringBuilder_Local_AppendSingleChar_DefaultCapacity |    Core | char[] => string |  58.68 ns |   4.42 |
   StringBuilder_Local_AppendSingleChar_ExactCapacity |    Core | char[] => string |  59.85 ns |   4.51 |
   StringBuilder_Local_AppendAllChars_DefaultCapacity |    Core | char[] => string |  40.62 ns |   3.06 |
     StringBuilder_Local_AppendAllChars_ExactCapacity |    Core | char[] => string |  43.67 ns |   3.29 |
                 StringBuilder_Field_AppendSingleChar |    Core | char[] => string |  54.49 ns |   4.11 |
                   StringBuilder_Field_AppendAllChars |    Core | char[] => string |  31.05 ns |   2.34 |
                                                      |         |                  |           |        |
                       String_Constructor_CharPointer |    Core |  void* => string |  22.87 ns |   1.00 |
                      String_Constructor_SBytePointer |    Core |  void* => string |  83.11 ns |   3.63 |
                   UnsafeArrayCopy_String_Constructor |    Core |  void* => string |  35.30 ns |   1.54 |
                                                      |         |                  |           |        |
                                   Encoding_GetString |    Core | byte[] => string |  36.19 ns |   1.00 |
                 Encoding_GetChars_String_Constructor |    Core | byte[] => string |  58.99 ns |   1.63 |
                     SafeArrayCopy_String_Constructor |    Core | byte[] => string |  27.81 ns |   0.77 |

...运行此代码需要BenchmarkDotNet程序集并使用/unsafe编译...

using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using BenchmarkDotNet.Attributes;

namespace StackOverflow_51584129
{
    [CategoriesColumn()]
    [ClrJob()]
    [CoreJob()]
    [GroupBenchmarksBy(BenchmarkDotNet.Configs.BenchmarkLogicalGroupRule.ByCategory)]
    public class StringCreationBenchmarks
    {
        private static readonly Encoding InputEncoding = Encoding.ASCII;

        private const string InputString = "132,29";
        private static readonly byte[] InputBytes = InputEncoding.GetBytes(InputString);
        private static readonly char[] InputChars = InputString.ToCharArray();
        private static readonly sbyte[] InputSBytes = InputBytes.Select(Convert.ToSByte).ToArray();

        private GCHandle _inputBytesHandle;
        private GCHandle _inputCharsHandle;
        private GCHandle _inputSBytesHandle;

        private StringBuilder _builder;

        [Benchmark(Baseline = true)]
        [BenchmarkCategory("char[] => string")]
        public string String_Constructor_CharArray()
        {
            return new string(InputChars);
        }

        [Benchmark(Baseline = true)]
        [BenchmarkCategory("void* => string")]
        public unsafe string String_Constructor_CharPointer()
        {
            var pointer = (char*) _inputCharsHandle.AddrOfPinnedObject();

            return new string(pointer);
        }

        [Benchmark()]
        [BenchmarkCategory("void* => string")]
        public unsafe string String_Constructor_SBytePointer()
        {
            var pointer = (sbyte*) _inputSBytesHandle.AddrOfPinnedObject();

            return new string(pointer);
        }

        [Benchmark()]
        [BenchmarkCategory("char[] => string")]
        public string String_Concat()
        {
            return string.Concat(InputChars);
        }

        [Benchmark()]
        [BenchmarkCategory("char[] => string")]
        public string StringBuilder_Local_AppendSingleChar_DefaultCapacity()
        {
            var builder = new StringBuilder();

            foreach (var c in InputChars)
                builder.Append(c);

            return builder.ToString();
        }

        [Benchmark()]
        [BenchmarkCategory("char[] => string")]
        public string StringBuilder_Local_AppendSingleChar_ExactCapacity()
        {
            var builder = new StringBuilder(InputChars.Length);

            foreach (var c in InputChars)
                builder.Append(c);

            return builder.ToString();
        }

        [Benchmark()]
        [BenchmarkCategory("char[] => string")]
        public string StringBuilder_Local_AppendAllChars_DefaultCapacity()
        {
            var builder = new StringBuilder().Append(InputChars);

            return builder.ToString();
        }

        [Benchmark()]
        [BenchmarkCategory("char[] => string")]
        public string StringBuilder_Local_AppendAllChars_ExactCapacity()
        {
            var builder = new StringBuilder(InputChars.Length).Append(InputChars);

            return builder.ToString();
        }

        [Benchmark()]
        [BenchmarkCategory("char[] => string")]
        public string StringBuilder_Field_AppendSingleChar()
        {
            _builder.Clear();

            foreach (var c in InputChars)
                _builder.Append(c);

            return _builder.ToString();
        }

        [Benchmark()]
        [BenchmarkCategory("char[] => string")]
        public string StringBuilder_Field_AppendAllChars()
        {
            return _builder.Clear().Append(InputChars).ToString();
        }

        [Benchmark(Baseline = true)]
        [BenchmarkCategory("byte[] => string")]
        public string Encoding_GetString()
        {
            return InputEncoding.GetString(InputBytes);
        }

        [Benchmark()]
        [BenchmarkCategory("byte[] => string")]
        public string Encoding_GetChars_String_Constructor()
        {
            var chars = InputEncoding.GetChars(InputBytes);

            return new string(chars);
        }

        [Benchmark()]
        [BenchmarkCategory("byte[] => string")]
        public string SafeArrayCopy_String_Constructor()
        {
            var chars = new char[InputString.Length];

            for (int i = 0; i < InputString.Length; i++)
                chars[i] = Convert.ToChar(InputBytes[i]);

            return new string(chars);
        }

        [Benchmark()]
        [BenchmarkCategory("void* => string")]
        public unsafe string UnsafeArrayCopy_String_Constructor()
        {
            fixed (char* chars = new char[InputString.Length])
            {
                var bytes = (byte*) _inputBytesHandle.AddrOfPinnedObject();

                for (int i = 0; i < InputString.Length; i++)
                    chars[i] = Convert.ToChar(bytes[i]);

                return new string(chars);
            }
        }

        [GlobalSetup(Targets = new[] { nameof(StringBuilder_Field_AppendAllChars), nameof(StringBuilder_Field_AppendSingleChar) })]
        public void SetupStringBuilderField()
        {
            _builder = new StringBuilder();
        }

        [GlobalSetup(Target = nameof(UnsafeArrayCopy_String_Constructor))]
        public void SetupBytesHandle()
        {
            _inputBytesHandle = GCHandle.Alloc(InputBytes, GCHandleType.Pinned);
        }

        [GlobalCleanup(Target = nameof(UnsafeArrayCopy_String_Constructor))]
        public void CleanupBytesHandle()
        {
            _inputBytesHandle.Free();
        }

        [GlobalSetup(Target = nameof(String_Constructor_CharPointer))]
        public void SetupCharsHandle()
        {
            _inputCharsHandle = GCHandle.Alloc(InputChars, GCHandleType.Pinned);
        }

        [GlobalCleanup(Target = nameof(String_Constructor_CharPointer))]
        public void CleanupCharsHandle()
        {
            _inputCharsHandle.Free();
        }

        [GlobalSetup(Target = nameof(String_Constructor_SBytePointer))]
        public void SetupSByteHandle()
        {
            _inputSBytesHandle = GCHandle.Alloc(InputSBytes, GCHandleType.Pinned);
        }

        [GlobalCleanup(Target = nameof(String_Constructor_SBytePointer))]
        public void CleanupSByteHandle()
        {
            _inputSBytesHandle.Free();
        }

        public static void Main(string[] args)
        {
            BenchmarkDotNet.Running.BenchmarkRunner.Run<StringCreationBenchmarks>();
        }
    }
}

干得好!得记住这个基准库。 - martinstoeckli
谢谢。这是我第一次尝试使用任何基准库。很高兴它相当简单易用,使你能够编写仅包含待测试代码的基准方法。另一方面,我发现它非常强大(复杂),文档有些欠缺,因此尝试弄清楚控制什么以及如何从默认值更改事物真的变得相当繁琐(特别是每次运行需要一段时间才能看到刚刚更改的结果),所以我最终放弃了对其进行调整。 - Lance U. Matthews

5
在解析 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()
        {
            // 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>();
        }
    }
}

在你进行基准测试时,需要注意一点,我不确定他的目标 .NET 版本是什么。在某些情况下,.NET Core 可能与 .NET 4.7.2 具有不同的特性。使用 Dot Net Benchmark 工具时,可以同时测试多个框架版本。 - Glenn Watson
@GlennWatson 是的,我已经配置它来测试 .NET Framework 和 Core,但是 Framework 的结果都返回为 NA。那时我不想再折腾了(请参见我其他答案的评论),而且正如你所指出的,我不知道作者没有使用 Core,因此我将 Framework 数字留给作者/读者练习。也许我会再试一次,以填充我的答案并提供更多数据。 - Lance U. Matthews
根据FAQ的说明,我只需要对我的.NET Framework项目文件进行一些小修改就可以运行基准测试了。我已经更新了答案,提供了新的Framework和Core基准测试结果。 - Lance U. Matthews
干得好。为了登录,我使用了以下代码: float sgn = 1; while (currentIndex < boundaryIndex && (currentChar = (char)InputBytes[currentIndex++]) != DecimalSeparator) { if (currentChar == '-') sgn *= -1; else { var currentDigit = currentChar - '0'; wholePart = 10 * wholePart + currentDigit; } } ..... 最后将结果乘以 sgn ..... - Goodies

2

有意思的话题,可以在家里进行优化细节的工作 :) 祝大家身体健康。

我的目标是:将Ascii CSV矩阵尽可能快地转换为C#浮点矩阵。为此,使用string.Split()分割行并单独转换每个项也会引入开销。为了克服这个问题,我修改了BACON的解决方案来解析我的浮点数行,像这样使用:

  var falist = new List<float[]>();
  for (int row=0; row<sRowList.Count; row++)
  {
    var sRow = sRowList[row];
    falist.Add(CustomFloatParseRowByIndexing(nTerms, sRow.ToCharArray(), '.'));
  }

以下是我的行解析器变体的代码。这些是基准测试结果,将一个40x31矩阵转换为1000x:

基准测试0:拆分行并解析每个条目以转换为浮点矩阵 dT = 704毫秒

基准测试1:拆分行并尝试解析每个条目以转换为浮点矩阵 dT = 640毫秒

基准测试2:拆分行并使用CustomFloatParseByIndexing将术语转换为浮点矩阵 dT = 211毫秒

基准测试3:使用CustomFloatParseRowByIndexing将行转换为浮点矩阵 dT = 120毫秒

public float[] CustomFloatParseRowByIndexing(int nItems, char[] InputBytes, char   DecimalSeparator)
{
// Convert semicolon-separated floats from InputBytes into nItems float[] result.
// Constraints are:
//   - no scientific notation or .x allowed
//   - every row has exactly nItems values
//   - semicolon delimiter after each value
//   - terms 'u'  or 'undef' or 'undefined' allowed for bad values
//   - minus sign allowed
//   - leading space allowed
//   - all terms must comply

// FOR DEMO PURPOSE ONLY
// based on BACON on Stackoverflow, modified to read nItems delimited float values
// https://dev59.com/1q3la4cB1Zd3GeqPJ0BD

var currentIndex = 0;
var boundaryIndex = InputBytes.Length;
bool termready, ready = false;
float[] result = new float[nItems];
int cItem = 0;
while (currentIndex < boundaryIndex)
{
    termready = false;
    if ((char)InputBytes[currentIndex] == ' ') { currentIndex++; continue; }
    char currentChar;
    var wholePart = 0;
    float sgn = 1;
    while (currentIndex < boundaryIndex && (currentChar = (char)InputBytes[currentIndex++]) != DecimalSeparator)
    {
        if (currentChar == 'u')
        {
            while ((char)InputBytes[currentIndex++] != ';') ;
            result[cItem++] = -9999.0f;
            continue;
        }
        else
        if (currentChar == ' ')
        {                       
            continue;
        }
        else
        if (currentChar == ';')
        {
            termready = true;
            break;
        }
        else
        if (currentChar == '-') sgn = -1;
        else
        {
            var currentDigit = currentChar - '0';
            wholePart = 10 * wholePart + currentDigit;
        }
    }
    var fractionalPart = 0F;
    var nextFractionalDigitScale = 0.1F;
    if (!termready)
        while (currentIndex < boundaryIndex)
        {
            currentChar = (char)InputBytes[currentIndex++];
            if (currentChar == ';')
            {
                termready = true;
                break;
            }
            var currentDigit = currentChar - '0';
            fractionalPart += currentDigit * nextFractionalDigitScale;
            nextFractionalDigitScale *= 0.1F;
        }
    if (termready) 
    { 
      result[cItem++] = sgn * (wholePart + fractionalPart); 
    }
  }   
  return result;
}

2

经过一些实验和 这篇文章 的测试:

stringchar[] 中获得的最快方法是使用 new string

另外需要注意的是,根据 Microsoft 的文章,在输入非法的情况下,TryParse 是解析浮点数最快的方法。所以,请考虑使用它。

在执行时间中,TryParse 仅占 .5%,而 Parse 占用 18%,Convert 占用 14%


1
你的引用被断章取义了。在这句话之前是“如下所示,当你使用错误数据时,你可以看到TryParse、Parse和ConvertTo之间的巨大差异。”(强调我的)而在这句话之后是“正如我们猜测的那样,区别在于异常处理代码。”因此,这只是表明TryParse在返回false方面比Parse抛出异常要快得多,这已经是众所周知的。此外,文章中的数字是针对解析int而不是float的。 - Lance U. Matthews
1
与同一问题的另一个答案声称“如果您实际上拥有char数组,即使将其作为IEnumerable传递,调用string构造函数也会更快”,并且测试结果表明,即使在char[]上不必要地调用.ToArray(),string构造函数仍比使用StringBuilder快得多。这是重要的,因为其他问题正在询问如何从IEnumerable<char>创建字符串,而在此问题中,我们有一个char[]。 - Lance U. Matthews
1
那个答案也谈到了如何从IEnumerable<char>创建一个string。但是在这个问题中,我们拥有更好的东西:一个char[],它的长度是已知的。(当然,char[]实现了IEnumerable<char>。)你不能仅通过测试接口并将其作为所有实现该接口的类型都成立来得出结论。你需要比较在操作**char[]**时new string(char[])StringBuilder.Append(char)的差异,而不是在需要调用.ToArray()扩展方法的IEnumerable<char>上进行操作。 - Lance U. Matthews
1
StringBuilder甚至可以接受字符数组,因此sb.Append(a);会更快。但我觉得很难相信字符串构造函数不应该被优化。也许现在是时候进行自己的测试了,考虑到BACON的反对意见,并使用编译器模式“release”运行它们。 - martinstoeckli
2
请查看我对链接答案的评论。虽然它得出了正确的结论,但代码存在错误,结果(在此引用)无效,而且它也没有测试相同的场景。不是针对你,但这就是为什么在引用数据之前查看数据的上下文和来源很重要的原因。StringBuilder很可能是从char[]获取string的最快方法,但我已经给这个答案投了反对票,因为它和其支持评论三次引用或链接到误导性信息,这并不有帮助。 - Lance U. Matthews
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接