Skip to content

Commit eec47c4

Browse files
authored
Merge pull request #248 from EddiG/alignment-fn
Allow the data alignment to support zero-copy decoding
2 parents 429deca + 1f9e335 commit eec47c4

File tree

5 files changed

+151
-21
lines changed

5 files changed

+151
-21
lines changed

benchmark/msgpack-benchmark.js

+91-15
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,65 @@
33
"use strict";
44
require("ts-node/register");
55
const Benchmark = require("benchmark");
6-
const fs = require("fs");
7-
const msgpack = require("../src");
6+
7+
const msgpackEncode = require("..").encode;
8+
const msgpackDecode = require("..").decode;
9+
const ExtensionCodec = require("..").ExtensionCodec;
10+
11+
const float32ArrayExtensionCodec = new ExtensionCodec();
12+
float32ArrayExtensionCodec.register({
13+
type: 0x01,
14+
encode: (object) => {
15+
if (object instanceof Float32Array) {
16+
return new Uint8Array(object.buffer, object.byteOffset, object.byteLength);
17+
}
18+
return null;
19+
},
20+
decode: (data) => {
21+
const copy = new Uint8Array(data.byteLength);
22+
copy.set(data);
23+
return new Float32Array(copy.buffer);
24+
},
25+
});
26+
27+
const float32ArrayZeroCopyExtensionCodec = new ExtensionCodec();
28+
float32ArrayZeroCopyExtensionCodec.register({
29+
type: 0x01,
30+
encode: (object) => {
31+
if (object instanceof Float32Array) {
32+
return (pos) => {
33+
const bpe = Float32Array.BYTES_PER_ELEMENT;
34+
const padding = 1 + ((bpe - ((pos + 1) % bpe)) % bpe);
35+
const data = new Uint8Array(object.buffer);
36+
const result = new Uint8Array(padding + data.length);
37+
result[0] = padding;
38+
result.set(data, padding);
39+
return result;
40+
};
41+
}
42+
return null;
43+
},
44+
decode: (data) => {
45+
const padding = data[0];
46+
const bpe = Float32Array.BYTES_PER_ELEMENT;
47+
const offset = data.byteOffset + padding;
48+
const length = data.byteLength - padding;
49+
return new Float32Array(data.buffer, offset, length / bpe);
50+
},
51+
});
852

