Подтвердить что ты не робот

Более быстрый синтаксический анализ чисел в .NET.

Я написал две функции, которые преобразуют строку целых чисел, разделенных пробелами, в массив int. Первая функция использует Substring, а затем применяет System.Int32.Parse для преобразования подстроки в значение int:

let intsOfString (s: string) =
  let ints = ResizeArray()
  let rec inside i j =
    if j = s.Length then
      ints.Add(s.Substring(i, j-i) |> System.Int32.Parse)
    else
      let c = s.[j]
      if '0' <= c && c <= '9' then
        inside i (j+1)
      else
        ints.Add(s.Substring(i, j-i) |> System.Int32.Parse)
        outside (j+1)
  and outside i =
    if i < s.Length then
      let c = s.[i]
      if '0' <= c && c <= '9' then
        inside i (i+1)
      else
        outside (i+1)
  outside 0
  ints.ToArray()

Вторая функция пересекает символы строки на месте, аккумулируя целое число без создания временной подстроки:

let intsOfString (s: string) =
  let ints = ResizeArray()
  let rec inside n i =
    if i = s.Length then
      ints.Add n
    else
      let c = s.[i]
      if '0' <= c && c <= '9' then
        inside (10*n + int c - 48) (i+1)
      else
        ints.Add n
        outside(i+1)
  and outside i =
    if i < s.Length then
      let c = s.[i]
      if '0' <= c && c <= '9' then
        inside (int c - 48) (i+1)
      else
        outside (i+1)
  outside 0
  ints.ToArray()

Бенчмаркинг по целым числам от 1 до 1 000 000, первая версия занимает 1,5 секунды, тогда как вторая версия занимает 0,3 с.

Анализ таких значений может быть критичным по производительности, поэтому сохранение 5-кратной производительности в таблице с использованием временных подстрок может быть нежелательным. Разделение целых чисел легко, но синтаксический анализ других значений, таких как числа с плавающей запятой, десятичные знаки и даты, значительно сложнее.

Итак, существуют ли встроенные функции для синтаксического анализа непосредственно из подстроки внутри строки (т.е. с использованием заданного начала и длины строки), чтобы избежать генерации временной строки? Если нет, существуют ли библиотеки, которые предоставляют эффективные функции для этого?

4b9b3361

Ответ 1

System.Int32.Parse является самым медленным, поскольку он использовал CultureInfo, FormatInfo и т.д.; и причина производительности не во временных строках.

Код от отражения:

