Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: faster hex to byte implementation #6596

Merged
merged 6 commits into from
Nov 20, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 40 additions & 11 deletions packages/web3-validator/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,20 +449,49 @@ export function uint8ArrayToHexString(uint8Array: Uint8Array): string {
return hexString;
}

// for optimized technique for hex to bytes conversion
const charCodeMap = {
zero: 48,
nine: 57,
A: 65,
F: 70,
a: 97,
f: 102,
} as const

function charCodeToBase16(char: number) {
if (char >= charCodeMap.zero && char <= charCodeMap.nine)
return char - charCodeMap.zero
if (char >= charCodeMap.A && char <= charCodeMap.F)
return char - (charCodeMap.A - 10)
if (char >= charCodeMap.a && char <= charCodeMap.f)
return char - (charCodeMap.a - 10)
return undefined
}
Comment on lines +453 to +470
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mpetrunic Is this function not equivalent to the native parseInt with difference take it take integer parameter while parseInt take string parameter as charCodeToBase16(97) is same as parseInt('a', 16).

The native parseInt in that use case is almost 5 times more faster.

https://runkit.com/nazarhussain/6571df9b17589500083041e5

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well yes, but for parseInt, I need to substring 2 characters which is expensive (memory allocation and all).
Funny enough, benching on runkit yields totally different results than locally:
image
image


export function hexToUint8Array(hex: string): Uint8Array {
let value;
if (hex.toLowerCase().startsWith('0x')) {
value = hex.slice(2);
} else {
value = hex;
let offset = 0;
if (hex.startsWith('0') && (hex[1] === 'x' || hex[1] === 'X')) {
offset = 2;
}
if (value.length % 2 !== 0) {
if (hex.length % 2 !== 0) {
throw new InvalidBytesError(`hex string has odd length: ${hex}`);
}
const bytes = new Uint8Array(Math.ceil(value.length / 2));
for (let i = 0; i < bytes.length; i += 1) {
const byte = parseInt(value.substring(i * 2, i * 2 + 2), 16);
bytes[i] = byte;
const length = (hex.length - offset) / 2;
const bytes = new Uint8Array(length);
for (let index = 0, j = offset; index < length; index+=1) {
// eslint-disable-next-line no-plusplus
const nibbleLeft = charCodeToBase16(hex.charCodeAt(j++))
// eslint-disable-next-line no-plusplus
const nibbleRight = charCodeToBase16(hex.charCodeAt(j++))
if (nibbleLeft === undefined || nibbleRight === undefined) {
throw new InvalidBytesError(
`Invalid byte sequence ("${hex[j - 2]}${
hex[j - 1]
}" in "${hex}").`,
)
}
bytes[index] = nibbleLeft * 16 + nibbleRight
}
return bytes;
return bytes
}
Loading