From b2b2f3b7c398fd81be218ceb5d7a50717abb5f58 Mon Sep 17 00:00:00 2001 From: harkamal Date: Sun, 29 Aug 2021 15:16:57 +0530 Subject: [PATCH] * dataview based readwrite num bytes opts * tests --- src/types/basic/uint.ts | 63 ++++++++-------- src/util/numhelper.ts | 156 ++++++++++++++++++++++++++++++++++++++++ test/perf/uint.test.ts | 28 ++++++++ test/unit/util.test.ts | 12 ++++ 4 files changed, 228 insertions(+), 31 deletions(-) create mode 100644 src/util/numhelper.ts diff --git a/src/types/basic/uint.ts b/src/types/basic/uint.ts index 0e05af42..e1fd71ca 100644 --- a/src/types/basic/uint.ts +++ b/src/types/basic/uint.ts @@ -3,6 +3,12 @@ import {bigIntPow} from "../../util/bigInt"; import {isTypeOf, Type} from "../type"; import {BasicType} from "./abstract"; import {HashObject} from "@chainsafe/as-sha256"; +import { + getNumberFromBytesLE, + getBytesFromNumberLE, + getNumberFromBytesLE32, + getBytesFromNumberLE32, +} from "../../util/numhelper"; export interface IUintOptions { byteLength: number; @@ -83,12 +89,7 @@ export class NumberUintType extends UintType { output[i] = 0xff; } } else { - let v = value; - const MAX_BYTE = 0xff; - for (let i = 0; i < this.byteLength; i++) { - output[offset + i] = v & MAX_BYTE; - v = Math.floor(v / 256); - } + getBytesFromNumberLE(value, output, this.byteLength, offset); } return offset + this.byteLength; } @@ -96,9 +97,7 @@ export class NumberUintType extends UintType { struct_deserializeFromBytes(data: Uint8Array, offset: number): number { this.bytes_validate(data, offset); let isInfinity = true; - let output = 0; for (let i = 0; i < this.byteLength; i++) { - output += data[offset + i] * 2 ** (8 * i); if (data[offset + i] !== 0xff) { isInfinity = false; } @@ -106,7 +105,8 @@ export class NumberUintType extends UintType { if (this.infinityWhenBig && isInfinity) { return Infinity; } - return Number(output); + + return getNumberFromBytesLE(data, this.byteLength, offset); } struct_convertFromJson(data: Json): number { @@ -258,15 +258,15 @@ export class BigIntUintType extends UintType { // and do bit shifting on the number let v = value; let groupedBytes = Number(BigInt.asUintN(32, v)); - for (let i = 0; i < this.byteLength; i++) { - output[offset + i] = Number(groupedBytes & 0xff); - if ((i + 1) % 4 !== 0) { - groupedBytes >>= 8; - } else { - v >>= BIGINT_4_BYTES; - groupedBytes = Number(BigInt.asUintN(32, v)); - } + + let i; + for (i = 0; i < this.byteLength - 4; i += 4) { + getBytesFromNumberLE32(groupedBytes, output, offset + i); + v >>= BIGINT_4_BYTES; + groupedBytes = Number(BigInt.asUintN(32, v)); } + getBytesFromNumberLE(groupedBytes, output, this.byteLength - i, offset + i); + return offset + this.byteLength; } @@ -283,28 +283,29 @@ export class BigIntUintType extends UintType { let output = BigInt(0); let groupIndex = 0, groupOutput = 0; - for (let i = 0; i < this.byteLength; i++) { - groupOutput += data[offset + i] << (8 * (i % 4)); - if ((i + 1) % 4 === 0) { - // Left shift returns a signed integer and the output may have become negative - // In that case, the output needs to be converted to unsigned integer - if (groupOutput < 0) { - groupOutput >>>= 0; - } - // Optimization to set the output the first time, forgoing BigInt addition + + let i; + for (i = 0; i < this.byteLength - 4; i += 4) { + groupOutput = getNumberFromBytesLE32(data, offset + i); + // Optimization to set the output the first time, forgoing BigInt addition + if (groupOutput > 0) { if (groupIndex === 0) { output = BigInt(groupOutput); } else { output += BigInt(groupOutput) << BigInt(32 * groupIndex); } - groupIndex++; - groupOutput = 0; } + groupIndex++; } - // if this.byteLength isn't a multiple of 4, there will be additional data - if (groupOutput) { - output += BigInt(groupOutput >>> 0) << BigInt(32 * groupIndex); + groupOutput = getNumberFromBytesLE(data, this.byteLength - i, offset + i); + if (groupOutput > 0) { + if (groupIndex === 0) { + output = BigInt(groupOutput); + } else { + output += BigInt(groupOutput) << BigInt(32 * groupIndex); + } } + return output; } diff --git a/src/util/numhelper.ts b/src/util/numhelper.ts new file mode 100644 index 00000000..331ae47a --- /dev/null +++ b/src/util/numhelper.ts @@ -0,0 +1,156 @@ +export function copyFromBuf32LE(source: Buffer, target: Uint8Array, offset: number): void { + //unrolled fast copy + target[3 + offset] = source[3]; + target[2 + offset] = source[2]; + target[1 + offset] = source[1]; + target[0 + offset] = source[0]; +} + +export function copyToBuf32LE(source: Uint8Array, target: Buffer, offset: number): void { + //unrolled fast copy + target[3] = source[3 + offset]; + target[2] = source[2 + offset]; + target[1] = source[1 + offset]; + target[0] = source[0 + offset]; +} + +export function copyFromBuf64LE(source: Buffer, target: Uint8Array, length: number, offset: number): void { + //unrolled fast copy + switch (length) { + case 8: + target[7 + offset] = source[7]; + /* falls through */ + case 7: + target[6 + offset] = source[6]; + /* falls through */ + case 6: + target[5 + offset] = source[5]; + /* falls through */ + case 5: + target[4 + offset] = source[4]; + /* falls through */ + case 4: + target[3 + offset] = source[3]; + /* falls through */ + case 3: + target[2 + offset] = source[2]; + /* falls through */ + case 2: + target[1 + offset] = source[1]; + /* falls through */ + case 1: + target[0 + offset] = source[0]; + /* falls through */ + default: + } +} + +export function copyToBuf64LE(source: Uint8Array, target: Buffer, length: number, offset: number): void { + switch ( + length //we need to unroll it for fast computation, buffer copy/looping takes too much time + ) { + case 8: + target[7] = source[7 + offset]; + /* falls through */ + case 7: + target[6] = source[6 + offset]; + /* falls through */ + case 6: + target[5] = source[5 + offset]; + /* falls through */ + case 5: + target[4] = source[4 + offset]; + /* falls through */ + case 4: + target[3] = source[3 + offset]; + /* falls through */ + case 3: + target[2] = source[2 + offset]; + /* falls through */ + case 2: + target[1] = source[1 + offset]; + /* falls through */ + case 1: + target[0] = source[0 + offset]; + /* falls through */ + default: + } + switch ( + length //zero the rest + ) { + case 1: + target[1] = 0; + /* falls through */ + case 2: + target[2] = 0; + /* falls through */ + case 3: + target[3] = 0; + /* falls through */ + case 4: + target[4] = 0; + /* falls through */ + case 5: + target[5] = 0; + /* falls through */ + case 6: + target[6] = 0; + /* falls through */ + case 7: + target[7] = 0; + /* falls through */ + default: + } +} + +const workingABuf = new ArrayBuffer(8); +const wbuffer = Buffer.from(workingABuf); +const wView = new DataView(workingABuf); + +const TWO_POWER_32 = 2 ** 32; + +export function getBytesFromNumberLE(value: number, result: Uint8Array, length: number, offset: number): void { + // let mvalue; + length = Math.min(length, 8); + wView.setUint32(0, value, true); + if (length > 4) { + let mvalue = 0; + if (value >= TWO_POWER_32) mvalue = Math.floor(value / TWO_POWER_32); + wView.setUint32(4, mvalue, true); + } + copyFromBuf64LE(wbuffer, result, length, offset); +} + +export function getBytesFromNumberLE32(value: number, result: Uint8Array, offset: number): void { + // let mvalue; + wView.setUint32(0, value, true); + copyFromBuf32LE(wbuffer, result, offset); +} + +export function getNumberFromBytesLE(data: Uint8Array, length: number, offset = 0, ignoreUnsafe = true): number { + let isbigint = false, + combined, + left; + + length = Math.min(length, 8); + if (!ignoreUnsafe) { + if (length > 6) + isbigint = data[6] > 31 || data.slice(7, length).reduce((acc, vrow) => acc || vrow > 0, false as boolean); + + if (isbigint) throw new Error("UnSafeInteger"); + } + + copyToBuf64LE(data, wbuffer, length, offset); + + combined = wView.getUint32(0, true); + if (length > 4) { + left = wView.getUint32(4, true); + if (left > 0) combined = TWO_POWER_32 * left + combined; + } + return combined; +} + +export function getNumberFromBytesLE32(data: Uint8Array, offset = 0): number { + copyToBuf32LE(data, wbuffer, offset); + return wView.getUint32(0, true); +} diff --git a/test/perf/uint.test.ts b/test/perf/uint.test.ts index 27467925..662a3c3d 100644 --- a/test/perf/uint.test.ts +++ b/test/perf/uint.test.ts @@ -51,4 +51,32 @@ describe("Uint64 types", () => { } expect(tbState.slot).to.be.equal(numLoop); }); + + const TWO_POWER_32 = 2 ** 32; + const numTwoPower32Loop = TWO_POWER_32 + numLoop; + itBench(`struct - increase slot from 2**32 to 2**32 + ${numLoop}`, () => { + const state: IBeaconState = { + slot: TWO_POWER_32, + }; + for (let i = TWO_POWER_32; i < numTwoPower32Loop; i++) { + state.slot++; + } + expect(state.slot).to.be.equal(numTwoPower32Loop); + }); + + itBench(`NumberUintType - increase slot from 2**32 to 2**32 + ${numLoop}`, () => { + const tbState = BeaconState.createTreeBackedFromStruct({slot: TWO_POWER_32}); + for (let i = TWO_POWER_32; i < numTwoPower32Loop; i++) { + tbState.slot++; + } + expect(tbState.slot).to.be.equal(numTwoPower32Loop); + }); + + itBench(`Number64UintType - increase slot from 2**32 to 2**32 + ${numLoop}`, () => { + const tbState = BeaconState2.createTreeBackedFromStruct({slot: TWO_POWER_32}); + for (let i = TWO_POWER_32; i < numTwoPower32Loop; i++) { + tbState.slot++; + } + expect(tbState.slot).to.be.equal(numTwoPower32Loop); + }); }); diff --git a/test/unit/util.test.ts b/test/unit/util.test.ts index 35c95184..20470820 100644 --- a/test/unit/util.test.ts +++ b/test/unit/util.test.ts @@ -1,5 +1,7 @@ import {expect} from "chai"; import {bigIntPow} from "../../src/util/bigInt"; +import {getNumberFromBytesLE, getBytesFromNumberLE} from "../../src/util/numhelper"; +import {toBigIntLE, toBufferLE} from "bigint-buffer"; describe("util / bigIntPow", () => { it("should throw error on negative exponent", () => { @@ -12,4 +14,14 @@ describe("util / bigIntPow", () => { } } }); + it("numhelper byte serialization checks", () => { + const output = new Uint8Array(108); + for (let i = 0; i < 100; i++) { + const tnum = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); + getBytesFromNumberLE(tnum, output, (i % 8) + 1, i); + const mx = getNumberFromBytesLE(output, (i % 8) + 1, i); + const my = Number(toBigIntLE(toBufferLE(BigInt(tnum), (i % 8) + 1))); + expect(mx).to.be.equal(my); + } + }); });