From c71236f223f3e15de364c9a35896d3620f965a9f Mon Sep 17 00:00:00 2001 From: Timofey Gelazoniya Date: Fri, 7 Feb 2025 08:35:21 +0300 Subject: [PATCH] perf: optimize varint decoding docs: update type definitions --- lib/varint.js | 84 ++++++++++++++++++++----------------------- types/lib/varint.d.ts | 48 +++++++++++++++++++++---- 2 files changed, 79 insertions(+), 53 deletions(-) diff --git a/lib/varint.js b/lib/varint.js index 2fba172..cccd38e 100644 --- a/lib/varint.js +++ b/lib/varint.js @@ -66,65 +66,57 @@ const varint = { }, /** - * Decodes a varint integer value from a byte buffer. + * Decodes a varint integer value from a buffer. * @param {Buffer} buffer - The byte buffer to decode from. * @param {number} offset - The offset in the buffer to start decoding from. * @returns {number} */ decodeInt: (buffer, offset) => { - let val = 0; - let count = 0; - - while (true) { - const byte = buffer.readUInt8(offset++); - - val |= (byte & 0x7f) << (count++ * 7); - - if ((byte & 0x80) !== 0x80) { - break; - } + // Fast path for single-byte varints + const firstByte = buffer.readUInt8(offset); + if (firstByte < 0x80) { + return firstByte; } - return val; + let val = firstByte & 0x7f; + let position = 7; + + while (position < 32) { + const byte = buffer.readUInt8(++offset); + val |= (byte & 0x7f) << position; + + if ((byte & 0x80) === 0) { + return val; + } + + position += 7; + } + + throw new Error("VarInt is too big"); }, /** - * Calculates the number of bytes required to decode a varint integer value. - * @param {number} val - The varint integer value. - * @returns {5 | 7 | 8 | 1 | 2 | 3 | 4 | 6 | 9 | 10} + * Calculates how many bytes are needed to encode a number as a VarInt + * VarInts use a variable number of bytes to efficiently encode integers + * Each byte uses 7 bits for the value and 1 bit to indicate if more bytes follow + * VarInts are never longer than 5 bytes + * + * @param {number} val - The number to calculate the VarInt length for + * @returns {1|2|3|4|5} The number of bytes needed to encode the value */ decodeLength: (val) => { - // Constants representing the powers of 2 used for comparison - const N1 = Math.pow(2, 7); - const N2 = Math.pow(2, 14); - const N3 = Math.pow(2, 21); - const N4 = Math.pow(2, 28); - const N5 = Math.pow(2, 35); - const N6 = Math.pow(2, 42); - const N7 = Math.pow(2, 49); - const N8 = Math.pow(2, 56); - const N9 = Math.pow(2, 63); + // Using bit shifts to calculate power of 2 thresholds + // 1 << 7 = 2^7 = 128 - Numbers below this fit in 1 byte + // 1 << 14 = 2^14 = 16,384 - Numbers below this fit in 2 bytes + // 1 << 21 = 2^21 = 2,097,152 - Numbers below this fit in 3 bytes + // 1 << 28 = 2^28 = 268,435,456 - Numbers below this fit in 4 bytes + // Any larger number needs 5 bytes (maximum VarInt size) - // Return the number of bytes required based on the value - return val < N1 - ? 1 - : val < N2 - ? 2 - : val < N3 - ? 3 - : val < N4 - ? 4 - : val < N5 - ? 5 - : val < N6 - ? 6 - : val < N7 - ? 7 - : val < N8 - ? 8 - : val < N9 - ? 9 - : 10; + if (val < 1 << 7) return 1; + if (val < 1 << 14) return 2; + if (val < 1 << 21) return 3; + if (val < 1 << 28) return 4; + return 5; }, }; diff --git a/types/lib/varint.d.ts b/types/lib/varint.d.ts index f14b5f5..84c9804 100644 --- a/types/lib/varint.d.ts +++ b/types/lib/varint.d.ts @@ -1,10 +1,44 @@ export default varint; declare namespace varint { - function encodeInt(val: number): Buffer; - function encodeString(val: string): Buffer; - function encodeUShort(val: number): Buffer; - function concat(chunks: Buffer[]): Buffer; - function decodeInt(buffer: Buffer, offset: number): number; - function decodeString(val: Buffer, offset?: number): string; - function decodeLength(val: number): 5 | 7 | 8 | 1 | 2 | 3 | 4 | 6 | 9 | 10; + /** + * Encodes an integer value into a varint byte buffer. + * @param val - The integer value to encode. + */ + function encodeInt(val: number): Buffer; + + /** + * Encodes a string value into a UTF-8 byte buffer. + * @param val - The string value to encode. + */ + function encodeString(val: string): Buffer; + + /** + * Encodes an unsigned short value into a byte buffer. + * @param val - The unsigned short value to encode. + */ + function encodeUShort(val: number): Buffer; + + /** + * Concatenates multiple byte buffers into a single byte buffer. + * @param chunks - An array of byte buffers to concatenate. + */ + function concat(chunks: Buffer[]): Buffer; + + /** + * Decodes a varint integer value from a buffer. + * @param buffer - The byte buffer to decode from. + * @param offset - The offset in the buffer to start decoding from. + */ + function decodeInt(buffer: Buffer, offset: number): number; + + /** + * Calculates how many bytes are needed to encode a number as a VarInt. + * VarInts use a variable number of bytes to efficiently encode integers. + * Each byte uses 7 bits for the value and 1 bit to indicate if more bytes follow. + * VarInts are never longer than 5 bytes. + * + * @param val - The number to calculate the VarInt length for. + * @returns The number of bytes needed to encode the value (1-5). + */ + function decodeLength(val: number): 1 | 2 | 3 | 4 | 5; }