Skip to content

Commit

Permalink
perf: rewrite DataWriter, improve serialization
Browse files Browse the repository at this point in the history
- Rewrote `DataWriter` so it doesn't use builder, but simply reallocates buffer with exponentially growing size. Also it caches high/low bits needed for representing u64/i64 numbers as two u32.
- Refactored `Serializer` to use separate writers for body and header to simplify things a bit.
- `Serializer` instance is created once and reused. I'm not sure this is okay, but it works for now.
  • Loading branch information
norskeld committed Jul 29, 2023
1 parent 74c52c9 commit f839dc9
Show file tree
Hide file tree
Showing 7 changed files with 345 additions and 389 deletions.
2 changes: 1 addition & 1 deletion src/deserializer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ExtensionCodec } from './codec'
import { Extension, Format } from './formats'
import { decodeUtf8 } from './utils/unicode'
import { DataReader } from './rw'
import { DataReader } from './io'

export interface DeserializerOptions {
extensionCodec?: ExtensionCodec
Expand Down
27 changes: 11 additions & 16 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import { BytesBuilder, DataReader, DataWriter } from './rw'
import { DataReader, DataWriter } from './io'
import { Deserializer } from './deserializer'
import { Serializer } from './serializer'

export function serialize(data: unknown): Uint8Array {
const builder = new BytesBuilder()
const writer = new DataWriter(builder)
const serializer = new Serializer(writer)

serializer.encode(data)
const body = serializer.take()

serializer.encodeHeader()
const header = serializer.take()
const SharedBodyWriter = new DataWriter()
const SharedHeaderWriter = new DataWriter()

const serialized = new Uint8Array(body.length + header.length)
const SharedSerializer = new Serializer({
writers: {
body: SharedBodyWriter,
header: SharedHeaderWriter
}
})

serialized.set(header)
serialized.set(body, header.length)

return serialized
export function serialize(data: unknown): Uint8Array {
return SharedSerializer.serialize(data)
}

export function deserialize<T = unknown>(data: Uint8Array): T {
Expand Down
2 changes: 2 additions & 0 deletions src/io/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './reader'
export * from './writer'
83 changes: 83 additions & 0 deletions src/io/reader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
export class DataReader {
private readonly view: DataView
private readonly buffer: Uint8Array
private bufferOffset: number

get offset() {
return this.bufferOffset
}

constructor(data: Uint8Array) {
this.view = new DataView(data.buffer, data.byteOffset)
this.buffer = data
this.bufferOffset = 0
}

u8(): number {
return this.view.getUint8(this.bufferOffset++)
}

i8(): number {
return this.view.getInt8(this.bufferOffset++)
}

u16(): number {
const res = this.view.getUint16(this.bufferOffset)
this.bufferOffset += 2
return res
}

i16(): number {
const res = this.view.getInt16(this.bufferOffset)
this.bufferOffset += 2
return res
}

u32(): number {
const res = this.view.getUint32(this.bufferOffset)
this.bufferOffset += 4
return res
}

i32(): number {
const res = this.view.getInt32(this.bufferOffset)
this.bufferOffset += 4
return res
}

u64(): number {
const hi = this.u32()
const lo = this.u32()

return hi * 4_294_967_296 + lo
}

i64(): number {
const hi = this.i32()
const lo = this.u32()

return hi * 4_294_967_296 + lo
}

f32(): number {
const res = this.view.getFloat32(this.bufferOffset)
this.bufferOffset += 4
return res
}

f64(): number {
const res = this.view.getFloat64(this.bufferOffset)
this.bufferOffset += 8
return res
}

atBufferOffset(): number {
return this.buffer[this.bufferOffset++]
}

range(length: number): Uint8Array {
const res = this.buffer.subarray(this.bufferOffset, this.bufferOffset + length)
this.bufferOffset += length
return res
}
}
152 changes: 152 additions & 0 deletions src/io/writer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
const BUF_INITIAL_SIZE = 2048

export class DataWriter {
private offset: number
private view: DataView
private buffer: Uint8Array

private readonly cache64 = new Map<number, [number, number]>()

constructor() {
this.view = new DataView(new ArrayBuffer(BUF_INITIAL_SIZE))
this.buffer = new Uint8Array(this.view.buffer)
this.offset = 0
}

bytes() {
return this.buffer.slice(0, this.offset)
}

bytesRef() {
return this.buffer.subarray(0, this.offset)
}

resetOffset() {
this.offset = 0
}

batch(values: ArrayLike<number>) {
const size = values.length
this.ensureSize(size)
this.buffer.set(values, this.offset)
this.offset += size
return this
}

u8(i: number) {
this.ensureSize(1)
this.view.setUint8(this.offset, i)
this.offset++
return this
}

i8(i: number) {
this.ensureSize(1)
this.view.setInt8(this.offset, i)
this.offset++
return this
}

u16(i: number) {
this.ensureSize(2)
this.view.setUint16(this.offset, i)
this.offset += 2
return this
}

i16(i: number) {
this.ensureSize(2)
this.view.setInt16(this.offset, i)
this.offset += 2
return this
}

u32(i: number) {
this.ensureSize(4)
this.view.setUint32(this.offset, i)
this.offset += 4
return this
}

i32(i: number) {
this.ensureSize(4)
this.view.setInt32(this.offset, i)
this.offset += 4
return this
}

u64(i: number) {
this.ensureSize(8)

let hi: number
let lo: number

if (this.cache64.has(i)) {
;[hi, lo] = this.cache64.get(i)!
} else {
hi = i / 4_294_967_296
lo = i

this.cache64.set(i, [hi, lo])
}

this.u32(hi)
this.u32(lo)

return this
}

i64(i: number) {
this.ensureSize(8)

let hi: number
let lo: number

if (this.cache64.has(i)) {
;[hi, lo] = this.cache64.get(i)!
} else {
hi = Math.floor(i / 4_294_967_296)
lo = i

this.cache64.set(i, [hi, lo])
}

this.u32(hi)
this.u32(lo)

return this
}

f32(f: number) {
this.ensureSize(4)
this.view.setFloat32(this.offset, f)
this.offset += 4
return this
}

f64(f: number) {
this.ensureSize(8)
this.view.setFloat64(this.offset, f)
this.offset += 8
return this
}

private ensureSize(size: number) {
const needed = this.offset + size

if (this.view.byteLength < needed) {
this.resize(needed * 2)
}
}

private resize(size: number) {
const array = new ArrayBuffer(size)
const view = new DataView(array)
const buffer = new Uint8Array(array)

buffer.set(this.buffer)

this.view = view
this.buffer = buffer
}
}
Loading

0 comments on commit f839dc9

Please sign in to comment.