Skip to content

Commit

Permalink
Unroll u8aToBigInt loops (#1821)
Browse files Browse the repository at this point in the history
* Unroll u8aToBigInt loops

* Bump dev

* Rolling tests
  • Loading branch information
jacogr authored May 13, 2023
1 parent b73b96e commit e5373c3
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 114 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Changes:

- Align `u8aToBigInt` tests with `u8aToBn`
- Unroll loops on most-frequently used paths for `u8aToBigInt`


## 12.1.2 May 1, 2023
Expand Down
63 changes: 60 additions & 3 deletions packages/util/src/u8a/toBigInt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ describe('u8aToBigInt', (): void => {
).toBe('78563412');
});

it('converts values (i32)', (): void => {
expect(
u8aToBigInt(
new Uint8Array([0xf2, 0x34, 0x56, 0x78]),
{ isLe: false, isNegative: true }
).toString(16)
).toBe('-dcba988');
});

it('converts values (u40)', (): void => {
expect(
u8aToBigInt(
Expand All @@ -71,6 +80,29 @@ describe('u8aToBigInt', (): void => {
).toString(16)
).toBe('bc9a78563412');
});

it('converts values (u128)', (): void => {
expect(
u8aToBigInt(
new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78]),
{ isLe: true }
).toString(16)
).toBe('78563412785634127856341278563412');
});

for (let i = 1; i < 32; i++) {
const tu8a = [0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78];
const tstr = tu8a.map((n) => n.toString(16));

it(`converts values with length ${i}`, (): void => {
expect(
u8aToBigInt(
new Uint8Array(tu8a.slice(0, i)),
{ isLe: true }
).toString(16)
).toBe(tstr.slice(0, i).reverse().join(''));
});
}
});

describe('signed', (): void => {
Expand Down Expand Up @@ -193,6 +225,29 @@ describe('u8aToBigInt', (): void => {
).toString(16)
).toBe('123456789abc');
});

it('converts values (u128)', (): void => {
expect(
u8aToBigInt(
new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78]),
{ isLe: false }
).toString(16)
).toBe('12345678123456781234567812345678');
});

for (let i = 1; i < 32; i++) {
const tu8a = [0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78];
const tstr = tu8a.map((n) => n.toString(16));

it(`converts values with length ${i}`, (): void => {
expect(
u8aToBigInt(
new Uint8Array(tu8a.slice(0, i)),
{ isLe: false }
).toString(16)
).toBe(tstr.slice(0, i).join(''));
});
}
});

describe('empty creation', (): void => {
Expand Down Expand Up @@ -262,9 +317,11 @@ describe('u8aToBigInt', (): void => {
).toBe(256n);
});

perf('u8aToBigInt (u32)', 1_000_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], u8aToBigInt);
perf('u8aToBigInt (i32)', 1_000_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], (v: Uint8Array) => u8aToBigInt(v, { isNegative: true }));
perf('u8aToBigInt (u64)', 500_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c])]], u8aToBigInt);
perf('u8aToBigInt (i32)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], (v: Uint8Array) => u8aToBigInt(v, { isNegative: true }));

perf('u8aToBigInt (u32)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], u8aToBigInt);
perf('u8aToBigInt (u64)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c])]], u8aToBigInt);
perf('u8aToBigInt (u128)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c])]], u8aToBigInt);

// perf('BigInt (constructor)', 1_000_000, [[12345678]], (v: number) => BigInt(v).toString());
// perf('BigInt (constructor -> string)', 1_000_000, [[12345678]], (v: number) => BigInt(v).toString());
Expand Down
85 changes: 65 additions & 20 deletions packages/util/src/u8a/toBigInt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,90 @@ import { _1n } from '../bi/consts.js';

const U8_MAX = BigInt(256);
const U16_MAX = BigInt(256 * 256);
const U64_MAX = BigInt('0x10000000000000000');

/**
* @name u8aToBigInt
* @summary Creates a BigInt from a Uint8Array object.
*/
export function u8aToBigInt (value: Uint8Array, { isLe = true, isNegative = false }: ToBnOptions = {}): bigint {
if (!value || !value.length) {
return BigInt(0);
}

// BE is not the optimal path - LE is the SCALE default
const u8a = isLe
? value
: value.reverse();
const dvI = new DataView(u8a.buffer, u8a.byteOffset);
: value.slice().reverse();
const count = u8a.length;
const mod = count % 2;
let result = BigInt(0);

// This is mostly written for readability (with the single isNegative shortcut),
// as opposed to performance, e.g. `u8aToBn` does loop unrolling, etc.
if (isNegative) {
for (let i = count - 2; i >= mod; i -= 2) {
result = (result * U16_MAX) + BigInt(dvI.getUint16(i, true) ^ 0xffff);
switch (count) {
case 0:
return BigInt(0);

case 1:
return BigInt(((u8a[0] ^ 0x0000_00ff) * -1) - 1);

case 2:
return BigInt((((u8a[0] + (u8a[1] << 8)) ^ 0x0000_ffff) * -1) - 1);

case 4:
return BigInt((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) ^ 0xffff_ffff) * -1) - 1);
}

if (mod) {
result = (result * U8_MAX) + BigInt(u8a[0] ^ 0xff);
const dvI = new DataView(u8a.buffer, u8a.byteOffset);

if (count === 8) {
return dvI.getBigInt64(0, true);
}
} else {

let result = BigInt(0);
const mod = count % 2;

for (let i = count - 2; i >= mod; i -= 2) {
result = (result * U16_MAX) + BigInt(dvI.getUint16(i, true));
result = (result * U16_MAX) + BigInt(dvI.getUint16(i, true) ^ 0xffff);
}

if (mod) {
result = (result * U8_MAX) + BigInt(u8a[0]);
result = (result * U8_MAX) + BigInt(u8a[0] ^ 0xff);
}

return (result * -_1n) - _1n;
}

switch (count) {
case 0:
return BigInt(0);

case 1:
return BigInt(u8a[0]);

case 2:
return BigInt(u8a[0] + (u8a[1] << 8));

case 4:
return BigInt(u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00));
}

return isNegative
? ((result * -_1n) - _1n)
: result;
const dvI = new DataView(u8a.buffer, u8a.byteOffset);

switch (count) {
case 8:
return dvI.getBigUint64(0, true);

case 16:
return (dvI.getBigUint64(8, true) * U64_MAX) + dvI.getBigUint64(0, true);

default: {
let result = BigInt(0);
const mod = count % 2;

for (let i = count - 2; i >= mod; i -= 2) {
result = (result * U16_MAX) + BigInt(dvI.getUint16(i, true));
}

if (mod) {
result = (result * U8_MAX) + BigInt(u8a[0]);
}

return result;
}
}
}
63 changes: 60 additions & 3 deletions packages/util/src/u8a/toBn.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,29 @@ describe('u8aToBn', (): void => {
).toString(16)
).toBe('bc9a78563412');
});

it('converts values (u128)', (): void => {
expect(
u8aToBn(
new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78]),
{ isLe: true }
).toString(16)
).toBe('78563412785634127856341278563412');
});

