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

dataview based readwrite num bytes opts #176

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
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
63 changes: 32 additions & 31 deletions src/types/basic/uint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -83,30 +89,24 @@ export class NumberUintType extends UintType<number> {
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;
}

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;
}
}
if (this.infinityWhenBig && isInfinity) {
return Infinity;
}
return Number(output);

return getNumberFromBytesLE(data, this.byteLength, offset);
}

struct_convertFromJson(data: Json): number {
Expand Down Expand Up @@ -258,15 +258,15 @@ export class BigIntUintType extends UintType<bigint> {
// 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;
}

Expand All @@ -283,28 +283,29 @@ export class BigIntUintType extends UintType<bigint> {
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;
}

Expand Down
156 changes: 156 additions & 0 deletions src/util/numhelper.ts
Original file line number Diff line number Diff line change
@@ -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);
}
28 changes: 28 additions & 0 deletions test/perf/uint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
12 changes: 12 additions & 0 deletions test/unit/util.test.ts
Original file line number Diff line number Diff line change
@@ -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", () => {
Expand All @@ -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);
}
});
});