Быстрое преобразование byte[], содержащего строку ascii, в int/double/date и т. д. без новой строки

Я получаю строку сообщения FIX (ASCII) как ByteBuffer. Я анализирую пары значений тегов и сохраняю значения в виде примитивных объектов в карте дерева с тегом в качестве ключа. Поэтому мне нужно преобразовать значение byte[] в int/double/date и т. д. в зависимости от его типа.

Самый простой способ - создать новую строку и передать ее стандартным функциям конвертера. например

int convertToInt(byte[] buffer, int offset, int length)
{
  String valueStr = new String(buffer, offset, length);
  return Integer.parseInt(valueStr);
}

Я понимаю, что в Java создание нового объекта очень недорого, но есть ли способ напрямую преобразовать этот ascii byte[] в примитивный тип. Я пробовал использовать написанные от руки функции для этого, но обнаружил, что это отнимает много времени и не приводит к повышению производительности.

Существуют ли сторонние библиотеки для этого и, прежде всего, стоит ли это делать?


person Mahendra    schedule 07.02.2012    source источник
comment
измерение производительности, т. е. микротесты, сложно и почти всегда дает неверные результаты. Создание строк — плохая идея, если вам нужна производительность в целом. Вместо этого вы должны использовать ByteBufefr.putInt. Кроме того, написанный вручную ByteBuffer синтаксический анализ будет делать, последнее, если вы используете ByteBuffer, не конвертируйте в него byte[], это противоречит цели самого ByteBuffer.   -  person bestsss    schedule 07.02.2012
comment
Спасибо, bestss, но это ASCII ByteBuffer, а не двоичный, поэтому нельзя использовать getInt, putInt.   -  person Mahendra    schedule 07.02.2012
comment
что вы называете ASCII byteBuffer (в стандартном jdk такого класса нет)   -  person bestsss    schedule 07.02.2012


Ответы (4)


самое главное стоит ли это делать?

Почти наверняка нет — и вы должны измерить, чтобы убедиться, что это является узким местом в производительности, прежде чем предпринимать значительные усилия для его устранения.

Каково ваше выступление сейчас? Что это должно быть? («Как можно быстрее» — плохая цель, иначе вы никогда не остановитесь — потренируйтесь, когда сможете сказать, что «готово».)

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

Каждый тип синтаксического анализа, вероятно, будет иметь различные характеристики. Например, при синтаксическом анализе целых чисел, если вы обнаружите, что в течение значительного времени у вас есть одна цифра, вы можете захотеть использовать особый случай:

if (length == 1)
{
    char c = buffer[index];
    if (c >= '0' && c <= '9')
    {
        return c - '0';
    }
    // Invalid - throw an exception or whatever
}

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

person Jon Skeet    schedule 07.02.2012
comment
Я согласен с тобой Джон. Я понял, что было бы слишком много усилий, чтобы получить улучшение производительности на однозначные микросекунды. Профилировщики говорят, что новая строка приводит к большому количеству второстепенных коллекций. Но я думаю, что было бы разумнее профилировать библиотеку синтаксического анализа в контексте приложения, чтобы получить более четкую картину. - person Mahendra; 07.02.2012
comment
В настоящее время для создания древовидной карты из более чем 40 пар значений тегов требуется около 20 микросекунд. - person Mahendra; 07.02.2012

Согласитесь с Джоном, однако при обработке большого количества сообщений FIX это быстро складывается. Приведенный ниже метод позволит использовать числа, дополненные пробелами. Если вам нужно обрабатывать десятичные числа, тогда код будет немного другим. Разница в скорости между двумя методами составляет 11. ConvertToLong приводит к 0 GC. Код ниже на С#:

