【问题标题】:Fast conversion of byte[] containing ascii string to int/double/date, etc. without new String将包含 ascii 字符串的 byte[] 快速转换为 int/double/date 等,无需新字符串
【发布时间】:2012-02-07 07:21:20
【问题描述】:

我得到 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[] 转换为原始类型。我尝试了手写函数来执行此操作,但发现它很耗时并且没有带来更好的性能。

是否有任何第三方库可以这样做,最重要的是值得这样做吗?

【问题讨论】:

  • 衡量性能,即微基准测试很难,而且几乎总是出错。如果您需要整体性能,则字符串化是一个坏主意。您应该改用ByteBufefr.putInt。除此之外,手写ByteBuffer 解析会做,最后如果你使用ByteBuffer 不要转换为它字节[],它违背了ByteBuffer 本身的目的。
  • 谢谢bestss,但它是ASCII ByteBuffer,不是二进制,所以不能使用getInt,putInt。
  • 什么叫ASCII byteBuffer(标准jdk中没有这个类)

标签: java parsing bytebuffer


【解决方案1】:

最重要的是值得做吗?

几乎可以肯定不是 - 您应该先测量以检查这性能瓶颈,然后再进行重大努力来缓解它。

你现在的表现如何?它需要是什么? (“尽可能快”不是一个好的目标,否则您将永远不会停止 - 当您可以说“完成”时锻炼。)

分析代码 - 真的 是字符串创建中的问题吗?检查您收集垃圾的频率等(再次,使用分析器)。

每种解析类型可能具有不同的特征。例如,在解析整数时,如果您发现在很长一段时间内只有一个数字,您可能想要特殊情况:

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

...但是在你走这条路之前检查一下这种情况发生的频率。对从未真正出现的特定优化应用大量检查会适得其反。

【讨论】:

  • 我同意乔恩的观点。我意识到要获得个位数微秒的性能改进将付出太多努力。 Profilers 说新的 String 会导致大量的次要集合。但我认为,在应用程序上下文中分析解析库以获得更清晰的画面会更有意义。
  • 目前从 40 多个标签值对中创建树状图大约需要 20 微秒。
【解决方案2】:

同意 Jon 的观点,但是在处理许多 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

【讨论】:

    【解决方案3】:

    看看ByteBuffer。它具有执行此操作的功能,包括处理字节顺序(字节序)。

    【讨论】:

    • 我认为 ByteBuffer 没有任何东西可以解析 text 数据,是吗?
    • @JonSkeet - 不,但 OP 说“我需要将 byte[] 值转换为 int/double/date 等。”
    • 谢谢特德!它说 byte[] 包含 ascii 字符串。
    【解决方案4】:

    通常我不喜欢粘贴这样的代码,但无论如何,它是如何完成的 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;
            }
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 2018-10-16
      • 2022-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-28
      • 2010-10-20
      • 2020-09-03
      相关资源
      最近更新 更多