From 514ea9254f8f55f56767d5f06dea988d0365c3cc Mon Sep 17 00:00:00 2001 From: MierenManz Date: Tue, 9 Jan 2024 11:51:24 +0100 Subject: [PATCH 1/8] optimize array allocation Setting `Array.length` is not the correct way to allocate upfront. It seems fast but is not. `new Array(length)` is a lot faster --- src/array/array.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/array/array.ts b/src/array/array.ts index fe11b32..ee2dab5 100644 --- a/src/array/array.ts +++ b/src/array/array.ts @@ -7,8 +7,7 @@ export class ArrayType extends UnsizedType { readPacked(dt: DataView, options: Options = { byteOffset: 0 }): T[] { if (this.length === 0) return []; - const result = []; - result.length = this.length; + const result = new Array(this.length); for (let i = 0; i < this.length; i++) { result[i] = this.type.readPacked(dt, options); @@ -20,15 +19,14 @@ export class ArrayType extends UnsizedType { read(dt: DataView, options: Options = { byteOffset: 0 }): T[] { if (this.length === 0) return []; - const result: unknown[] = []; - result.length = this.length; + const result = new Array(this.length); for (let i = 0; i < this.length; i++) { result[i] = this.type.read(dt, options); // No need for the increment offset. This is handled by the `type.read` function } - return result as T[]; + return result; } writePacked( From b04ccf4dbfd74611ae63523f9c0db94585378475 Mon Sep 17 00:00:00 2001 From: MierenManz Date: Tue, 9 Jan 2024 12:20:23 +0100 Subject: [PATCH 2/8] use `result.length` Allows v8 to determine all inserts are inbound which makes it skip bound checks (5% faster) --- src/array/array.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/array/array.ts b/src/array/array.ts index ee2dab5..6cfec8f 100644 --- a/src/array/array.ts +++ b/src/array/array.ts @@ -9,7 +9,7 @@ export class ArrayType extends UnsizedType { if (this.length === 0) return []; const result = new Array(this.length); - for (let i = 0; i < this.length; i++) { + for (let i = 0; i < result.length; i++) { result[i] = this.type.readPacked(dt, options); // No need for the increment offset. This is handled by the `type.readPacked` function } @@ -21,7 +21,7 @@ export class ArrayType extends UnsizedType { if (this.length === 0) return []; const result = new Array(this.length); - for (let i = 0; i < this.length; i++) { + for (let i = 0; i < result.length; i++) { result[i] = this.type.read(dt, options); // No need for the increment offset. This is handled by the `type.read` function } From 9d25fc65c29b8f8c7f52811240b31eec5d351273 Mon Sep 17 00:00:00 2001 From: MierenManz Date: Tue, 9 Jan 2024 12:27:19 +0100 Subject: [PATCH 3/8] destructure `this` and loop over `value.length` to skip bound check (6% faster) --- src/array/array.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/array/array.ts b/src/array/array.ts index 6cfec8f..9e70cbf 100644 --- a/src/array/array.ts +++ b/src/array/array.ts @@ -37,10 +37,12 @@ export class ArrayType extends UnsizedType { if (value.length !== this.length) { throw new TypeError("T[].length !== ArrayType.length"); } + if (value.length === 0) return; + const { type } = this; - for (let i = 0; i < this.length; i++) { - this.type.writePacked(value[i], dt, options); + for (let i = 0; i < value.length; i++) { + type.writePacked(value[i], dt, options); // No need for the increment offset. This is handled by the `type.writePacked` function } } From edd2e77fc731ac2537efc96e1be2fb384c785f32 Mon Sep 17 00:00:00 2001 From: MierenManz Date: Tue, 9 Jan 2024 12:27:56 +0100 Subject: [PATCH 4/8] ditto --- src/array/array.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/array/array.ts b/src/array/array.ts index 9e70cbf..bb54c89 100644 --- a/src/array/array.ts +++ b/src/array/array.ts @@ -55,10 +55,12 @@ export class ArrayType extends UnsizedType { if (value.length !== this.length) { throw new TypeError("T[].length !== ArrayType.length"); } + if (value.length === 0) return; + const { type } = this; - for (let i = 0; i < this.length; i++) { - this.type.write(value[i], dt, options); + for (let i = 0; i < value.length; i++) { + type.write(value[i], dt, options); // No need for the increment offset. This is handled by the `type.write` function } } From 81effdadbbf325205581f4deb58ef9b478e135cf Mon Sep 17 00:00:00 2001 From: MierenManz Date: Thu, 11 Jan 2024 09:00:19 +0100 Subject: [PATCH 5/8] x --- src/array/array.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/array/array.ts b/src/array/array.ts index 115cb71..44ceedb 100644 --- a/src/array/array.ts +++ b/src/array/array.ts @@ -8,16 +8,10 @@ export class ArrayType extends UnsizedType { readPacked(dt: DataView, options: Options = { byteOffset: 0 }): T[] { if (this.length === 0) return []; const result = new Array(this.length); -<<<<<<< HEAD - - for (let i = 0; i < result.length; i++) { - result[i] = this.type.readPacked(dt, options); -======= const { type } = this; for (let i = 0; i < result.length; i++) { result[i] = type.readPacked(dt, options); ->>>>>>> 3ece55a2b93a8535164340828ebbce8bc416a9e8 // No need for the increment offset. This is handled by the `type.readPacked` function } @@ -46,13 +40,7 @@ export class ArrayType extends UnsizedType { throw new TypeError("T[].length !== ArrayType.length"); } -<<<<<<< HEAD - if (value.length === 0) return; - const { type } = this; - -======= const { type } = this; ->>>>>>> 3ece55a2b93a8535164340828ebbce8bc416a9e8 for (let i = 0; i < value.length; i++) { type.writePacked(value[i], dt, options); // No need for the increment offset. This is handled by the `type.writePacked` function @@ -68,13 +56,7 @@ export class ArrayType extends UnsizedType { throw new TypeError("T[].length !== ArrayType.length"); } -<<<<<<< HEAD - if (value.length === 0) return; - const { type } = this; - -======= const { type } = this; ->>>>>>> 3ece55a2b93a8535164340828ebbce8bc416a9e8 for (let i = 0; i < value.length; i++) { type.write(value[i], dt, options); // No need for the increment offset. This is handled by the `type.write` function From 8691e158774c5d56411be9512e9eff58d89b3d14 Mon Sep 17 00:00:00 2001 From: MierenManz Date: Fri, 9 Feb 2024 10:10:14 +0100 Subject: [PATCH 6/8] small ints --- src/small_number/mod.ts | 49 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/small_number/mod.ts diff --git a/src/small_number/mod.ts b/src/small_number/mod.ts new file mode 100644 index 0000000..c4321f2 --- /dev/null +++ b/src/small_number/mod.ts @@ -0,0 +1,49 @@ +import { type Options, SizedType } from "../mod.ts"; + +type U2Number = 0 | 1 | 2 | 3; +type U4Number = U2Number | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15; + +export class U2 extends SizedType { + constructor() { + super(1, 1); + } + + readPacked(dt: DataView, options: Options = { byteOffset: 0 }): U2Number { + const v = dt.getUint8(options.byteOffset) & 0b11; + super.incrementOffset(options); + return v as U2Number; + } + + writePacked( + value: U2Number, + dt: DataView, + options: Options = { byteOffset: 0 }, + ): void { + dt.setUint8(options.byteOffset, value & 0b11); + super.incrementOffset(options); + } +} + +export class U4 extends SizedType { + constructor() { + super(1, 1); + } + + readPacked(dt: DataView, options: Options = { byteOffset: 0 }): U4Number { + const v = dt.getUint8(options.byteOffset) & 0b1111; + super.incrementOffset(options); + return v as U4Number; + } + + writePacked( + value: U4Number, + dt: DataView, + options: Options = { byteOffset: 0 }, + ): void { + dt.setUint8(options.byteOffset, value & 0b1111); + super.incrementOffset(options); + } +} + +export const u2 = new U2(); +export const u4 = new U4(); From b38b550f238eaf114c3f6eb7be83341c5b7d2bdb Mon Sep 17 00:00:00 2001 From: Skye Date: Sun, 3 Mar 2024 23:13:56 +0100 Subject: [PATCH 7/8] ignores --- src/array/array_bench.ts | 1 + src/compound/tuple_bench.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/array/array_bench.ts b/src/array/array_bench.ts index 4e67ece..6fd9a8b 100644 --- a/src/array/array_bench.ts +++ b/src/array/array_bench.ts @@ -7,6 +7,7 @@ const AB = new Uint8Array(SIZE).map((_, i) => i).buffer; const DT = new DataView(AB); const atn = new ArrayType(u8, SIZE); +// @ts-ignore: const ato = new OArrayType(u8, SIZE); Deno.bench("nop", () => {}); diff --git a/src/compound/tuple_bench.ts b/src/compound/tuple_bench.ts index 8113ba6..f6dd9a3 100644 --- a/src/compound/tuple_bench.ts +++ b/src/compound/tuple_bench.ts @@ -3,6 +3,7 @@ import { Tuple, u32, u8 } from "../mod.ts"; const record = [u32, u8]; +// @ts-ignore: const oTuple = new OTuple(record); const nTuple = new Tuple(record); From 5b4329b9b826ca92ba1597bda675f5214870a661 Mon Sep 17 00:00:00 2001 From: Skye Date: Wed, 12 Jun 2024 22:22:10 +0200 Subject: [PATCH 8/8] remove unneeded benchmarks --- src/array/array_bench.ts | 73 ------------------------- src/bitflags/bitflags64_bench.ts | 92 -------------------------------- src/compound/tuple_bench.ts | 75 -------------------------- 3 files changed, 240 deletions(-) delete mode 100644 src/array/array_bench.ts delete mode 100644 src/bitflags/bitflags64_bench.ts delete mode 100644 src/compound/tuple_bench.ts diff --git a/src/array/array_bench.ts b/src/array/array_bench.ts deleted file mode 100644 index 6fd9a8b..0000000 --- a/src/array/array_bench.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { ArrayType as OArrayType } from "https://raw.githubusercontent.com/denosaurs/byte_type/796f19a5d0e4368b3910aa1e13fac70d59a7221d/mod.ts"; -import { ArrayType, u8 } from "../../mod.ts"; - -const SIZE = 10; -const DATA = new Array(SIZE).fill(0).map((_, i) => i); -const AB = new Uint8Array(SIZE).map((_, i) => i).buffer; -const DT = new DataView(AB); - -const atn = new ArrayType(u8, SIZE); -// @ts-ignore: -const ato = new OArrayType(u8, SIZE); - -Deno.bench("nop", () => {}); -//#region READ BENCHMARKS -Deno.bench({ - name: "Old Read Packed", - group: "rp", - fn: () => { - ato.readPacked(DT); - }, -}); -Deno.bench({ - name: "New Read Packed", - group: "rp", - fn: () => { - atn.readPacked(DT); - }, -}); -Deno.bench({ - name: "Old Read", - group: "r", - fn: () => { - ato.read(DT); - }, -}); -Deno.bench({ - name: "New Read", - group: "r", - fn: () => { - atn.read(DT); - }, -}); -//#endregion -//#region WRITE BENCHMARKS -Deno.bench({ - name: "Old Write Packed", - group: "wp", - fn: () => { - ato.writePacked(DATA, DT); - }, -}); -Deno.bench({ - name: "New Write Packed", - group: "wp", - fn: () => { - atn.writePacked(DATA, DT); - }, -}); -Deno.bench({ - name: "Old Write", - group: "w", - fn: () => { - ato.write(DATA, DT); - }, -}); -Deno.bench({ - name: "New Write", - group: "w", - fn: () => { - atn.write(DATA, DT); - }, -}); -//#endregion diff --git a/src/bitflags/bitflags64_bench.ts b/src/bitflags/bitflags64_bench.ts deleted file mode 100644 index 77aede9..0000000 --- a/src/bitflags/bitflags64_bench.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { BitFlags64 as OBF8 } from "https://raw.githubusercontent.com/denosaurs/byte_type/796f19a5d0e4368b3910aa1e13fac70d59a7221d/mod.ts"; -import { BitFlags64 } from "./mod.ts"; - -const AB = new ArrayBuffer(8); -const DT = new DataView(AB); -const inputRecord = { - one: 1n, - two: 2n, - four: 4n, - eight: 8n, - sixteen: 16n, - thirtytwo: 32n, - sixtyfour: 64n, - onetwentyeight: 128n, -}; - -const dataRecord = { - one: true, - two: true, - four: true, - eight: true, - sixteen: true, - thirtytwo: true, - sixtyfour: true, - onetwentyeight: true, -}; - -const obf = new OBF8(inputRecord); -const nbf = new BitFlags64(inputRecord); - -Deno.bench("nop", () => {}); - -//#region READ BENCHMARKS -Deno.bench({ - name: "Old Read Packed", - group: "rp", - fn: () => { - obf.readPacked(DT); - }, -}); -Deno.bench({ - name: "New Read Packed", - group: "rp", - fn: () => { - nbf.readPacked(DT); - }, -}); -Deno.bench({ - name: "Old Read", - group: "r", - fn: () => { - obf.read(DT); - }, -}); -Deno.bench({ - name: "New Read", - group: "r", - fn: () => { - nbf.read(DT); - }, -}); -//#endregion -//#region WRITE BENCHMARKS -Deno.bench({ - name: "Old Write Packed", - group: "wp", - fn: () => { - obf.writePacked(dataRecord, DT); - }, -}); -Deno.bench({ - name: "New Write Packed", - group: "wp", - fn: () => { - nbf.writePacked(dataRecord, DT); - }, -}); -Deno.bench({ - name: "Old Write", - group: "w", - fn: () => { - obf.write(dataRecord, DT); - }, -}); -Deno.bench({ - name: "New Write", - group: "w", - fn: () => { - nbf.write(dataRecord, DT); - }, -}); -//#endregion diff --git a/src/compound/tuple_bench.ts b/src/compound/tuple_bench.ts deleted file mode 100644 index f6dd9a3..0000000 --- a/src/compound/tuple_bench.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Tuple as OTuple } from "https://raw.githubusercontent.com/denosaurs/byte_type/796f19a5d0e4368b3910aa1e13fac70d59a7221d/mod.ts"; -import { Tuple, u32, u8 } from "../mod.ts"; - -const record = [u32, u8]; - -// @ts-ignore: -const oTuple = new OTuple(record); -const nTuple = new Tuple(record); - -const data = [32, 8]; -const AB = new ArrayBuffer(256); -const DT = new DataView(AB); - -Deno.bench("nop", () => {}); - -//#region READ BENCHMARKS -Deno.bench({ - name: "Old Read Packed", - group: "rp", - fn: () => { - oTuple.readPacked(DT); - }, -}); -Deno.bench({ - name: "New Read Packed", - group: "rp", - fn: () => { - nTuple.readPacked(DT); - }, -}); -Deno.bench({ - name: "Old Read", - group: "r", - fn: () => { - oTuple.read(DT); - }, -}); -Deno.bench({ - name: "New Read", - group: "r", - fn: () => { - nTuple.read(DT); - }, -}); -//#endregion -//#region WRITE BENCHMARKS -Deno.bench({ - name: "Old Write Packed", - group: "wp", - fn: () => { - oTuple.writePacked(data, DT); - }, -}); -Deno.bench({ - name: "New Write Packed", - group: "wp", - fn: () => { - nTuple.writePacked(data, DT); - }, -}); -Deno.bench({ - name: "Old Write", - group: "w", - fn: () => { - oTuple.write(data, DT); - }, -}); -Deno.bench({ - name: "New Write", - group: "w", - fn: () => { - nTuple.write(data, DT); - }, -}); -//#endregion