953
const implementations = {
1054
"@msgpack/msgpack": {
11-
encode: require("..").encode,
12-
decode: require("..").decode,
55+
encode: msgpackEncode,
56+
decode: msgpackDecode,
57+
},
58+
"@msgpack/msgpack (Float32Array extension)": {
59+
encode: (data) => msgpackEncode(data, { extensionCodec: float32ArrayExtensionCodec }),
60+
decode: (data) => msgpackDecode(data, { extensionCodec: float32ArrayExtensionCodec }),
61+
},
62+
"@msgpack/msgpack (Float32Array with zero-copy extension)": {
63+
encode: (data) => msgpackEncode(data, { extensionCodec: float32ArrayZeroCopyExtensionCodec }),
64+
decode: (data) => msgpackDecode(data, { extensionCodec: float32ArrayZeroCopyExtensionCodec }),
1365
},
1466
"msgpack-lite": {
1567
encode: require("msgpack-lite").encode,
@@ -21,28 +73,52 @@ const implementations = {
2173
},
2274
};
2375

24-
// exactly the same as:
25-
// https://raw.githubusercontent.com/endel/msgpack-benchmark/master/sample-large.json
26-
const sampleFiles = ["./sample-large.json"];
76+
const samples = [
77+
{
78+
// exactly the same as:
79+
// https://raw.githubusercontent.com/endel/msgpack-benchmark/master/sample-large.json
80+
name: "./sample-large.json",
81+
data: require("./sample-large.json"),
82+
},
83+
{
84+
name: "Large array of numbers",
85+
data: [
86+
{
87+
position: new Array(1e3).fill(1.14),
88+
},
89+
],
90+
},
91+
{
92+
name: "Large Float32Array",
93+
data: [
94+
{
95+
position: new Float32Array(1e3).fill(1.14),
96+
},
97+
],
98+
},
99+
];
27100

28101
function validate(name, data, encoded) {
29-
if (JSON.stringify(data) !== JSON.stringify(implementations[name].decode(encoded))) {
30-
throw new Error("Bad implementation: " + name);
31-
}
102+
return JSON.stringify(data) === JSON.stringify(implementations[name].decode(encoded));
32103
}
33104

34-
for (const sampleFile of sampleFiles) {
35-
const data = require(sampleFile);
105+
for (const sample of samples) {
106+
const { name: sampleName, data } = sample;
36107
const encodeSuite = new Benchmark.Suite();
37108
const decodeSuite = new Benchmark.Suite();
38109

39110
console.log("");
40-
console.log("**" + sampleFile + ":** (" + JSON.stringify(data).length + " bytes in JSON)");
111+
console.log("**" + sampleName + ":** (" + JSON.stringify(data).length + " bytes in JSON)");
41112
console.log("");
42113

43114
for (const name of Object.keys(implementations)) {
44115
implementations[name].toDecode = implementations[name].encode(data);
45-
validate(name, data, implementations[name].toDecode);
116+
if (!validate(name, data, implementations[name].toDecode)) {
117+
console.log("```");
118+
console.log("Not supported by " + name);
119+
console.log("```");
120+
continue;
121+
}
46122
encodeSuite.add("(encode) " + name, () => {
47123
implementations[name].encode(data);
48124
});
@@ -60,7 +136,7 @@ for (const sampleFile of sampleFiles) {
60136

61137
console.log("");
62138

63-
decodeSuite.on("cycle", function(event) {
139+
decodeSuite.on("cycle", (event) => {
64140
console.log(String(event.target));
65141
});
66142

src/Encoder.ts

+15
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,21 @@ export class Encoder<ContextType = undefined> {
396396
}
397397

398398
private encodeExtension(ext: ExtData) {
399+
if (typeof ext.data === "function") {
400+
const data = ext.data(this.pos + 6);
401+
const size = data.length;
402+
403+
if (size >= 0x100000000) {
404+
throw new Error(`Too large extension object: ${size}`);
405+
}
406+
407+
this.writeU8(0xc9);
408+
this.writeU32(size);
409+
this.writeI8(ext.type);
410+
this.writeU8a(data);
411+
return;
412+
}
413+
399414
const size = ext.data.length;
400415
if (size === 1) {
401416
// fixext 1

src/ExtData.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@
33
*/
44
export class ExtData {
55
readonly type: number;
6-
readonly data: Uint8Array;
6+
readonly data: Uint8Array | ((pos: number) => Uint8Array);
77

8-
constructor(
9-
type: number,
10-
data: Uint8Array,
11-
) {
8+
constructor(type: number, data: Uint8Array | ((pos: number) => Uint8Array)) {
129
this.type = type;
1310
this.data = data;
1411
}

src/ExtensionCodec.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ export type ExtensionDecoderType<ContextType> = (
99
context: ContextType,
1010
) => unknown;
1111

12-
export type ExtensionEncoderType<ContextType> = (input: unknown, context: ContextType) => Uint8Array | null;
12+
export type ExtensionEncoderType<ContextType> = (
13+
input: unknown,
14+
context: ContextType,
15+
) => Uint8Array | ((dataPos: number) => Uint8Array) | null;
1316

1417
// immutable interface to ExtensionCodec
1518
export type ExtensionCodecType<ContextType> = {

test/ExtensionCodec.test.ts

+39
Original file line numberDiff line numberDiff line change
@@ -201,4 +201,43 @@ describe("ExtensionCodec", () => {
201201
]);
202202
});
203203
});
204+
205+
context("custom extensions with alignment", () => {
206+
const extensionCodec = new ExtensionCodec();
207+
208+
extensionCodec.register({
209+
type: 0x01,
210+
encode: (object: unknown) => {
211+
if (object instanceof Float32Array) {
212+
return (pos: number) => {
213+
const bpe = Float32Array.BYTES_PER_ELEMENT;
214+
const padding = 1 + ((bpe - ((pos + 1) % bpe)) % bpe);
215+
const data = new Uint8Array(object.buffer);
216+
const result = new Uint8Array(padding + data.length);
217+
result[0] = padding;
218+
result.set(data, padding);
219+
return result;
220+
};
221+
}
222+
return null;
223+
},
224+
decode: (data: Uint8Array) => {
225+
const padding = data[0]!;
226+
const bpe = Float32Array.BYTES_PER_ELEMENT;
227+
const offset = data.byteOffset + padding;
228+
const length = data.byteLength - padding;
229+
return new Float32Array(data.buffer, offset, length / bpe);
230+
},
231+
});
232+
233+
it("encodes and decodes Float32Array type with zero-copy", () => {
234+
const data = {
235+
position: new Float32Array([1.1, 2.2, 3.3, 4.4, 5.5]),
236+
};
237+
const encoded = encode(data, { extensionCodec });
238+
const decoded = decode(encoded, { extensionCodec });
239+
assert.deepStrictEqual(decoded, data);
240+
assert.strictEqual(decoded.position.buffer, encoded.buffer);
241+
});
242+
});
204243
});

0 commit comments

Comments
 (0)