From 117c7ecc1c54bd687803b7306545df9748930e66 Mon Sep 17 00:00:00 2001 From: Dieter Reinert Date: Sat, 11 Jan 2025 21:22:24 +0100 Subject: [PATCH 1/6] Utils: Optimize hex buffer conversion functions This PR introduces optimized versions of the `bufFromHex`, `bufWriteHex`, and `bufReadHex` TypeScript functions. By utilizing pre-built lookup tables, the performance of hexadecimal to buffer conversions and vice versa is significantly improved. --- lib/utils.ts | 88 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 77 insertions(+), 11 deletions(-) diff --git a/lib/utils.ts b/lib/utils.ts index eb516008c805..a4bf31a33c8b 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -413,19 +413,85 @@ export function formatSQLArray(arr: unknown[], args?: unknown[]) { return [...'?'.repeat(arr.length)].join(', '); } -export function bufFromHex(hex: string) { - const buf = new Uint8Array(Math.ceil(hex.length / 2)); - bufWriteHex(buf, hex); - return buf; +/** + * Nibble lookup table to convert ASCII character codes to their hexadecimal values (0-15). + * Invalid characters are marked with 0xFF. + */ +const nibbleLookup: Uint8Array = (() => { + const lookup = new Uint8Array(256); + for (let i = 0; i < 256; i++) { + lookup[i] = 0xFF; // Mark as invalid by default + } + '0123456789abcdef'.split('').forEach((char, index) => { + lookup[char.charCodeAt(0)] = index; + }); + '0123456789ABCDEF'.split('').forEach((char, index) => { + lookup[char.charCodeAt(0)] = index; + }); + return lookup; +})(); + +/** + * Byte-to-hex lookup table for efficient conversion from bytes to hexadecimal strings. + */ +const byteToHexTable: string[] = (() => { + const table: string[] = new Array(256); + for (let i = 0; i < 256; i++) { + table[i] = (i < 16 ? '0' : '') + i.toString(16); + } + return table; +})(); + +export function bufFromHex(hex: string): Uint8Array { + const len = hex.length; + const size = Math.ceil(len / 2); + const buf = new Uint8Array(size); + let j = 0; + + for (let i = 0; i < len; i += 2) { + const hiChar = hex.charCodeAt(i); + const loChar = i + 1 < len ? hex.charCodeAt(i + 1) : 0x30; // '0' if no character + const hi = nibbleLookup[hiChar]; + const lo = nibbleLookup[loChar]; + + if (hi === 0xFF || lo === 0xFF) { + throw new Error( + `Invalid hex character encountered: '${hex[i]}' or '${hex[i + 1] || '0'}'` + ); + } + + buf[j++] = (hi << 4) | lo; + } + + return buf; } -export function bufWriteHex(buf: Uint8Array, hex: string, offset = 0) { - const size = Math.ceil(hex.length / 2); - for (let i = 0; i < size; i++) { - buf[offset + i] = parseInt(hex.slice(i * 2, i * 2 + 2).padEnd(2, '0'), 16); - } +export function bufWriteHex(buf: Uint8Array, hex: string, offset: number = 0): void { + const len = hex.length; + const size = Math.ceil(len / 2); + let j = offset; + + for (let i = 0; i < len; i += 2) { + const hiChar = hex.charCodeAt(i); + const loChar = i + 1 < len ? hex.charCodeAt(i + 1) : 0x30; // '0' if no character + const hi = nibbleLookup[hiChar]; + const lo = nibbleLookup[loChar]; + + if (hi === 0xFF || lo === 0xFF) { + throw new Error( + `Invalid hex character encountered: '${hex[i]}' or '${hex[i + 1] || '0'}'` + ); + } + + buf[j++] = (hi << 4) | lo; + } } -export function bufReadHex(buf: Uint8Array, start = 0, end?: number) { - return [...buf.slice(start, end)].map(val => val.toString(16).padStart(2, '0')).join(''); +export function bufReadHex(buf: Uint8Array, start: number = 0, end?: number): string { + if (end === undefined) end = buf.length; + let out = ''; + for (let i = start; i < end; i++) { + out += byteToHexTable[buf[i]]; + } + return out; } export class Multiset extends Map { From 67d73fce7982bb4390dd507421d64c839fdea88b Mon Sep 17 00:00:00 2001 From: Dieter Reinert Date: Sat, 11 Jan 2025 21:27:30 +0100 Subject: [PATCH 2/6] fix eslint --- lib/utils.ts | 126 +++++++++++++++++++++++++-------------------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/lib/utils.ts b/lib/utils.ts index a4bf31a33c8b..2b2edc4eed01 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -417,81 +417,81 @@ export function formatSQLArray(arr: unknown[], args?: unknown[]) { * Nibble lookup table to convert ASCII character codes to their hexadecimal values (0-15). * Invalid characters are marked with 0xFF. */ -const nibbleLookup: Uint8Array = (() => { - const lookup = new Uint8Array(256); - for (let i = 0; i < 256; i++) { - lookup[i] = 0xFF; // Mark as invalid by default - } - '0123456789abcdef'.split('').forEach((char, index) => { - lookup[char.charCodeAt(0)] = index; - }); - '0123456789ABCDEF'.split('').forEach((char, index) => { - lookup[char.charCodeAt(0)] = index; - }); - return lookup; +const nibbleLookup = (() => { + const lookup = new Uint8Array(256); // this is line 421 + for (let i = 0; i < 256; i++) { + lookup[i] = 0xFF; // Mark as invalid by default + } + '0123456789abcdef'.split('').forEach((char, index) => { + lookup[char.charCodeAt(0)] = index; + }); + '0123456789ABCDEF'.split('').forEach((char, index) => { + lookup[char.charCodeAt(0)] = index; + }); + return lookup; })(); /** * Byte-to-hex lookup table for efficient conversion from bytes to hexadecimal strings. */ -const byteToHexTable: string[] = (() => { - const table: string[] = new Array(256); - for (let i = 0; i < 256; i++) { - table[i] = (i < 16 ? '0' : '') + i.toString(16); - } - return table; +const byteToHexTable = (() => { + const table = new Array(256); + for (let i = 0; i < 256; i++) { + table[i] = (i < 16 ? '0' : '') + i.toString(16); + } + return table; })(); export function bufFromHex(hex: string): Uint8Array { - const len = hex.length; - const size = Math.ceil(len / 2); - const buf = new Uint8Array(size); - let j = 0; - - for (let i = 0; i < len; i += 2) { - const hiChar = hex.charCodeAt(i); - const loChar = i + 1 < len ? hex.charCodeAt(i + 1) : 0x30; // '0' if no character - const hi = nibbleLookup[hiChar]; - const lo = nibbleLookup[loChar]; - - if (hi === 0xFF || lo === 0xFF) { - throw new Error( - `Invalid hex character encountered: '${hex[i]}' or '${hex[i + 1] || '0'}'` - ); - } - - buf[j++] = (hi << 4) | lo; - } - - return buf; + const len = hex.length; + const buf = new Uint8Array(Math.ceil(len / 2)); + let j = 0; + + for (let i = 0; i < len; i += 2) { + const hiChar = hex.charCodeAt(i); + const loChar = i + 1 < len ? hex.charCodeAt(i + 1) : 0x30; // '0' if no character + const hi = nibbleLookup[hiChar]; + const lo = nibbleLookup[loChar]; + + if (hi === 0xFF || lo === 0xFF) { + throw new Error( + `Invalid hex character encountered: '${hex[i]}' or '${hex[i + 1] || '0'}'` + ); + } + + buf[j++] = (hi << 4) | lo; + } + + return buf; } + export function bufWriteHex(buf: Uint8Array, hex: string, offset: number = 0): void { - const len = hex.length; - const size = Math.ceil(len / 2); - let j = offset; - - for (let i = 0; i < len; i += 2) { - const hiChar = hex.charCodeAt(i); - const loChar = i + 1 < len ? hex.charCodeAt(i + 1) : 0x30; // '0' if no character - const hi = nibbleLookup[hiChar]; - const lo = nibbleLookup[loChar]; - - if (hi === 0xFF || lo === 0xFF) { - throw new Error( - `Invalid hex character encountered: '${hex[i]}' or '${hex[i + 1] || '0'}'` - ); - } - - buf[j++] = (hi << 4) | lo; - } + const len = hex.length; + let j = offset; + + for (let i = 0; i < len; i += 2) { + const hiChar = hex.charCodeAt(i); + const loChar = i + 1 < len ? hex.charCodeAt(i + 1) : 0x30; // '0' if no character + const hi = nibbleLookup[hiChar]; + const lo = nibbleLookup[loChar]; + + if (hi === 0xFF || lo === 0xFF) { + throw new Error( + `Invalid hex character encountered: '${hex[i]}' or '${hex[i + 1] || '0'}'` + ); + } + + buf[j++] = (hi << 4) | lo; + } } + export function bufReadHex(buf: Uint8Array, start: number = 0, end?: number): string { - if (end === undefined) end = buf.length; - let out = ''; - for (let i = start; i < end; i++) { - out += byteToHexTable[buf[i]]; - } - return out; + if (end === undefined) end = buf.length; + let out = ''; + for (let i = start; i < end; i++) { + out += byteToHexTable[buf[i]]; + } + return out; } export class Multiset extends Map { From 75de3473ee966ae549ccd1dcd0e9c86f3214cd32 Mon Sep 17 00:00:00 2001 From: Dieter Reinert Date: Sat, 11 Jan 2025 21:32:17 +0100 Subject: [PATCH 3/6] fix eslint2 --- lib/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/utils.ts b/lib/utils.ts index 2b2edc4eed01..0ab375dbbaeb 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -465,7 +465,7 @@ export function bufFromHex(hex: string): Uint8Array { return buf; } -export function bufWriteHex(buf: Uint8Array, hex: string, offset: number = 0): void { +export function bufWriteHex(buf: Uint8Array, hex: string, offset = 0): void { const len = hex.length; let j = offset; @@ -485,7 +485,7 @@ export function bufWriteHex(buf: Uint8Array, hex: string, offset: number = 0): v } } -export function bufReadHex(buf: Uint8Array, start: number = 0, end?: number): string { +export function bufReadHex(buf: Uint8Array, start = 0, end?): string { if (end === undefined) end = buf.length; let out = ''; for (let i = start; i < end; i++) { From 37d94b9222fb1eb671d787852b8a99dd5da33305 Mon Sep 17 00:00:00 2001 From: Dieter Reinert Date: Sat, 11 Jan 2025 21:36:19 +0100 Subject: [PATCH 4/6] fix eslint3 --- lib/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils.ts b/lib/utils.ts index 0ab375dbbaeb..c09b125b3b9e 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -485,7 +485,7 @@ export function bufWriteHex(buf: Uint8Array, hex: string, offset = 0): void { } } -export function bufReadHex(buf: Uint8Array, start = 0, end?): string { +export function bufReadHex(buf: Uint8Array, start = 0, end?: Number): string { if (end === undefined) end = buf.length; let out = ''; for (let i = start; i < end; i++) { From c6a4c2091623b6da621c156871db8d5e3e6fe5ee Mon Sep 17 00:00:00 2001 From: Dieter Reinert Date: Sat, 11 Jan 2025 21:37:09 +0100 Subject: [PATCH 5/6] typo --- lib/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils.ts b/lib/utils.ts index c09b125b3b9e..5c0c4611a6ff 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -485,7 +485,7 @@ export function bufWriteHex(buf: Uint8Array, hex: string, offset = 0): void { } } -export function bufReadHex(buf: Uint8Array, start = 0, end?: Number): string { +export function bufReadHex(buf: Uint8Array, start = 0, end?: number): string { if (end === undefined) end = buf.length; let out = ''; for (let i = start; i < end; i++) { From 018f3f37e219e9e1004dbfe4fa34cb69877697a2 Mon Sep 17 00:00:00 2001 From: Dieter Reinert Date: Sat, 11 Jan 2025 21:56:09 +0100 Subject: [PATCH 6/6] missed debug comment --- lib/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils.ts b/lib/utils.ts index 5c0c4611a6ff..af0953d583d3 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -418,7 +418,7 @@ export function formatSQLArray(arr: unknown[], args?: unknown[]) { * Invalid characters are marked with 0xFF. */ const nibbleLookup = (() => { - const lookup = new Uint8Array(256); // this is line 421 + const lookup = new Uint8Array(256); for (let i = 0; i < 256; i++) { lookup[i] = 0xFF; // Mark as invalid by default }