private unsafe static bool ParseNumber(ref char* str, NumberStyles options, ref Number.NumberBuffer number, NumberFormatInfo numfmt, bool parseDecimal)
{
    number.scale = 0;
    number.sign = false;
    string text = null;
    string text2 = null;
    string str2 = null;
    string str3 = null;
    bool flag = false;
    string str4;
    string str5;
    if ((options & NumberStyles.AllowCurrencySymbol) != NumberStyles.None)
    {
        text = numfmt.CurrencySymbol;
        if (numfmt.ansiCurrencySymbol != null)
        {
            text2 = numfmt.ansiCurrencySymbol;
        }
        str2 = numfmt.NumberDecimalSeparator;
        str3 = numfmt.NumberGroupSeparator;
        str4 = numfmt.CurrencyDecimalSeparator;
        str5 = numfmt.CurrencyGroupSeparator;
        flag = true;
    }
    else
    {
        str4 = numfmt.NumberDecimalSeparator;
        str5 = numfmt.NumberGroupSeparator;
    }
    int num = 0;
    char* ptr = str;
    char c = *ptr;
    while (true)
    {
        if (!Number.IsWhite(c) || (options & NumberStyles.AllowLeadingWhite) == NumberStyles.None || ((num & 1) != 0 && ((num & 1) == 0 || ((num & 32) == 0 && numfmt.numberNegativePattern != 2))))
        {
            bool flag2;
            char* ptr2;
            if ((flag2 = (((options & NumberStyles.AllowLeadingSign) == NumberStyles.None) ? false : ((num & 1) == 0))) && (ptr2 = Number.MatchChars(ptr, numfmt.positiveSign)) != null)
            {
                num |= 1;
                ptr = ptr2 - (IntPtr)2 / 2;
            }
            else
            {
                if (flag2 && (ptr2 = Number.MatchChars(ptr, numfmt.negativeSign)) != null)
                {
                    num |= 1;
                    number.sign = true;
                    ptr = ptr2 - (IntPtr)2 / 2;
                }
                else
                {
                    if (c == '(' && (options & NumberStyles.AllowParentheses) != NumberStyles.None && (num & 1) == 0)
                    {
                        num |= 3;
                        number.sign = true;
                    }
                    else
                    {
                        if ((text == null || (ptr2 = Number.MatchChars(ptr, text)) == null) && (text2 == null || (ptr2 = Number.MatchChars(ptr, text2)) == null))
                        {
                            break;
                        }
                        num |= 32;
                        text = null;
                        text2 = null;
                        ptr = ptr2 - (IntPtr)2 / 2;
                    }
                }
            }
        }
        c = *(ptr += (IntPtr)2 / 2);
    }
    int num2 = 0;
    int num3 = 0;
    while (true)
    {
        if ((c >= '0' && c <= '9') || ((options & NumberStyles.AllowHexSpecifier) != NumberStyles.None && ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))))
        {
            num |= 4;
            if (c != '0' || (num & 8) != 0)
            {
                if (num2 < 50)
                {
                    number.digits[(IntPtr)(num2++)] = c;
                    if (c != '0' || parseDecimal)
                    {
                        num3 = num2;
                    }
                }
                if ((num & 16) == 0)
                {
                    number.scale++;
                }
                num |= 8;
            }
            else
            {
                if ((num & 16) != 0)
                {
                    number.scale--;
                }
            }
        }
        else
        {
            char* ptr2;
            if ((options & NumberStyles.AllowDecimalPoint) != NumberStyles.None && (num & 16) == 0 && ((ptr2 = Number.MatchChars(ptr, str4)) != null || (flag && (num & 32) == 0 && (ptr2 = Number.MatchChars(ptr, str2)) != null)))
            {
                num |= 16;
                ptr = ptr2 - (IntPtr)2 / 2;
            }
            else
            {
                if ((options & NumberStyles.AllowThousands) == NumberStyles.None || (num & 4) == 0 || (num & 16) != 0 || ((ptr2 = Number.MatchChars(ptr, str5)) == null && (!flag || (num & 32) != 0 || (ptr2 = Number.MatchChars(ptr, str3)) == null)))
                {
                    break;
                }
                ptr = ptr2 - (IntPtr)2 / 2;
            }
        }
        c = *(ptr += (IntPtr)2 / 2);
    }
    bool flag3 = false;
    number.precision = num3;
    number.digits[(IntPtr)num3] = '\0';
    if ((num & 4) != 0)
    {
        if ((c == 'E' || c == 'e') && (options & NumberStyles.AllowExponent) != NumberStyles.None)
        {
            char* ptr3 = ptr;
            c = *(ptr += (IntPtr)2 / 2);
            char* ptr2;
            if ((ptr2 = Number.MatchChars(ptr, numfmt.positiveSign)) != null)
            {
                c = *(ptr = ptr2);
            }
            else
            {
                if ((ptr2 = Number.MatchChars(ptr, numfmt.negativeSign)) != null)
                {
                    c = *(ptr = ptr2);
                    flag3 = true;
                }
            }
            if (c >= '0' && c <= '9')
            {
                int num4 = 0;
                do
                {
                    num4 = num4 * 10 + (int)(c - '0');
                    c = *(ptr += (IntPtr)2 / 2);
                    if (num4 > 1000)
                    {
                        num4 = 9999;
                        while (c >= '0' && c <= '9')
                        {
                            c = *(ptr += (IntPtr)2 / 2);
                        }
                    }
                }
                while (c >= '0' && c <= '9');
                if (flag3)
                {
                    num4 = -num4;
                }
                number.scale += num4;
            }
            else
            {
                ptr = ptr3;
                c = *ptr;
            }
        }
        while (true)
        {
            if (!Number.IsWhite(c) || (options & NumberStyles.AllowTrailingWhite) == NumberStyles.None)
            {
                bool flag2;
                char* ptr2;
                if ((flag2 = (((options & NumberStyles.AllowTrailingSign) == NumberStyles.None) ? false : ((num & 1) == 0))) && (ptr2 = Number.MatchChars(ptr, numfmt.positiveSign)) != null)
                {
                    num |= 1;
                    ptr = ptr2 - (IntPtr)2 / 2;
                }
                else
                {
                    if (flag2 && (ptr2 = Number.MatchChars(ptr, numfmt.negativeSign)) != null)
                    {
                        num |= 1;
                        number.sign = true;
                        ptr = ptr2 - (IntPtr)2 / 2;
                    }
                    else
                    {
                        if (c == ')' && (num & 2) != 0)
                        {
                            num &= -3;
                        }
                        else
                        {
                            if ((text == null || (ptr2 = Number.MatchChars(ptr, text)) == null) && (text2 == null || (ptr2 = Number.MatchChars(ptr, text2)) == null))
                            {
                                break;
                            }
                            text = null;
                            text2 = null;
                            ptr = ptr2 - (IntPtr)2 / 2;
                        }
                    }
                }
            }
            c = *(ptr += (IntPtr)2 / 2);
        }
        if ((num & 2) == 0)
        {
            if ((num & 8) == 0)
            {
                if (!parseDecimal)
                {
                    number.scale = 0;
                }
                if ((num & 16) == 0)
                {
                    number.sign = false;
                }
            }
            str = ptr;
            return true;
        }
    }
    str = ptr;
    return false;
}
public static int Parse(string s)
{
    return Number.ParseInt32(s, NumberStyles.Integer, NumberFormatInfo.CurrentInfo);
}