///<summary>
///Converts a byte[] of characters that represent a number into a .net long type. Numbers can be padded from left
/// with spaces.
///</summary>
///<param name="buffer">The buffer containing the number as characters</param>
///<param name="startIndex">The startIndex of the number component</param>
///<param name="endIndex">The EndIndex of the number component</param>
///<returns>The price will be returned as a long from the ASCII characters</returns>
public static long ConvertToLong(this byte[] buffer, int startIndex, int endIndex)
{
    long result = 0;
    for (int i = startIndex; i <= endIndex; i++)
    {
        if (buffer[i] != 0x20)
        {
            // 48 is the decimal value of the '0' character. So to convert the char value
            // of an int to a number we subtract 48. e.g '1' = 49 -48 = 1
            result = result * 10 + (buffer[i] - 48);
        }
    }
    return result;
}

/// <summary>
/// Same as above but converting to string then to long
/// </summary>
public static long ConvertToLong2(this byte[] buffer, int startIndex, int endIndex)
{
    for (int i = startIndex; i <= endIndex; i++)
    {
        if (buffer[i] != SpaceChar)
        {
            return long.Parse(System.Text.Encoding.UTF8.GetString(buffer, i, (endIndex - i) + 1));
        }
    }
    return 0;
}

[Test]
public void TestPerformance(){
    const int iterations = 200 * 1000;
    const int testRuns = 10;
    const int warmUp = 10000;
    const string number = "    123400";
    byte[] buffer = System.Text.Encoding.UTF8.GetBytes(number);

    double result = 0;
    for (int i = 0; i < warmUp; i++){
        result = buffer.ConvertToLong(0, buffer.Length - 1);
    }
    for (int testRun = 0; testRun < testRuns; testRun++){
        Stopwatch sw = new Stopwatch();
        sw.Start();
        for (int i = 0; i < iterations; i++){
            result = buffer.ConvertToLong(0, buffer.Length - 1);
        }
        sw.Stop();
        Console.WriteLine("Test {4}: {0} ticks, {1}ms, 1 conversion takes = {2}μs or {3}ns. GCs: {5}", sw.ElapsedTicks,
            sw.ElapsedMilliseconds, (((decimal) sw.ElapsedMilliseconds)/((decimal) iterations))*1000,
            (((decimal) sw.ElapsedMilliseconds)/((decimal) iterations))*1000*1000, testRun,
            GC.CollectionCount(0) + GC.CollectionCount(1) + GC.CollectionCount(2));
    }
}
RESULTS
ConvertToLong:
Test 0: 9243 ticks, 4ms, 1 conversion takes = 0.02000μs or 20.00000ns. GCs: 2
Test 1: 8339 ticks, 4ms, 1 conversion takes = 0.02000μs or 20.00000ns. GCs: 2
Test 2: 8425 ticks, 4ms, 1 conversion takes = 0.02000μs or 20.00000ns. GCs: 2
Test 3: 8333 ticks, 4ms, 1 conversion takes = 0.02000μs or 20.00000ns. GCs: 2
Test 4: 8332 ticks, 4ms, 1 conversion takes = 0.02000μs or 20.00000ns. GCs: 2
Test 5: 8331 ticks, 4ms, 1 conversion takes = 0.02000μs or 20.00000ns. GCs: 2
Test 6: 8409 ticks, 4ms, 1 conversion takes = 0.02000μs or 20.00000ns. GCs: 2
Test 7: 8334 ticks, 4ms, 1 conversion takes = 0.02000μs or 20.00000ns. GCs: 2
Test 8: 8335 ticks, 4ms, 1 conversion takes = 0.02000μs or 20.00000ns. GCs: 2
Test 9: 8331 ticks, 4ms, 1 conversion takes = 0.02000μs or 20.00000ns. GCs: 2
ConvertToLong2:
Test 0: 109067 ticks, 55ms, 1 conversion takes = 0.275000μs or 275.000000ns. GCs: 4
Test 1: 109861 ticks, 56ms, 1 conversion takes = 0.28000μs or 280.00000ns. GCs: 8
Test 2: 102888 ticks, 52ms, 1 conversion takes = 0.26000μs or 260.00000ns. GCs: 9
Test 3: 105164 ticks, 53ms, 1 conversion takes = 0.265000μs or 265.000000ns. GCs: 10
Test 4: 104083 ticks, 53ms, 1 conversion takes = 0.265000μs or 265.000000ns. GCs: 11
Test 5: 102756 ticks, 52ms, 1 conversion takes = 0.26000μs or 260.00000ns. GCs: 13
Test 6: 102219 ticks, 52ms, 1 conversion takes = 0.26000μs or 260.00000ns. GCs: 14
Test 7: 102086 ticks, 52ms, 1 conversion takes = 0.26000μs or 260.00000ns. GCs: 15
Test 8: 102672 ticks, 52ms, 1 conversion takes = 0.26000μs or 260.00000ns. GCs: 17
Test 9: 102025 ticks, 52ms, 1 conversion takes = 0.26000μs or 260.00000ns. GCs: 18
person Domc    schedule 20.09.2013

