From 452030de5ea906a9f76d2b8384fc5988d50d3010 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Mon, 1 Apr 2024 12:44:30 +0200 Subject: [PATCH] use more efficient way to determine needed bytes for variable byte encoding I experimented with a few branch-free variants, but they were slower than just using ternary operators and readable code. --- .../byteencoder/VariableByteEncoder.java | 23 +++++++++++-------- .../byteencoder/VariableByteEncoderTest.java | 4 ++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/pdb-utils/src/main/java/org/lucares/utils/byteencoder/VariableByteEncoder.java b/pdb-utils/src/main/java/org/lucares/utils/byteencoder/VariableByteEncoder.java index 2fb69da..aea6645 100644 --- a/pdb-utils/src/main/java/org/lucares/utils/byteencoder/VariableByteEncoder.java +++ b/pdb-utils/src/main/java/org/lucares/utils/byteencoder/VariableByteEncoder.java @@ -28,15 +28,10 @@ public class VariableByteEncoder { public static final long MIN_VALUE = Long.MIN_VALUE / 2 + 1; public static final long MAX_VALUE = Long.MAX_VALUE / 2; - private static final int MAX_BYTES_PER_VALUE = 10; - private static final int CONTINUATION_BYTE_FLAG = 1 << 7; // 10000000 private static final long DATA_BITS = (1 << 7) - 1; // 01111111 - private static final ThreadLocal SINGLE_VALUE_BUFFER = ThreadLocal - .withInitial(() -> new byte[MAX_BYTES_PER_VALUE]); - /** * Encodes time and value into the given buffer. *

@@ -137,8 +132,18 @@ public class VariableByteEncoder { * input: 0 1 -1 2 -2 3 -3 * encoded: 1 2 3 4 5 6 7 * + * + * Note: 1. I tried to replace this ternary operator with a branchfree + * alternative, but it was slower: + * + *

+     * final long sign = (x - 1) >> 63; // will be 111...1 if first bit was 1 (x was negative) and 000..0
+     *                                  // otherwise
+     * final long signBit = sign & 0x1; // will be 1 for negative x and 0 otherwise
+     * return (((x * 2) ^ (sign))) + (signBit * 2); // same as above, but branchless
+     * 
*/ - private static long encodeIntoPositiveValue(final long value) { + static long encodeIntoPositiveValue(final long value) { return value > 0 ? value * 2 : (value * -2) + 1; } @@ -234,9 +239,9 @@ public class VariableByteEncoder { } public static int neededBytes(final long value) { - final byte[] buffer = SINGLE_VALUE_BUFFER.get(); - final int usedBytes = encodeInto(value, buffer, 0); - return usedBytes; + final long val = encodeIntoPositiveValue(value); + final int numberOfOnes = 64 - Long.numberOfLeadingZeros(val); + return numberOfOnes / 7 + (numberOfOnes % 7 == 0 ? 0 : 1); } } diff --git a/pdb-utils/src/test/java/org/lucares/utils/byteencoder/VariableByteEncoderTest.java b/pdb-utils/src/test/java/org/lucares/utils/byteencoder/VariableByteEncoderTest.java index 7bc8e38..d0a8089 100644 --- a/pdb-utils/src/test/java/org/lucares/utils/byteencoder/VariableByteEncoderTest.java +++ b/pdb-utils/src/test/java/org/lucares/utils/byteencoder/VariableByteEncoderTest.java @@ -81,7 +81,7 @@ public class VariableByteEncoderTest { Assertions.assertEquals(originalValues, decodedValues); } - public static Stream providerNededBytes() { + public static Stream providerNeededBytes() { return Stream.of( // Arguments.of(0, 1), // Arguments.of(-10, 1), // @@ -98,7 +98,7 @@ public class VariableByteEncoderTest { } @ParameterizedTest - @MethodSource("providerNededBytes") + @MethodSource("providerNeededBytes") public void testNeededBytes(final long value, final int expectedNeededBytes) { final int neededBytes = VariableByteEncoder.neededBytes(value);