internal unsafe static int ParseInt32(string s, NumberStyles style, NumberFormatInfo info)
{
    byte* stackBuffer = stackalloc byte[1 * 114 / 1];
    Number.NumberBuffer numberBuffer = new Number.NumberBuffer(stackBuffer);
    int result = 0;
    Number.StringToNumber(s, style, ref numberBuffer, info, false);
    if ((style & NumberStyles.AllowHexSpecifier) != NumberStyles.None)
    {
        if (!Number.HexNumberToInt32(ref numberBuffer, ref result))
        {
            throw new OverflowException(Environment.GetResourceString("Overflow_Int32"));
        }
    }
    else
    {
        if (!Number.NumberToInt32(ref numberBuffer, ref result))
        {
            throw new OverflowException(Environment.GetResourceString("Overflow_Int32"));
        }
    }
    return result;
}

private unsafe static void StringToNumber(string str, NumberStyles options, ref Number.NumberBuffer number, NumberFormatInfo info, bool parseDecimal)
{
    if (str == null)
    {
        throw new ArgumentNullException("String");
    }
    fixed (char* ptr = str)
    {
        char* ptr2 = ptr;
        if (!Number.ParseNumber(ref ptr2, options, ref number, info, parseDecimal) || ((ptr2 - ptr / 2) / 2 < str.Length && !Number.TrailingZeros(str, (ptr2 - ptr / 2) / 2)))
        {
            throw new FormatException(Environment.GetResourceString("Format_InvalidString"));
        }
    }
}

Ответ 2

Я написал этот для парных, что не создает временную подстроку. Он предназначен для использования в парсере JSON, поэтому он ограничивается тем, как удвоения могут быть представлены в JSON в соответствии с http://www.json.org/.

Это еще не оптимально, потому что вам нужно знать, где начинается и заканчивается число (параметры begin и end), поэтому вам нужно будет дважды пройти длину номера, чтобы узнать, где он заканчивается. Он все еще на 10-15 раз быстрее, чем double.Parse, и его можно довольно легко изменить, чтобы найти end внутри функции, которая затем возвращается как параметр out, чтобы узнать, где вы должны возобновить синтаксический анализ основной строки.

Используется так:

Parsers.TryParseDoubleFastStream("1", 0, 1, out j);
Parsers.TryParseDoubleFastStream("2.0", 0, 3, out j);
Parsers.TryParseDoubleFastStream("3.5", 0, 3, out j);
Parsers.TryParseDoubleFastStream("-4.5", 0, 4, out j);
Parsers.TryParseDoubleFastStream("50.06", 0, 5, out j);
Parsers.TryParseDoubleFastStream("1000.65", 0, 7, out j);
Parsers.TryParseDoubleFastStream("-10000.8600", 0, 11, out j);

Код можно найти здесь:

https://gist.github.com/3010984 (было бы слишком длинным для публикации здесь).

И StandardFunctions.IgnoreChar для моей цели прост как:

public static bool IgnoreChar(char c)
{
  return c < 33;
}

Ответ 3

Вставьте весь этот код в С# и вызовите Test(). Это так близко, что вы можете напрямую работать с массивом строк для анализа чисел с помощью С#. Он построен для скорости, а не изящества. Функции ParseInt и ParseFloat были созданы для графического движка OpenGL для импорта векторов из текстовых 3D-моделей. В этом процессе значительным узким местом является синтаксический анализ. Это было так быстро, как я мог это сделать.