Взгляните на ByteBuffer. У него есть возможности для этого, в том числе работа с порядком байтов (порядок байтов).

person Ted Hopp    schedule 07.02.2012
comment
Я не думаю, что у ByteBuffer есть что-нибудь для разбора текстовых данных, не так ли? - person Jon Skeet; 07.02.2012
comment
@JonSkeet - Нет, но OP говорит, что мне нужно преобразовать значение byte[] в int/double/date и т. д. - person Ted Hopp; 07.02.2012
comment
Спасибо, Тед! Он говорит, что byte[] содержит строку ascii. - person Mahendra; 07.02.2012

Обычно я не предпочитаю вставлять такой код, но в любом случае, 100 строк, как это делается (производственный код), я бы не советовал его использовать, но иметь какой-то справочный код приятно (обычно)

package t1;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;

public class IntParser {
    final static byte[] digits = {
        '0' , '1' , '2' , '3' , '4' , '5' ,
        '6' , '7' , '8' , '9' , 'a' , 'b' ,
        'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
        'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
        'o' , 'p' , 'q' , 'r' , 's' , 't' ,
        'u' , 'v' , 'w' , 'x' , 'y' , 'z'
    };

    static boolean isDigit(byte b) {
    return b>='0' &&  b<='9';
  }

    static int digit(byte b){
        //negative = error

        int result  = b-'0';
        if (result>9)
            result = -1;
        return result;
    }

    static NumberFormatException forInputString(ByteBuffer b){
        byte[] bytes=new byte[b.remaining()];
        b.get(bytes);
        try {
            return new NumberFormatException("bad integer: "+new String(bytes, "8859_1"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
    public static int parseInt(ByteBuffer b){
        return parseInt(b, 10, b.position(), b.limit());
    }
    public static int parseInt(ByteBuffer b, int radix, int i, int max) throws NumberFormatException{
        int result = 0;
        boolean negative = false;


        int limit;
        int multmin;
        int digit;      

        if (max > i) {
            if (b.get(i) == '-') {
                negative = true;
                limit = Integer.MIN_VALUE;
                i++;
            } else {
                limit = -Integer.MAX_VALUE;
            }
            multmin = limit / radix;
            if (i < max) {
                digit = digit(b.get(i++));
                if (digit < 0) {
                    throw forInputString(b);
                } else {
                    result = -digit;
                }
            }
            while (i < max) {
                // Accumulating negatively avoids surprises near MAX_VALUE
                digit = digit(b.get(i++));
                if (digit < 0) {
                    throw forInputString(b);
                }
                if (result < multmin) {
                    throw forInputString(b);
                }
                result *= radix;
                if (result < limit + digit) {
                    throw forInputString(b);
                }
                result -= digit;
            }
        } else {
            throw forInputString(b);
        }
        if (negative) {
            if (i > b.position()+1) {
                return result;
            } else {    /* Only got "-" */
                throw forInputString(b);
            }
        } else {
            return -result;
        }
    }

}
person bestsss    schedule 07.02.2012