Быстрое преобразование байта [], содержащего строку ascii, в int / double / date и т. Д. Без новой строки - PullRequest
1 голос
/ 07 февраля 2012

Я получаю строку сообщения 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 [] в примитивный тип. Для этого я попробовал написанные от руки функции, но нашел, что это отнимает много времени и не приводит к повышению производительности.

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

Ответы [ 4 ]

3 голосов
/ 07 февраля 2012

больше всего стоит?

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

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

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

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

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

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

2 голосов
/ 20 сентября 2013

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

///<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
1 голос
/ 07 февраля 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;
        }
    }

}
1 голос
/ 07 февраля 2012

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

...