using System.Diagnostics;

    private void Test()
    {
        Stopwatch sw = new Stopwatch();
        StringBuilder sb = new StringBuilder();
        int iterations = 1000;

        // Build a string of 1000000 space separated numbers
        for (var n = 0; n < iterations; n++)
        {
            if (n > 0)
                sb.Append(' ');
            sb.Append(n.ToString());
        }

        string numberString = sb.ToString();

        // Time the process
        sw.Start();
        StringToInts(numberString, iterations);
        //StringToFloats(numberString, iterations);
        sw.Stop();
        long proc1 = sw.ElapsedMilliseconds;

        Console.WriteLine("iterations: {0} \t {1}ms", iterations, proc1);
    }

    private unsafe int[] StringToInts(string s, int length)
    {
        int[] ints = new int[length];
        int index = 0;
        int startpos = 0;

        fixed (char* pStringBuffer = s)
        {
            fixed (int* pIntBuffer = ints)
            {
                for (int n = 0; n < s.Length; n++)
                {
                    if (s[n] == ' ' || n == s.Length - 1)
                    {
                        if (n == s.Length - 1)
                            n++;
                        // pIntBuffer[index++] = int.Parse(new string(pStringBuffer, startpos, n - startpos));
                        pIntBuffer[index++] = ParseInt((pStringBuffer + startpos), n - startpos); 
                        startpos = n + 1;
                    }
                }
            }
        }

        return ints;
    }

    private unsafe float[] StringToFloats(string s, int length)
    {
        float[] floats = new float[length];
        int index = 0;
        int startpos = 0;

        fixed (char* pStringBuffer = s)
        {
            fixed (float* pFloatBuffer = floats)
            {
                for (int n = 0; n < s.Length; n++)
                {
                    if (s[n] == ' ' || n == s.Length - 1)
                    {
                        if (n == s.Length - 1)
                            n++;

                        pFloatBuffer[index++] = ParseFloat((pStringBuffer + startpos), n - startpos); // int.Parse(new string(pStringBuffer, startpos, n - startpos));
                        startpos = n + 1;
                    }
                }
            }
        }

        return floats;
    }

    public static unsafe int ParseInt(char* input, int len)
    {
        int pos = 0;           // read pointer position
        int part = 0;          // the current part (int, float and sci parts of the number)
        bool neg = false;      // true if part is a negative number

        int* ret = stackalloc int[1];

        while (pos < len && (*(input + pos) > '9' || *(input + pos) < '0') && *(input + pos) != '-')
            pos++;

        // sign
        if (*(input + pos) == '-')
        {
            neg = true;
            pos++;
        }

        // integer part
        while (pos < len && !(input[pos] > '9' || input[pos] < '0'))
            part = part * 10 + (input[pos++] - '0');

        *ret = neg ? (part * -1) : part;
        return *ret;
    }

    public static unsafe float ParseFloat(char* input, int len)
    {
        //float ret = 0f;      // return value
        int pos = 0;           // read pointer position
        int part = 0;          // the current part (int, float and sci parts of the number)
        bool neg = false;      // true if part is a negative number

        float* ret = stackalloc float[1];

        // find start
        while (pos < len && (input[pos] < '0' || input[pos] > '9') && input[pos] != '-' && input[pos] != '.')
            pos++;

        // sign
        if (input[pos] == '-')
        {
            neg = true;
            pos++;
        }

        // integer part
        while (pos < len && !(input[pos] > '9' || input[pos] < '0'))
            part = part * 10 + (input[pos++] - '0');

        *ret = neg ? (float)(part * -1) : (float)part;

        // float part
        if (pos < len && input[pos] == '.')
        {
            pos++;
            double mul = 1;
            part = 0;

            while (pos < len && !(input[pos] > '9' || input[pos] < '0'))
            {
                part = part * 10 + (input[pos] - '0');
                mul *= 10; 
                pos++;
            }

            if (neg)
                *ret -= (float)part / (float)mul;
            else
                *ret += (float)part / (float)mul;

        }

        // scientific part
        if (pos < len && (input[pos] == 'e' || input[pos] == 'E'))
        {
            pos++;
            neg = (input[pos] == '-'); pos++;
            part = 0;
            while (pos < len && !(input[pos] > '9' || input[pos] < '0'))
            {
                part = part * 10 + (input[pos++] - '0');
            }

            if (neg)
                *ret /= (float)Math.Pow(10d, (double)part);
            else
                *ret *= (float)Math.Pow(10d, (double)part);
        }

        return (float)*ret;
    }

Ответ 4

Итак, существуют ли встроенные функции для синтаксического анализа непосредственно из подстроки внутри строки (т. используя заданный старт и длину строки), чтобы избежать генерации временного строка? Если нет, существуют ли библиотеки, которые предоставляют эффективные функции для этого?

Кажется, что вы хотите использовать лексический буфер и лексер, аналогичный тому, что OCaml может предоставить с помощью ocamllex и буфера Lexbuf. (Я не могу предоставить ссылки для F #.)

Если ваш тестовый показатель, включающий огромную цепочку целых чисел, разделенных другими токенами, является вашим типичным случаем, он будет работать хорошо. Но в других ситуациях это может быть непрактичным.

Ответ 5

Не уверен, что это хорошо, но вы пробовали что-то вроде:

var stringValues = input.split(" ");

var intValues = Array.ConvertAll(stringValues, s => int.Parse(s));