Skip to content

Commit 1dfecda

Browse files
authored
Merge pull request msgpack#121 from msgpack/reuse_instances
make Encoder/Decoder instances easily reusable
2 parents 8a1ecc9 + 8ca191c commit 1dfecda

File tree

10 files changed

+339
-111
lines changed

10 files changed

+339
-111
lines changed

.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ module.exports = {
4545
"@typescript-eslint/no-throw-literal": "warn",
4646
"@typescript-eslint/no-extra-semi": "warn",
4747
"@typescript-eslint/no-extra-non-null-assertion": "warn",
48-
"@typescript-eslint/no-unused-vars": "warn",
48+
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
4949
"@typescript-eslint/no-use-before-define": "warn",
5050
"@typescript-eslint/no-for-in-array": "warn",
5151
"@typescript-eslint/no-unnecessary-condition": ["warn", { "allowConstantLoopConditions": true }],

README.md

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ deepStrictEqual(decode(encoded), object);
4949
- [`decodeAsync(stream: AsyncIterable<ArrayLike<number>> | ReadableStream<ArrayLike<number>>, options?: DecodeAsyncOptions): Promise<unknown>`](#decodeasyncstream-asynciterablearraylikenumber--readablestreamarraylikenumber-options-decodeasyncoptions-promiseunknown)
5050
- [`decodeArrayStream(stream: AsyncIterable<ArrayLike<number>> | ReadableStream<ArrayLike<number>>, options?: DecodeAsyncOptions): AsyncIterable<unknown>`](#decodearraystreamstream-asynciterablearraylikenumber--readablestreamarraylikenumber-options-decodeasyncoptions-asynciterableunknown)
5151
- [`decodeStream(stream: AsyncIterable<ArrayLike<number>> | ReadableStream<ArrayLike<number>>, options?: DecodeAsyncOptions): AsyncIterable<unknown>`](#decodestreamstream-asynciterablearraylikenumber--readablestreamarraylikenumber-options-decodeasyncoptions-asynciterableunknown)
52-
- [Extension Types](#extension-types)
53-
- [Codec context](#codec-context)
52+
- [Reusing Encoder and Decoder instances](#reusing-encoder-and-decoder-instances)
53+
- [Extension Types](#extension-types)
54+
- [ExtensionCodec context](#extensioncodec-context)
5455
- [Handling BigInt with ExtensionCodec](#handling-bigint-with-extensioncodec)
5556
- [The temporal module as timestamp extensions](#the-temporal-module-as-timestamp-extensions)
5657
- [Decoding a Blob](#decoding-a-blob)
@@ -212,7 +213,27 @@ for await (const item of decodeStream(stream)) {
212213
}
213214
```
214215

215-
### Extension Types
216+
### Reusing Encoder and Decoder instances
217+
218+
`Encoder` and `Decoder` classes is provided for better performance:
219+
220+
```typescript
221+
import { deepStrictEqual } from "assert";
222+
import { Encoder, Decoder } from "@msgpack/msgpack";
223+
224+
const encoder = new Encoder();
225+
const decoder = new Decoder();
226+
227+
const encoded: Uint8Array = encoder.encode(object);
228+
deepStrictEqual(decoder.decode(encoded), object);
229+
```
230+
231+
According to our benchmark, reusing `Encoder` instance is about 2% faster
232+
than `encode()` function, and reusing `Decoder` instance is about 35% faster
233+
than `decode()` function. Note that the result should vary in environments
234+
and data structure.
235+
236+
## Extension Types
216237

217238
To handle [MessagePack Extension Types](https://github.com/msgpack/msgpack/blob/master/spec.md#extension-types), this library provides `ExtensionCodec` class.
218239

@@ -266,7 +287,7 @@ const decoded = decode(encoded, { extensionCodec });
266287

267288
Not that extension types for custom objects must be `[0, 127]`, while `[-1, -128]` is reserved for MessagePack itself.
268289

269-
#### Codec context
290+
#### ExtensionCodec context
270291

271292
When using an extension codec, it may be necessary to keep encoding/decoding state, to keep track of which objects got encoded/re-created. To do this, pass a `context` to the `EncodeOptions` and `DecodeOptions` (and if using typescript, type the `ExtensionCodec` too). Don't forget to pass the `{extensionCodec, context}` along recursive encoding/decoding:
272293

benchmark/benchmark-from-msgpack-lite.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ var msgpack_lite = try_require("msgpack-lite");
88
var msgpack_js = try_require("msgpack-js");
99
var msgpack_js_v5 = try_require("msgpack-js-v5");
1010
var msgpack5 = try_require("msgpack5");
11-
var msgpack_unpack = try_require("msgpack-unpack");
1211
var notepack = try_require("notepack");
1312

1413
msgpack5 = msgpack5 && msgpack5();
@@ -64,6 +63,17 @@ if (msgpack_msgpack) {
6463
buf = bench('buf = require("@msgpack/msgpack").encode(obj);', msgpack_msgpack.encode, data);
6564
obj = bench('obj = require("@msgpack/msgpack").decode(buf);', msgpack_msgpack.decode, buf);
6665
runTest(obj);
66+
67+
const encoder = new msgpack_msgpack.Encoder();
68+
const decoder = new msgpack_msgpack.Decoder();
69+
buf = bench('buf = /* @msgpack/msgpack */ encoder.encode(obj);', (data) => encoder.encode(data), data);
70+
obj = bench('obj = /* @msgpack/msgpack */ decoder.decode(buf);', (buf) => decoder.decode(buf), buf);
71+
runTest(obj);
72+
73+
if (process.env.CACHE_HIT_RATE) {
74+
const {hit, miss} = decoder.keyDecoder;
75+
console.log(`CACHE_HIT_RATE: cache hit rate in CachedKeyDecoder: hit=${hit}, hiss=${miss}, hit rate=${hit / (hit + miss)}`);
76+
}
6777
}
6878

6979
if (msgpack_js_v5) {
@@ -90,11 +100,6 @@ if (notepack) {
90100
runTest(obj);
91101
}
92102

93-
if (msgpack_unpack) {
94-
obj = bench('obj = require("msgpack-unpack").decode(buf);', msgpack_unpack, packed);
95-
runTest(obj);
96-
}
97-
98103
function JSON_stringify(src: any) {
99104
return Buffer.from(JSON.stringify(src));
100105
}

src/CachedKeyDecoder.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ interface KeyCacheRecord {
88
const DEFAULT_MAX_KEY_LENGTH = 16;
99
const DEFAULT_MAX_LENGTH_PER_KEY = 16;
1010

11-
export class CachedKeyDecoder {
11+
export interface KeyDecoder {
12+
canBeCached(byteLength: number): boolean;
13+
decode(bytes: Uint8Array, inputOffset: number, byteLength: number): string;
14+
}
15+
16+
export class CachedKeyDecoder implements KeyDecoder {
17+
hit = 0;
18+
miss = 0;
1219
private readonly caches: Array<Array<KeyCacheRecord>>;
1320

1421
constructor(readonly maxKeyLength = DEFAULT_MAX_KEY_LENGTH, readonly maxLengthPerKey = DEFAULT_MAX_LENGTH_PER_KEY) {
@@ -57,8 +64,10 @@ export class CachedKeyDecoder {
5764
public decode(bytes: Uint8Array, inputOffset: number, byteLength: number): string {
5865
const cachedValue = this.get(bytes, inputOffset, byteLength);
5966
if (cachedValue != null) {
67+
this.hit++;
6068
return cachedValue;
6169
}
70+
this.miss++;
6271

6372
const value = utf8DecodeJs(bytes, inputOffset, byteLength);
6473
// Ensure to copy a slice of bytes because the byte may be NodeJS Buffer and Buffer#slice() returns a reference to its internal ArrayBuffer.

0 commit comments

Comments
 (0)