for (let i = 1; i < 32; i++) {
const tu8a = [0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78];
const tstr = tu8a.map((n) => n.toString(16));

it(`converts values with length ${i}`, (): void => {
expect(
u8aToBn(
new Uint8Array(tu8a.slice(0, i)),
{ isLe: true }
).toString(16)
).toBe(tstr.slice(0, i).reverse().join(''));
});
}
});

describe('signed', (): void => {
Expand Down Expand Up @@ -177,6 +200,15 @@ describe('u8aToBn', (): void => {
).toBe('12345678');
});

it('converts values (i32)', (): void => {
expect(
u8aToBn(
new Uint8Array([0xf2, 0x34, 0x56, 0x78]),
{ isLe: false, isNegative: true }
).toString(16)
).toBe('-dcba988');
});

it('converts values (u40)', (): void => {
expect(
u8aToBn(
Expand All @@ -194,6 +226,29 @@ describe('u8aToBn', (): void => {
).toString(16)
).toBe('123456789abc');
});

it('converts values (u128)', (): void => {
expect(
u8aToBn(
new Uint8Array([0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78]),
{ isLe: false }
).toString(16)
).toBe('12345678123456781234567812345678');
});

for (let i = 1; i < 32; i++) {
const tu8a = [0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78];
const tstr = tu8a.map((n) => n.toString(16));

it(`converts values with length ${i}`, (): void => {
expect(
u8aToBn(
new Uint8Array(tu8a.slice(0, i)),
{ isLe: false }
).toString(16)
).toBe(tstr.slice(0, i).join(''));
});
}
});

describe('empty creation', (): void => {
Expand Down Expand Up @@ -263,9 +318,11 @@ describe('u8aToBn', (): void => {
).toBe(256);
});

perf('u8aToBn (u32)', 1_000_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], u8aToBn);
perf('u8aToBn (i32)', 1_000_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], (v: Uint8Array) => u8aToBn(v, { isNegative: true }));
perf('u8aToBn (u64)', 500_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c])]], u8aToBn);
perf('u8aToBn (i32)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], (v: Uint8Array) => u8aToBn(v, { isNegative: true }));

perf('u8aToBn (u32)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c])]], u8aToBn);
perf('u8aToBn (u64)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c])]], u8aToBn);
perf('u8aToBn (u128)', 750_000, [[new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c, 0x68, 0x65, 0x6c, 0x6c])]], u8aToBn);

// perf('BN (constructor)', 1_000_000, [[12345678]], (v: number) => new BN(v));
// perf('BN (constructor -> string)', 1_000_000, [[12345678]], (v: number) => new BN(v).toString());
Expand Down
Loading

0 comments on commit e5373c3

Please sign in to comment.