Skip to content

Commit

Permalink
[JavaScript] Improve tag write performance (#1241)
Browse files Browse the repository at this point in the history
  • Loading branch information
bytemain authored Dec 27, 2023
1 parent 224cd03 commit dd2f24a
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 108 deletions.
52 changes: 32 additions & 20 deletions javascript/packages/fury/lib/classResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ import mapSerializer from "./internalSerializer/map";
import setSerializer from "./internalSerializer/set";
import boolSerializer from "./internalSerializer/bool";
import { uInt16Serializer, int16Serializer, int32Serializer, uInt32Serializer, uInt64Serializer, floatSerializer, doubleSerializer, uInt8Serializer, int64Serializer, int8Serializer } from "./internalSerializer/number";
import { InternalSerializerType, Serializer, Fury, BinaryReader, BinaryWriter } from "./type";
import { InternalSerializerType, Serializer, Fury, BinaryReader, BinaryWriter as TBinaryWriter } from "./type";
import anySerializer from "./internalSerializer/any";
import { PlatformBuffer, fromUint8Array } from "./platformBuffer";
import { fromString } from "./platformBuffer";
import { x64hash128 } from "./murmurHash3";
import { BinaryWriter as BufferWriter } from "./writer";
import { BinaryWriter } from "./writer";

const USESTRINGVALUE = 0;
const USESTRINGID = 1;

Expand All @@ -39,7 +40,8 @@ export default class SerializerResolver {
};

private readStringPool: string[] = [];
private writeStringIndex: string[] = [];
private writeStringCount = 0;
private writeStringIndex: number[] = [];

private initInternalSerializer(fury: Fury) {
const _anySerializer = anySerializer(fury);
Expand Down Expand Up @@ -77,7 +79,7 @@ export default class SerializerResolver {

reset() {
this.readStringPool = [];
this.writeStringIndex = [];
this.writeStringIndex.fill(-1);
}

getSerializerById(id: InternalSerializerType) {
Expand All @@ -97,30 +99,40 @@ export default class SerializerResolver {
return this.customSerializer[tag];
}

tagToBuffer(tag: string) {
const tagBuffer = fromUint8Array(new TextEncoder().encode(tag));
createTagWriter(tag: string) {
this.writeStringIndex.push(-1);
const idx = this.writeStringIndex.length - 1;
const tagBuffer = fromString(tag);
const bufferLen = tagBuffer.byteLength;

const writer = BinaryWriter({});

let tagHash = x64hash128(tagBuffer, 47).getBigUint64(0);
if (tagHash === BigInt(0)) {
tagHash = BigInt(1);
}
const bufferLen = tagBuffer.byteLength;
const writer = BufferWriter({});

writer.uint8(USESTRINGVALUE);
writer.uint64(tagHash);
writer.int16(bufferLen);
writer.bufferWithoutMemCheck(tagBuffer, bufferLen);
return writer.dump();
}

writeTag(binaryWriter: BinaryWriter, tag: string, bf: PlatformBuffer, byteLength: number) {
const index = this.writeStringIndex.indexOf(tag);
if (index > -1) {
binaryWriter.uint8(USESTRINGID);
binaryWriter.int16(index);
return;
}
this.writeStringIndex.push(tag);
binaryWriter.bufferWithoutMemCheck(bf, byteLength);
const fullBuffer = writer.dump();

return {
write: (binaryWriter: TBinaryWriter) => {
const tagIndex = this.writeStringIndex[idx];
if (tagIndex > -1) {
// equivalent of: `uint8(USESTRINGID); int16(tagIndex)`
binaryWriter.int24((tagIndex << 8) | USESTRINGID);
return;
}

this.writeStringIndex[idx] = this.writeStringCount++;
binaryWriter.buffer(fullBuffer);
},
bufferLen,
};
}

detectTag(binaryReader: BinaryReader) {
Expand Down
60 changes: 30 additions & 30 deletions javascript/packages/fury/lib/codeGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import setSerializer from "./internalSerializer/set";
import { arraySerializer } from "./internalSerializer/array";
import { tupleSerializer } from "./internalSerializer/tuple";
import { ArrayTypeDescription, Cast, MapTypeDescription, ObjectTypeDescription, SetTypeDescription, TupleTypeDescription, TypeDescription } from "./description";
import { fromString } from "./platformBuffer";

function computeFieldHash(hash: number, id: number): number {
let newHash = (hash) * 31 + (id);
Expand All @@ -34,7 +35,7 @@ function computeFieldHash(hash: number, id: number): number {
}

const computeStringHash = (str: string) => {
const bytes = new TextEncoder().encode(str);
const bytes = fromString(str);
let hash = 17;
bytes.forEach((b) => {
hash = hash * 31 + b;
Expand Down Expand Up @@ -169,36 +170,35 @@ export const generateInlineCode = (fury: Fury, description: TypeDescription) =>
return new Function(
`
return function (fury, scope) {
const { referenceResolver, binaryWriter, classResolver, binaryReader } = fury;
const { writeNullOrRef, pushReadObject } = referenceResolver;
const { RefFlags, InternalSerializerType, arraySerializer, tupleSerializer, mapSerializer, setSerializer } = scope;
${declarations.join("")}
const tagBuffer = classResolver.tagToBuffer("${validTag}");
const bufferLen = tagBuffer.byteLength;
const reserves = ${names.map(x => `${x}.config().reserve`).join(" + ")};
return {
...referenceResolver.deref(() => {
const hash = binaryReader.int32();
if (hash !== ${expectHash}) {
throw new Error("validate hash failed: ${validTag}. expect ${expectHash}, but got" + hash);
}
{
${read}
}
}),
write: referenceResolver.withNullableOrRefWriter(InternalSerializerType.FURY_TYPE_TAG, (v) => {
classResolver.writeTag(binaryWriter, "${validTag}", tagBuffer, bufferLen);
binaryWriter.int32(${expectHash});
binaryWriter.reserve(reserves);
${write}
}),
config() {
return {
reserve: bufferLen + 8,
}
}
const { referenceResolver, binaryWriter, classResolver, binaryReader } = fury;
const { writeNullOrRef, pushReadObject } = referenceResolver;
const { RefFlags, InternalSerializerType, arraySerializer, tupleSerializer, mapSerializer, setSerializer } = scope;
${declarations.join("")}
const tagWriter = classResolver.createTagWriter("${validTag}");
const reserves = ${names.map(x => `${x}.config().reserve`).join(" + ")};
return {
...referenceResolver.deref(() => {
const hash = binaryReader.int32();
if (hash !== ${expectHash}) {
throw new Error("validate hash failed: ${validTag}. expect ${expectHash}, but got" + hash);
}
{
${read}
}
}),
write: referenceResolver.withNullableOrRefWriter(InternalSerializerType.FURY_TYPE_TAG, (v) => {
tagWriter.write(binaryWriter);
binaryWriter.int32(${expectHash});
binaryWriter.reserve(reserves);
${write}
}),
config() {
return {
reserve: tagWriter.bufferLen + 8,
}
}
}
}
`
);
Expand Down
12 changes: 12 additions & 0 deletions javascript/packages/fury/lib/platformBuffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,15 @@ export const fromUint8Array = isNodeEnv
export const alloc = (isNodeEnv ? Buffer.allocUnsafe : BrowserBuffer.alloc) as unknown as (size: number) => PlatformBuffer;

export const strByteLength = isNodeEnv ? Buffer.byteLength : BrowserBuffer.byteLength;

let utf8Encoder: TextEncoder | null;

export const fromString
= isNodeEnv
? (str: string) => Buffer.from(str) as unknown as PlatformBuffer
: (str: string) => {
if (!utf8Encoder) {
utf8Encoder = new TextEncoder();
}
return new BrowserBuffer(utf8Encoder.encode(str));
};
2 changes: 0 additions & 2 deletions javascript/packages/fury/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
* under the License.
*/

export const utf8Encoder = new TextEncoder();

const isReserved = (key: string) => {
return /^(?:do|if|in|for|let|new|try|var|case|else|enum|eval|false|null|this|true|void|with|break|catch|class|const|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$/.test(key);
};
Expand Down
110 changes: 54 additions & 56 deletions javascript/test/__snapshots__/codeGen.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,27 @@ exports[`codeGen can generate tuple declaration code 1`] = `
) {
return function (fury, scope) {
const { referenceResolver, binaryWriter, classResolver, binaryReader } = fury;
const { writeNullOrRef, pushReadObject } = referenceResolver;
const { RefFlags, InternalSerializerType, arraySerializer, tupleSerializer, mapSerializer, setSerializer } = scope;
const { referenceResolver, binaryWriter, classResolver, binaryReader } = fury;
const { writeNullOrRef, pushReadObject } = referenceResolver;
const { RefFlags, InternalSerializerType, arraySerializer, tupleSerializer, mapSerializer, setSerializer } = scope;
const tag_0 = classResolver.getSerializerByTag("example.foo.1")
const tag_1 = classResolver.getSerializerByTag("example.foo.2")
const tuple_tag_0_tag_1 = tupleSerializer(fury, [tag_0, tag_1])
const tag_5 = classResolver.getSerializerByTag("example.bar.1")
const tag_6 = classResolver.getSerializerByTag("example.bar.2")
const tuple_tag_0_tag_5_tag_6 = tupleSerializer(fury, [tag_0, tag_5, tag_6])
const tagBuffer = classResolver.tagToBuffer("tuple-object-wrapper");
const bufferLen = tagBuffer.byteLength;
const tagWriter = classResolver.createTagWriter("tuple-object-wrapper");
const reserves = tag_0.config().reserve + tag_1.config().reserve + tuple_tag_0_tag_1.config().reserve + tag_5.config().reserve + tag_6.config().reserve + tuple_tag_0_tag_5_tag_6.config().reserve;
return {
...referenceResolver.deref(() => {
const hash = binaryReader.int32();
if (hash !== 16469457) {
throw new Error("validate hash failed: tuple-object-wrapper. expect 16469457, but got" + hash);
}
{
const reserves = tag_0.config().reserve + tag_1.config().reserve + tuple_tag_0_tag_1.config().reserve + tag_5.config().reserve + tag_6.config().reserve + tuple_tag_0_tag_5_tag_6.config().reserve;
return {
...referenceResolver.deref(() => {
const hash = binaryReader.int32();
if (hash !== 16469457) {
throw new Error("validate hash failed: tuple-object-wrapper. expect 16469457, but got" + hash);
}
{
// relation tag: tuple-object-wrapper
const result = {
tuple1: null,
Expand All @@ -41,23 +40,23 @@ result.tuple2 = tuple_tag_0_tag_5_tag_6.read();
result.tuple2_ = tuple_tag_0_tag_5_tag_6.read()
return result;
}
}),
write: referenceResolver.withNullableOrRefWriter(InternalSerializerType.FURY_TYPE_TAG, (v) => {
classResolver.writeTag(binaryWriter, "tuple-object-wrapper", tagBuffer, bufferLen);
binaryWriter.int32(16469457);
binaryWriter.reserve(reserves);
tuple_tag_0_tag_1.write(v.tuple1);
}
}),
write: referenceResolver.withNullableOrRefWriter(InternalSerializerType.FURY_TYPE_TAG, (v) => {
tagWriter.write(binaryWriter);
binaryWriter.int32(16469457);
binaryWriter.reserve(reserves);
tuple_tag_0_tag_1.write(v.tuple1);
tuple_tag_0_tag_1.write(v.tuple1_);
tuple_tag_0_tag_5_tag_6.write(v.tuple2);
tuple_tag_0_tag_5_tag_6.write(v.tuple2_)
}),
config() {
return {
reserve: bufferLen + 8,
}
}
}),
config() {
return {
reserve: tagWriter.bufferLen + 8,
}
}
}
}
}"
Expand All @@ -68,28 +67,27 @@ exports[`codeGen can generate tuple declaration code 2`] = `
) {
return function (fury, scope) {
const { referenceResolver, binaryWriter, classResolver, binaryReader } = fury;
const { writeNullOrRef, pushReadObject } = referenceResolver;
const { RefFlags, InternalSerializerType, arraySerializer, tupleSerializer, mapSerializer, setSerializer } = scope;
const { referenceResolver, binaryWriter, classResolver, binaryReader } = fury;
const { writeNullOrRef, pushReadObject } = referenceResolver;
const { RefFlags, InternalSerializerType, arraySerializer, tupleSerializer, mapSerializer, setSerializer } = scope;
const type_13 = classResolver.getSerializerById(13)
const type_1 = classResolver.getSerializerById(1)
const type_6 = classResolver.getSerializerById(6)
const type_14 = classResolver.getSerializerById(14)
const tuple_type_14 = tupleSerializer(fury, [type_14])
const tuple_type_13_type_1_type_6_tuple_type_14 = tupleSerializer(fury, [type_13, type_1, type_6, tuple_type_14])
const tagBuffer = classResolver.tagToBuffer("tuple-object-type3-tag");
const bufferLen = tagBuffer.byteLength;
const tagWriter = classResolver.createTagWriter("tuple-object-type3-tag");
const reserves = type_13.config().reserve + type_1.config().reserve + type_6.config().reserve + type_14.config().reserve + tuple_type_14.config().reserve + tuple_type_13_type_1_type_6_tuple_type_14.config().reserve;
return {
...referenceResolver.deref(() => {
const hash = binaryReader.int32();
if (hash !== 552) {
throw new Error("validate hash failed: tuple-object-type3-tag. expect 552, but got" + hash);
}
{
const reserves = type_13.config().reserve + type_1.config().reserve + type_6.config().reserve + type_14.config().reserve + tuple_type_14.config().reserve + tuple_type_13_type_1_type_6_tuple_type_14.config().reserve;
return {
...referenceResolver.deref(() => {
const hash = binaryReader.int32();
if (hash !== 552) {
throw new Error("validate hash failed: tuple-object-type3-tag. expect 552, but got" + hash);
}
{
// relation tag: tuple-object-type3-tag
const result = {
tuple: null
Expand All @@ -98,20 +96,20 @@ return function (fury, scope) {
result.tuple = tuple_type_13_type_1_type_6_tuple_type_14.read()
return result;
}
}),
write: referenceResolver.withNullableOrRefWriter(InternalSerializerType.FURY_TYPE_TAG, (v) => {
classResolver.writeTag(binaryWriter, "tuple-object-type3-tag", tagBuffer, bufferLen);
binaryWriter.int32(552);
binaryWriter.reserve(reserves);
tuple_type_13_type_1_type_6_tuple_type_14.write(v.tuple)
}),
config() {
return {
reserve: bufferLen + 8,
}
}
}
}),
write: referenceResolver.withNullableOrRefWriter(InternalSerializerType.FURY_TYPE_TAG, (v) => {
tagWriter.write(binaryWriter);
binaryWriter.int32(552);
binaryWriter.reserve(reserves);
tuple_type_13_type_1_type_6_tuple_type_14.write(v.tuple)
}),
config() {
return {
reserve: tagWriter.bufferLen + 8,
}
}
}
}
}"
Expand Down

0 comments on commit dd2f24a

Please sign in to comment.