Skip to content

Commit 6c2f861

Browse files
committed
cache DataView to improve performance
1 parent dd05a75 commit 6c2f861

2 files changed

Lines changed: 95 additions & 109 deletions

File tree

serde-generate/runtime/typescript/serde.ts

Lines changed: 85 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export type WrapperOfCase<T extends { $: string }, K = T["$"]> = T extends { $:
2626

2727
export interface Reader {
2828
readString(): string
29-
readBytes(): Uint8Array
3029
readBool(): boolean
3130
readUnit(): null
3231
readChar(): string
@@ -52,7 +51,6 @@ export interface Reader {
5251

5352
export interface Writer {
5453
writeString(value: string): void
55-
writeBytes(value: Uint8Array): void
5654
writeBool(value: boolean): void
5755
writeUnit(value: null): void
5856
writeChar(value: string): void
@@ -76,135 +74,119 @@ export interface Writer {
7674
sortMapEntries(offsets: number[]): void
7775
}
7876

77+
const BIG_32 = 32n
78+
const BIG_64 = 64n
79+
const BIG_32Fs = 429967295n
80+
const BIG_64Fs = 18446744073709551615n
81+
7982
export abstract class BinaryWriter implements Writer {
80-
public static readonly BIG_32 = 32n
81-
public static readonly BIG_64 = 64n
82-
public static readonly BIG_32Fs = 429967295n
83-
public static readonly BIG_64Fs = 18446744073709551615n
8483
public static readonly TEXT_ENCODER = new TextEncoder()
8584

86-
public buffer = new ArrayBuffer(8)
85+
public view = new DataView(new ArrayBuffer(128))
8786
public offset = 0
8887

89-
private ensureBufferWillHandleSize(bytes: number) {
90-
const wishSize = this.offset + bytes
91-
if (wishSize > this.buffer.byteLength) {
92-
let newBufferLength = this.buffer.byteLength
93-
while (newBufferLength < wishSize) newBufferLength *= 2
88+
private alloc(allocLength: number) {
89+
const wishSize = this.offset + allocLength
90+
91+
const currentLength = this.view.buffer.byteLength
92+
if (wishSize > currentLength) {
93+
let newBufferLength = currentLength
94+
while (newBufferLength <= wishSize) newBufferLength = newBufferLength << 1
9495

9596
// TODO: there is new API for resizing buffer, but in Node it seems to be slower then allocating new
9697
// this.buffer.resize(newBufferLength)
9798

9899
const newBuffer = new Uint8Array(newBufferLength)
99-
newBuffer.set(new Uint8Array(this.buffer))
100-
this.buffer = newBuffer.buffer
100+
newBuffer.set(new Uint8Array(this.view.buffer))
101+
102+
this.view = new DataView(newBuffer.buffer)
101103
}
102104
}
103105

104-
protected write(values: Uint8Array) {
105-
this.ensureBufferWillHandleSize(values.length)
106-
new Uint8Array(this.buffer, this.offset).set(values)
107-
this.offset += values.length
108-
}
109-
110106
abstract writeLength(value: number): void
111107
abstract writeVariantIndex(value: number): void
112108
abstract sortMapEntries(offsets: number[]): void
113109

114110
public writeString(value: string) {
115111
const bytes = value.length * 3 + 8
116-
this.ensureBufferWillHandleSize(bytes)
112+
this.alloc(bytes)
117113
// TODO: check this for correctness
118-
const { written } = BinaryWriter.TEXT_ENCODER.encodeInto(value, new Uint8Array(this.buffer, this.offset + 8))
114+
const { written } = BinaryWriter.TEXT_ENCODER.encodeInto(value, new Uint8Array(this.view.buffer, this.offset + 8))
119115
this.writeU64(written)
120116
this.offset += written
121117
}
122118

123-
public writeBytes(value: Uint8Array) {
124-
this.writeLength(value.length)
125-
this.write(value)
126-
}
127-
128119
public writeBool(value: boolean) {
129-
const byteValue = value ? 1 : 0
130-
this.write(new Uint8Array([byteValue]))
120+
this.writeU8(value ? 1 : 0)
131121
}
132122

133123
// eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/explicit-module-boundary-types
134124
public writeUnit(_value: null) {
135125
return
136126
}
137127

138-
private writeWithFunction(fn: (byteOffset: number, value: number, littleEndian: boolean) => void, bytesLength: number, value: number) {
139-
this.ensureBufferWillHandleSize(bytesLength)
140-
const dv = new DataView(this.buffer, this.offset)
141-
fn.apply(dv, [0, value, true])
142-
this.offset += bytesLength
143-
}
144-
145128
public writeU8(value: number) {
146-
this.write(new Uint8Array([value]))
129+
this.alloc(1)
130+
this.view.setUint8(this.offset, value)
131+
this.offset += 1
147132
}
148133

149134
public writeU16(value: number) {
150-
this.writeWithFunction(DataView.prototype.setUint16, 2, value)
135+
this.alloc(2)
136+
this.view.setUint16(this.offset, value, true)
137+
this.offset += 2
151138
}
152139

153140
public writeU32(value: number) {
154-
this.writeWithFunction(DataView.prototype.setUint32, 4, value)
141+
this.alloc(4)
142+
this.view.setUint32(this.offset, value, true)
143+
this.offset += 4
155144
}
156145

157146
public writeU64(value: bigint | number) {
158-
const low = BigInt(value) & BinaryWriter.BIG_32Fs,
159-
high = BigInt(value) >> BinaryWriter.BIG_32
147+
const low = BigInt(value) & BIG_32Fs, high = BigInt(value) >> BIG_32
160148

161149
// write little endian number
162150
this.writeU32(Number(low))
163151
this.writeU32(Number(high))
164152
}
165153

166154
public writeU128(value: bigint | number) {
167-
const low = BigInt(value) & BinaryWriter.BIG_64Fs,
168-
high = BigInt(value) >> BinaryWriter.BIG_64
155+
const low = BigInt(value) & BIG_64Fs, high = BigInt(value) >> BIG_64
169156

170157
// write little endian number
171158
this.writeU64(low)
172159
this.writeU64(high)
173160
}
174161

175162
public writeI8(value: number) {
176-
const bytes = 1
177-
this.ensureBufferWillHandleSize(bytes)
178-
new DataView(this.buffer, this.offset).setInt8(0, value)
179-
this.offset += bytes
163+
this.alloc(1)
164+
this.view.setInt8(this.offset, value)
165+
this.offset += 1
180166
}
181167

182168
public writeI16(value: number) {
183-
const bytes = 2
184-
this.ensureBufferWillHandleSize(bytes)
185-
new DataView(this.buffer, this.offset).setInt16(0, value, true)
186-
this.offset += bytes
169+
this.alloc(2)
170+
this.view.setInt16(this.offset, value, true)
171+
this.offset += 2
187172
}
188173

189174
public writeI32(value: number) {
190-
const bytes = 4
191-
this.ensureBufferWillHandleSize(bytes)
192-
new DataView(this.buffer, this.offset).setInt32(0, value, true)
193-
this.offset += bytes
175+
this.alloc(4)
176+
this.view.setInt32(this.offset, value, true)
177+
this.offset += 4
194178
}
195179

196180
public writeI64(value: bigint | number) {
197-
const low = BigInt(value) & BinaryWriter.BIG_32Fs,
198-
high = BigInt(value) >> BinaryWriter.BIG_32
181+
const low = BigInt(value) & BIG_32Fs, high = BigInt(value) >> BIG_32
199182

200183
// write little endian number
201184
this.writeI32(Number(low))
202185
this.writeI32(Number(high))
203186
}
204187

205188
public writeI128(value: bigint | number) {
206-
const low = BigInt(value) & BinaryWriter.BIG_64Fs,
207-
high = BigInt(value) >> BinaryWriter.BIG_64
189+
const low = BigInt(value) & BIG_64Fs, high = BigInt(value) >> BIG_64
208190

209191
// write little endian number
210192
this.writeI64(low)
@@ -227,116 +209,116 @@ export abstract class BinaryWriter implements Writer {
227209
}
228210

229211
public writeF32(value: number) {
230-
const bytes = 4
231-
this.ensureBufferWillHandleSize(bytes)
232-
new DataView(this.buffer, this.offset).setFloat32(0, value, true)
233-
this.offset += bytes
212+
this.alloc(4)
213+
this.view.setFloat32(this.offset, value, true)
214+
this.offset += 4
234215
}
235216

236217
public writeF64(value: number) {
237-
const bytes = 8
238-
this.ensureBufferWillHandleSize(bytes)
239-
new DataView(this.buffer, this.offset).setFloat64(0, value, true)
240-
this.offset += bytes
218+
this.alloc(8)
219+
this.view.setFloat64(this.offset, value, true)
220+
this.offset += 8
241221
}
242222

243223
public writeChar(_value: string) {
244224
throw new Error("Method serializeChar not implemented.")
245225
}
246226

247227
public getBytes() {
248-
return new Uint8Array(this.buffer.slice(0, this.offset))
228+
return new Uint8Array(this.view.buffer).slice(0, this.offset)
249229
}
250230
}
251231

252232
export abstract class BinaryReader implements Reader {
253-
private static readonly BIG_32 = 32n
254-
private static readonly BIG_64 = 64n
255233
private static readonly TEXT_DECODER = new TextDecoder()
256234

257-
public buffer: ArrayBuffer
258235
public offset = 0
236+
public view: DataView
259237

260238
constructor(data: Uint8Array) {
261239
// copies data to prevent outside mutation of buffer.
262-
this.buffer = new ArrayBuffer(data.length)
263-
new Uint8Array(this.buffer).set(data, 0)
264-
}
265-
266-
private read(length: number) {
267-
return this.buffer.slice(this.offset, (this.offset = this.offset + length))
240+
const buffer = new ArrayBuffer(data.length)
241+
new Uint8Array(buffer).set(data, 0)
242+
this.view = new DataView(buffer)
268243
}
269244

270245
abstract readLength(): number
271246
abstract readVariantIndex(): number
272247
abstract checkThatKeySlicesAreIncreasing(key1: [number, number], key2: [number, number]): void
273248

274249
public readString() {
275-
return BinaryReader.TEXT_DECODER.decode(this.readBytes())
276-
}
277-
278-
public readBytes() {
279-
const len = this.readLength()
280-
if (len < 0) {
281-
throw new Error("Length of a bytes array can't be negative")
282-
}
283-
return new Uint8Array(this.read(len))
250+
const length = this.readLength()
251+
const decoded = BinaryReader.TEXT_DECODER.decode(new Uint8Array(this.view.buffer, this.offset, length))
252+
this.offset += length
253+
return decoded
284254
}
285255

286256
public readBool() {
287-
return new Uint8Array(this.read(1))[0] === 1
257+
return this.readU8() === 1
288258
}
289259

290260
public readUnit() {
291261
return null
292262
}
293263

294264
public readU8() {
295-
return new DataView(this.read(1)).getUint8(0)
265+
const value = this.view.getUint8(this.offset)
266+
this.offset += 1
267+
return value
296268
}
297269

298270
public readU16() {
299-
return new DataView(this.read(2)).getUint16(0, true)
271+
const value = this.view.getUint16(this.offset, true)
272+
this.offset += 2
273+
return value
300274
}
301275

302276
public readU32() {
303-
return new DataView(this.read(4)).getUint32(0, true)
277+
const value = this.view.getUint32(this.offset, true)
278+
this.offset += 4
279+
return value
304280
}
305281

306282
public readU64() {
307283
const low = this.readU32(), high = this.readU32()
308284
// combine the two 32-bit values and return (little endian)
309-
return (BigInt(high) << BinaryReader.BIG_32) | BigInt(low)
285+
return (BigInt(high) << BIG_32) | BigInt(low)
310286
}
311287

312288
public readU128() {
313289
const low = this.readU64(), high = this.readU64()
314290
// combine the two 64-bit values and return (little endian)
315-
return (high << BinaryReader.BIG_64) | low
291+
return (high << BIG_64) | low
316292
}
317293

318294
public readI8() {
319-
return new DataView(this.read(1)).getInt8(0)
295+
const value = this.view.getInt8(this.offset)
296+
this.offset += 1
297+
return value
320298
}
321299

322300
public readI16() {
323-
return new DataView(this.read(2)).getInt16(0, true)
301+
const value = this.view.getInt16(this.offset, true)
302+
this.offset += 2
303+
return value
324304
}
325305

326306
public readI32() {
327-
return new DataView(this.read(4)).getInt32(0, true)
307+
const value = this.view.getInt32(this.offset, true)
308+
this.offset += 4
309+
return value
328310
}
329311

330312
public readI64() {
331313
const low = this.readI32(), high = this.readI32()
332314
// combine the two 32-bit values and return (little endian)
333-
return (BigInt(high) << BinaryReader.BIG_32) | BigInt(low)
315+
return (BigInt(high) << BIG_32) | BigInt(low)
334316
}
335317

336318
public readI128() {
337319
const low = this.readI64(), high = this.readI64()
338320
// combine the two 64-bit values and return (little endian)
339-
return (high << BinaryReader.BIG_64) | low
321+
return (high << BIG_64) | low
340322
}
341323

342324
public readOptionTag = this.readBool
@@ -367,10 +349,14 @@ export abstract class BinaryReader implements Reader {
367349
}
368350

369351
public readF32() {
370-
return new DataView(this.read(4)).getFloat32(0, true)
352+
const value = this.view.getFloat32(this.offset, true)
353+
this.offset += 4
354+
return value
371355
}
372356

373357
public readF64() {
374-
return new DataView(this.read(8)).getFloat64(0, true)
358+
const value = this.view.getFloat64(this.offset, true)
359+
this.offset += 8
360+
return value
375361
}
376362
}

suite/typescript/readme.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,21 @@ gen:proto && gen:bincode && run:test && run:benchmarks
1010
## Results
1111
### Encode
1212
```
13-
┌─────────┬───────────────────────────────┬─────────────┬───────────────────┬──────────┬─────────┐
14-
│ (index) │ Task Name │ ops/sec │ Average Time (ns) │ Margin │ Samples │
15-
├─────────┼───────────────────────────────┼─────────────┼───────────────────┼──────────┼─────────┤
16-
│ 0 │ 'JSON:encode' │ '416,024' │ 2403.70173186696 │ '±0.43%' │ 416025
17-
│ 1 │ 'protobuf-js-ts-proto:encode' │ '1,032,930' │ 968.1190244070607 │ '±0.57%' │ 1032931
18-
│ 2 │ 'serdegen-bincode:encode' │ '163,409' │ 6119.603714582189 │ '±0.15%' │ 163410
19-
└─────────┴───────────────────────────────┴─────────────┴───────────────────┴──────────┴─────────┘
13+
┌─────────┬───────────────────────────────┬─────────────┬───────────────────┬──────────┬─────────┐
14+
│ (index) │ Task Name │ ops/sec │ Average Time (ns) │ Margin │ Samples │
15+
├─────────┼───────────────────────────────┼─────────────┼───────────────────┼──────────┼─────────┤
16+
│ 0 │ 'serdegen-bincode:encode' │ '303,315' │ 3296.893675242794 │ '±0.35%' │ 454974
17+
│ 1 │ 'JSON:encode' │ '457,023' │ 2188.0700444621666 │ '±0.12%' │ 685536
18+
│ 2 │ 'protobuf-js-ts-proto:encode' │ '1,022,729' │ 977.7752381694245 │ '±0.43%' │ 1534095
19+
└─────────┴───────────────────────────────┴─────────────┴───────────────────┴──────────┴─────────┘
2020
```
2121
### Decode
2222
```
2323
┌─────────┬───────────────────────────────┬───────────┬────────────────────┬──────────┬─────────┐
2424
│ (index) │ Task Name │ ops/sec │ Average Time (ns) │ Margin │ Samples │
2525
├─────────┼───────────────────────────────┼───────────┼────────────────────┼──────────┼─────────┤
26-
│ 0 │ 'JSON:decode' │ '533,956' │ 1872.8128856825654 │ '±1.30%' │ 533957
27-
│ 1 │ 'protobuf-js-ts-proto:decode' │ '817,807' │ 1222.7815758713145 │ '±0.24%' │ 817808
28-
│ 2 │ 'serdegen-bincode:decode' │ '38,061' │ 26273.294650832962 │ '±0.58%' │ 38062
26+
│ 0 │ 'serdegen-bincode:decode' │ '717,551' │ 1393.628269449653 │ '±0.42%' │ 1076328
27+
│ 1 │ 'JSON:decode' │ '539,225' │ 1854.5121130414666 │ '±0.33%' │ 808839
28+
│ 2 │ 'protobuf-js-ts-proto:decode' │ '795,607' │ 1256.900927759333 │ '±0.19%' │ 1193412
2929
└─────────┴───────────────────────────────┴───────────┴────────────────────┴──────────┴─────────┘
3030
```

0 commit comments

Comments
 (0)