| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| const assert = require("node:assert/strict"); |
| const fs = require("node:fs"); |
| const os = require("node:os"); |
| const path = require("node:path"); |
| const process = require("node:process"); |
| |
| const REPO_ROOT = path.resolve(__dirname, "..", ".."); |
| const JS_ROOT = path.join(REPO_ROOT, "javascript"); |
| const core = require(path.join(JS_ROOT, "packages", "core", "dist", "index.js")); |
| const protobuf = require(path.join(JS_ROOT, "node_modules", "protobufjs")); |
| |
| const Fory = core.default; |
| const { BoolArray, Type } = core; |
| |
| const DEFAULT_DURATION_SECONDS = 3; |
| const SERIALIZER_ORDER = ["fory", "protobuf", "json"]; |
| const DATA_ORDER = [ |
| "struct", |
| "sample", |
| "mediacontent", |
| "structlist", |
| "samplelist", |
| "mediacontentlist", |
| ]; |
| const LIST_SIZE = 5; |
| const PLAYER_ENUM = { JAVA: 0, FLASH: 1 }; |
| const SIZE_ENUM = { SMALL: 0, LARGE: 1 }; |
| |
| let blackhole = 0; |
| |
| function parseArgs(argv) { |
| const options = { |
| data: "", |
| serializer: "", |
| durationSeconds: DEFAULT_DURATION_SECONDS, |
| output: path.join(__dirname, "benchmark_results.json"), |
| }; |
| for (let i = 0; i < argv.length; i += 1) { |
| const arg = argv[i]; |
| switch (arg) { |
| case "--data": |
| options.data = String(argv[++i] || ""); |
| break; |
| case "--serializer": |
| options.serializer = String(argv[++i] || ""); |
| break; |
| case "--duration": |
| options.durationSeconds = Number(argv[++i] || DEFAULT_DURATION_SECONDS); |
| break; |
| case "--output": |
| case "--benchmark_out": |
| options.output = path.resolve(String(argv[++i] || options.output)); |
| break; |
| case "--help": |
| case "-h": |
| printUsage(); |
| process.exit(0); |
| break; |
| default: |
| throw new Error(`Unknown option: ${arg}`); |
| } |
| } |
| if (!Number.isFinite(options.durationSeconds) || options.durationSeconds <= 0) { |
| throw new Error(`duration must be a positive number, got ${options.durationSeconds}`); |
| } |
| if (options.data && !DATA_ORDER.includes(options.data.toLowerCase())) { |
| throw new Error(`Unknown data type: ${options.data}`); |
| } |
| if (options.serializer && !SERIALIZER_ORDER.includes(options.serializer.toLowerCase())) { |
| throw new Error(`Unknown serializer: ${options.serializer}`); |
| } |
| options.data = options.data.toLowerCase(); |
| options.serializer = options.serializer.toLowerCase(); |
| return options; |
| } |
| |
| function printUsage() { |
| console.log(`Usage: node benchmark.js [OPTIONS] |
| |
| Options: |
| --data <struct|sample|mediacontent|structlist|samplelist|mediacontentlist> |
| Filter benchmark by data type |
| --serializer <fory|protobuf|json> |
| Filter benchmark by serializer |
| --duration <seconds> Minimum time to run each benchmark |
| --output <file> Output JSON file |
| `); |
| } |
| |
| function int32Field(id) { |
| return Type.int32().setId(id); |
| } |
| |
| function int64Field(id) { |
| return Type.int64().setId(id); |
| } |
| |
| function float32Field(id) { |
| return Type.float32().setId(id); |
| } |
| |
| function float64Field(id) { |
| return Type.float64().setId(id); |
| } |
| |
| function boolField(id) { |
| return Type.bool().setId(id); |
| } |
| |
| function stringField(id) { |
| return Type.string().setId(id); |
| } |
| |
| function listField(id, inner) { |
| return Type.list(inner).setId(id); |
| } |
| |
| function boolArrayField(id) { |
| return Type.boolArray().setId(id); |
| } |
| |
| function int32ArrayField(id) { |
| return Type.int32Array().setId(id); |
| } |
| |
| function int64ArrayField(id) { |
| return Type.int64Array().setId(id); |
| } |
| |
| function float32ArrayField(id) { |
| return Type.float32Array().setId(id); |
| } |
| |
| function float64ArrayField(id) { |
| return Type.float64Array().setId(id); |
| } |
| |
| function enumField(id, userTypeId, enumProps) { |
| return Type.enum(userTypeId, enumProps).setId(id); |
| } |
| |
| function structField(id, typeId) { |
| return Type.struct(typeId).setId(id); |
| } |
| |
| function createSchemas() { |
| return { |
| NumericStruct: Type.struct(1, { |
| f1: int32Field(1), |
| f2: int32Field(2), |
| f3: int32Field(3), |
| f4: int32Field(4), |
| f5: int32Field(5), |
| f6: int32Field(6), |
| f7: int32Field(7), |
| f8: int32Field(8), |
| f9: int32Field(9), |
| f10: int32Field(10), |
| f11: int32Field(11), |
| f12: int32Field(12), |
| }), |
| Sample: Type.struct(2, { |
| int_value: int32Field(1), |
| long_value: int64Field(2), |
| float_value: float32Field(3), |
| double_value: float64Field(4), |
| short_value: int32Field(5), |
| char_value: int32Field(6), |
| boolean_value: boolField(7), |
| int_value_boxed: int32Field(8), |
| long_value_boxed: int64Field(9), |
| float_value_boxed: float32Field(10), |
| double_value_boxed: float64Field(11), |
| short_value_boxed: int32Field(12), |
| char_value_boxed: int32Field(13), |
| boolean_value_boxed: boolField(14), |
| int_array: int32ArrayField(15), |
| long_array: int64ArrayField(16), |
| float_array: float32ArrayField(17), |
| double_array: float64ArrayField(18), |
| short_array: int32ArrayField(19), |
| char_array: int32ArrayField(20), |
| boolean_array: boolArrayField(21), |
| string: stringField(22), |
| }), |
| Media: Type.struct(3, { |
| uri: stringField(1), |
| title: stringField(2), |
| width: int32Field(3), |
| height: int32Field(4), |
| format: stringField(5), |
| duration: int64Field(6), |
| size: int64Field(7), |
| bitrate: int32Field(8), |
| has_bitrate: boolField(9), |
| persons: listField(10, Type.string()), |
| player: enumField(11, 101, PLAYER_ENUM), |
| copyright: stringField(12), |
| }), |
| Image: Type.struct(4, { |
| uri: stringField(1), |
| title: stringField(2), |
| width: int32Field(3), |
| height: int32Field(4), |
| size: enumField(5, 102, SIZE_ENUM), |
| }), |
| MediaContent: Type.struct(5, { |
| media: structField(1, 3), |
| images: listField(2, Type.struct(4)), |
| }), |
| NumericStructList: Type.struct(6, { |
| struct_list: listField(1, Type.struct(1)), |
| }), |
| SampleList: Type.struct(7, { |
| sample_list: listField(1, Type.struct(2)), |
| }), |
| MediaContentList: Type.struct(8, { |
| media_content_list: listField(1, Type.struct(5)), |
| }), |
| }; |
| } |
| |
| function createNumericStruct() { |
| return { |
| f1: -12345, |
| f2: 987654321, |
| f3: -31415, |
| f4: 27182818, |
| f5: -32000, |
| f6: 1000000, |
| f7: -999999999, |
| f8: 42, |
| f9: 123456789, |
| f10: -42, |
| f11: 31415926, |
| f12: -27182818, |
| }; |
| } |
| |
| function createSample() { |
| return { |
| int_value: 123, |
| long_value: 1230000, |
| float_value: 12.345, |
| double_value: 1.234567, |
| short_value: 12345, |
| char_value: "!".charCodeAt(0), |
| boolean_value: true, |
| int_value_boxed: 321, |
| long_value_boxed: 3210000, |
| float_value_boxed: 54.321, |
| double_value_boxed: 7.654321, |
| short_value_boxed: 32100, |
| char_value_boxed: "$".charCodeAt(0), |
| boolean_value_boxed: false, |
| int_array: [-1234, -123, -12, -1, 0, 1, 12, 123, 1234], |
| long_array: [-123400, -12300, -1200, -100, 0, 100, 1200, 12300, 123400], |
| float_array: [-12.34, -12.3, -12.0, -1.0, 0.0, 1.0, 12.0, 12.3, 12.34], |
| double_array: [-1.234, -1.23, -12.0, -1.0, 0.0, 1.0, 12.0, 1.23, 1.234], |
| short_array: [-1234, -123, -12, -1, 0, 1, 12, 123, 1234], |
| char_array: Array.from("asdfASDF", (char) => char.charCodeAt(0)), |
| boolean_array: [true, false, false, true], |
| string: "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", |
| }; |
| } |
| |
| function createMediaContent() { |
| return { |
| media: { |
| uri: "http://javaone.com/keynote.ogg", |
| title: "", |
| width: 641, |
| height: 481, |
| format: "video/theora\u1234", |
| duration: 18000001, |
| size: 58982401, |
| bitrate: 0, |
| has_bitrate: false, |
| persons: ["Bill Gates, Jr.", "Steven Jobs"], |
| player: 1, |
| copyright: "Copyright (c) 2009, Scooby Dooby Doo", |
| }, |
| images: [ |
| { |
| uri: "http://javaone.com/keynote_huge.jpg", |
| title: "Javaone Keynote\u1234", |
| width: 32000, |
| height: 24000, |
| size: 1, |
| }, |
| { |
| uri: "http://javaone.com/keynote_large.jpg", |
| title: "", |
| width: 1024, |
| height: 768, |
| size: 1, |
| }, |
| { |
| uri: "http://javaone.com/keynote_small.jpg", |
| title: "", |
| width: 320, |
| height: 240, |
| size: 0, |
| }, |
| ], |
| }; |
| } |
| |
| function repeat(factory) { |
| return Array.from({ length: LIST_SIZE }, () => factory()); |
| } |
| |
| function createNumericStructList() { |
| return { |
| struct_list: repeat(createNumericStruct), |
| }; |
| } |
| |
| function createSampleList() { |
| return { |
| sample_list: repeat(createSample), |
| }; |
| } |
| |
| function createMediaContentList() { |
| return { |
| media_content_list: repeat(createMediaContent), |
| }; |
| } |
| |
| function toProtoStruct(value) { |
| return { ...value }; |
| } |
| |
| function fromProtoStruct(value) { |
| return { |
| f1: value.f1, |
| f2: value.f2, |
| f3: value.f3, |
| f4: value.f4, |
| f5: value.f5, |
| f6: value.f6, |
| f7: value.f7, |
| f8: value.f8, |
| f9: value.f9, |
| f10: value.f10, |
| f11: value.f11, |
| f12: value.f12, |
| }; |
| } |
| |
| function toProtoSample(value) { |
| return { |
| intValue: value.int_value, |
| longValue: value.long_value, |
| floatValue: value.float_value, |
| doubleValue: value.double_value, |
| shortValue: value.short_value, |
| charValue: value.char_value, |
| booleanValue: value.boolean_value, |
| intValueBoxed: value.int_value_boxed, |
| longValueBoxed: value.long_value_boxed, |
| floatValueBoxed: value.float_value_boxed, |
| doubleValueBoxed: value.double_value_boxed, |
| shortValueBoxed: value.short_value_boxed, |
| charValueBoxed: value.char_value_boxed, |
| booleanValueBoxed: value.boolean_value_boxed, |
| intArray: value.int_array, |
| longArray: value.long_array, |
| floatArray: value.float_array, |
| doubleArray: value.double_array, |
| shortArray: value.short_array, |
| charArray: value.char_array, |
| booleanArray: value.boolean_array, |
| string: value.string, |
| }; |
| } |
| |
| function fromProtoSample(value) { |
| return { |
| int_value: value.intValue, |
| long_value: value.longValue, |
| float_value: value.floatValue, |
| double_value: value.doubleValue, |
| short_value: value.shortValue, |
| char_value: value.charValue, |
| boolean_value: value.booleanValue, |
| int_value_boxed: value.intValueBoxed, |
| long_value_boxed: value.longValueBoxed, |
| float_value_boxed: value.floatValueBoxed, |
| double_value_boxed: value.doubleValueBoxed, |
| short_value_boxed: value.shortValueBoxed, |
| char_value_boxed: value.charValueBoxed, |
| boolean_value_boxed: value.booleanValueBoxed, |
| int_array: value.intArray, |
| long_array: value.longArray, |
| float_array: value.floatArray, |
| double_array: value.doubleArray, |
| short_array: value.shortArray, |
| char_array: value.charArray, |
| boolean_array: value.booleanArray, |
| string: value.string, |
| }; |
| } |
| |
| function toProtoImage(value) { |
| return { |
| uri: value.uri, |
| width: value.width, |
| height: value.height, |
| size: value.size, |
| ...(value.title ? { title: value.title } : {}), |
| }; |
| } |
| |
| function fromProtoImage(value) { |
| return { |
| uri: value.uri, |
| title: value.title || "", |
| width: value.width, |
| height: value.height, |
| size: value.size, |
| }; |
| } |
| |
| function toProtoMedia(value) { |
| return { |
| uri: value.uri, |
| width: value.width, |
| height: value.height, |
| format: value.format, |
| duration: value.duration, |
| size: value.size, |
| bitrate: value.bitrate, |
| hasBitrate: value.has_bitrate, |
| persons: value.persons, |
| player: value.player, |
| copyright: value.copyright, |
| ...(value.title ? { title: value.title } : {}), |
| }; |
| } |
| |
| function fromProtoMedia(value) { |
| return { |
| uri: value.uri, |
| title: value.title || "", |
| width: value.width, |
| height: value.height, |
| format: value.format, |
| duration: value.duration, |
| size: value.size, |
| bitrate: value.bitrate, |
| has_bitrate: value.hasBitrate, |
| persons: value.persons || [], |
| player: value.player, |
| copyright: value.copyright, |
| }; |
| } |
| |
| function toProtoMediaContent(value) { |
| return { |
| media: toProtoMedia(value.media), |
| images: value.images.map(toProtoImage), |
| }; |
| } |
| |
| function fromProtoMediaContent(value) { |
| return { |
| media: fromProtoMedia(value.media), |
| images: (value.images || []).map(fromProtoImage), |
| }; |
| } |
| |
| function toProtoNumericStructList(value) { |
| return { |
| structList: value.struct_list.map(toProtoStruct), |
| }; |
| } |
| |
| function fromProtoNumericStructList(value) { |
| return { |
| struct_list: (value.structList || []).map(fromProtoStruct), |
| }; |
| } |
| |
| function toProtoSampleList(value) { |
| return { |
| sampleList: value.sample_list.map(toProtoSample), |
| }; |
| } |
| |
| function fromProtoSampleList(value) { |
| return { |
| sample_list: (value.sampleList || []).map(fromProtoSample), |
| }; |
| } |
| |
| function toProtoMediaContentList(value) { |
| return { |
| mediaContentList: value.media_content_list.map(toProtoMediaContent), |
| }; |
| } |
| |
| function fromProtoMediaContentList(value) { |
| return { |
| media_content_list: (value.mediaContentList || []).map(fromProtoMediaContent), |
| }; |
| } |
| |
| function createForyBenchmarks() { |
| const fory = new Fory({ |
| compatible: true, |
| ref: false, |
| }); |
| const schemas = createSchemas(); |
| const serializers = { |
| struct: fory.register(schemas.NumericStruct), |
| sample: fory.register(schemas.Sample), |
| media: fory.register(schemas.Media), |
| image: fory.register(schemas.Image), |
| mediacontent: fory.register(schemas.MediaContent), |
| structlist: fory.register(schemas.NumericStructList), |
| samplelist: fory.register(schemas.SampleList), |
| mediacontentlist: fory.register(schemas.MediaContentList), |
| }; |
| return { fory, serializers }; |
| } |
| |
| function createDatasets(root) { |
| const StructType = root.lookupType("protobuf.NumericStruct"); |
| const SampleType = root.lookupType("protobuf.Sample"); |
| const MediaContentType = root.lookupType("protobuf.MediaContent"); |
| const StructListType = root.lookupType("protobuf.NumericStructList"); |
| const SampleListType = root.lookupType("protobuf.SampleList"); |
| const MediaContentListType = root.lookupType("protobuf.MediaContentList"); |
| |
| const { serializers } = createForyBenchmarks(); |
| |
| return [ |
| { |
| key: "struct", |
| label: "NumericStruct", |
| createValue: createNumericStruct, |
| toProto: toProtoStruct, |
| fromProto: fromProtoStruct, |
| protoType: StructType, |
| forySerializer: serializers.struct, |
| sizeKey: "struct", |
| }, |
| { |
| key: "sample", |
| label: "Sample", |
| createValue: createSample, |
| toProto: toProtoSample, |
| fromProto: fromProtoSample, |
| protoType: SampleType, |
| forySerializer: serializers.sample, |
| sizeKey: "sample", |
| }, |
| { |
| key: "mediacontent", |
| label: "MediaContent", |
| createValue: createMediaContent, |
| toProto: toProtoMediaContent, |
| fromProto: fromProtoMediaContent, |
| protoType: MediaContentType, |
| forySerializer: serializers.mediacontent, |
| sizeKey: "media", |
| }, |
| { |
| key: "structlist", |
| label: "NumericStructList", |
| createValue: createNumericStructList, |
| toProto: toProtoNumericStructList, |
| fromProto: fromProtoNumericStructList, |
| protoType: StructListType, |
| forySerializer: serializers.structlist, |
| sizeKey: "struct_list", |
| }, |
| { |
| key: "samplelist", |
| label: "SampleList", |
| createValue: createSampleList, |
| toProto: toProtoSampleList, |
| fromProto: fromProtoSampleList, |
| protoType: SampleListType, |
| forySerializer: serializers.samplelist, |
| sizeKey: "sample_list", |
| }, |
| { |
| key: "mediacontentlist", |
| label: "MediaContentList", |
| createValue: createMediaContentList, |
| toProto: toProtoMediaContentList, |
| fromProto: fromProtoMediaContentList, |
| protoType: MediaContentListType, |
| forySerializer: serializers.mediacontentlist, |
| sizeKey: "media_list", |
| }, |
| ]; |
| } |
| |
| function decodeProtoObject(protoType, bytes) { |
| const message = protoType.decode(bytes); |
| return protoType.toObject(message, { |
| longs: Number, |
| enums: Number, |
| defaults: true, |
| }); |
| } |
| |
| function toFloat32(value) { |
| return new Float32Array([value])[0]; |
| } |
| |
| function normalizeForyValue(datasetKey, value) { |
| switch (datasetKey) { |
| case "sample": |
| return { |
| ...value, |
| long_value: BigInt(value.long_value), |
| long_value_boxed: BigInt(value.long_value_boxed), |
| float_value: toFloat32(value.float_value), |
| float_value_boxed: toFloat32(value.float_value_boxed), |
| int_array: Int32Array.from(value.int_array), |
| long_array: BigInt64Array.from(value.long_array, (item) => BigInt(item)), |
| float_array: Float32Array.from(value.float_array, toFloat32), |
| double_array: Float64Array.from(value.double_array), |
| short_array: Int32Array.from(value.short_array), |
| char_array: Int32Array.from(value.char_array), |
| }; |
| case "mediacontent": |
| return { |
| media: { |
| ...value.media, |
| duration: BigInt(value.media.duration), |
| size: BigInt(value.media.size), |
| }, |
| images: value.images.map((image) => ({ ...image })), |
| }; |
| case "structlist": |
| return { |
| struct_list: value.struct_list.map((item) => ({ ...item })), |
| }; |
| case "samplelist": |
| return { |
| sample_list: value.sample_list.map((item) => normalizeForyValue("sample", item)), |
| }; |
| case "mediacontentlist": |
| return { |
| media_content_list: value.media_content_list.map((item) => |
| normalizeForyValue("mediacontent", item) |
| ), |
| }; |
| default: |
| return value; |
| } |
| } |
| |
| function normalizeForyRoundTripValue(datasetKey, value) { |
| switch (datasetKey) { |
| case "sample": |
| return { |
| ...value, |
| boolean_array: value.boolean_array instanceof BoolArray |
| ? Array.from(value.boolean_array) |
| : value.boolean_array, |
| }; |
| case "samplelist": |
| return { |
| sample_list: value.sample_list.map((item) => |
| normalizeForyRoundTripValue("sample", item) |
| ), |
| }; |
| default: |
| return value; |
| } |
| } |
| |
| function normalizeProtobufValue(datasetKey, value) { |
| switch (datasetKey) { |
| case "sample": |
| return { |
| ...value, |
| float_value: toFloat32(value.float_value), |
| float_value_boxed: toFloat32(value.float_value_boxed), |
| float_array: value.float_array.map(toFloat32), |
| }; |
| case "samplelist": |
| return { |
| sample_list: value.sample_list.map((item) => normalizeProtobufValue("sample", item)), |
| }; |
| default: |
| return value; |
| } |
| } |
| |
| function ensureSerializationWorks(dataset) { |
| const value = dataset.createValue(); |
| const foryValue = normalizeForyValue(dataset.key, value); |
| const foryBytes = dataset.forySerializer.serialize(foryValue); |
| const foryRoundTrip = dataset.forySerializer.deserialize(foryBytes); |
| assert.deepStrictEqual( |
| normalizeForyRoundTripValue(dataset.key, foryRoundTrip), |
| foryValue |
| ); |
| |
| const protoPayload = dataset.toProto(value); |
| const protoBytes = dataset.protoType.encode(dataset.protoType.create(protoPayload)).finish(); |
| const protoRoundTrip = dataset.fromProto(decodeProtoObject(dataset.protoType, protoBytes)); |
| assert.deepStrictEqual(protoRoundTrip, normalizeProtobufValue(dataset.key, value)); |
| |
| const jsonBytes = Buffer.from(JSON.stringify(value), "utf8"); |
| const jsonRoundTrip = JSON.parse(jsonBytes.toString("utf8")); |
| assert.deepStrictEqual(jsonRoundTrip, value); |
| } |
| |
| function serializeBytes(serializerName, dataset, value) { |
| switch (serializerName) { |
| case "fory": |
| return dataset.forySerializer.serialize(normalizeForyValue(dataset.key, value)); |
| case "protobuf": |
| return dataset.protoType.encode(dataset.toProto(value)).finish(); |
| case "json": |
| return Buffer.from(JSON.stringify(value), "utf8"); |
| default: |
| throw new Error(`Unknown serializer ${serializerName}`); |
| } |
| } |
| |
| function createBenchmarkCase(serializerName, dataset, operation) { |
| const value = dataset.createValue(); |
| |
| if (serializerName === "fory") { |
| const foryValue = normalizeForyValue(dataset.key, value); |
| if (operation === "Serialize") { |
| return () => { |
| const bytes = dataset.forySerializer.serialize(foryValue); |
| blackhole ^= bytes.length; |
| }; |
| } |
| const bytes = dataset.forySerializer.serialize(foryValue); |
| return () => { |
| const decoded = dataset.forySerializer.deserialize(bytes); |
| blackhole ^= Array.isArray(decoded) ? decoded.length : 1; |
| }; |
| } |
| |
| if (serializerName === "protobuf") { |
| const protoValue = dataset.toProto(value); |
| if (operation === "Serialize") { |
| return () => { |
| const bytes = dataset.protoType.encode(protoValue).finish(); |
| blackhole ^= bytes.length; |
| }; |
| } |
| const bytes = dataset.protoType.encode(protoValue).finish(); |
| return () => { |
| const decoded = dataset.protoType.decode(bytes); |
| blackhole ^= decoded ? 1 : 0; |
| }; |
| } |
| |
| if (serializerName === "json") { |
| if (operation === "Serialize") { |
| return () => { |
| const json = JSON.stringify(value); |
| blackhole ^= json.length; |
| }; |
| } |
| const json = JSON.stringify(value); |
| return () => { |
| const decoded = JSON.parse(json); |
| blackhole ^= Array.isArray(decoded) ? decoded.length : 1; |
| }; |
| } |
| |
| throw new Error(`Unknown serializer ${serializerName}`); |
| } |
| |
| function measureBatch(fn, batchSize) { |
| const start = process.hrtime.bigint(); |
| for (let i = 0; i < batchSize; i += 1) { |
| fn(); |
| } |
| return process.hrtime.bigint() - start; |
| } |
| |
| function benchmark(fn, minDurationSeconds) { |
| fn(); |
| let batchSize = 1; |
| while (batchSize < 1_000_000) { |
| const elapsed = measureBatch(fn, batchSize); |
| if (elapsed >= 10_000_000n) { |
| break; |
| } |
| batchSize *= 2; |
| } |
| |
| const targetNs = BigInt(Math.floor(minDurationSeconds * 1e9)); |
| let totalElapsed = 0n; |
| let totalIterations = 0; |
| |
| while (totalElapsed < targetNs) { |
| const elapsed = measureBatch(fn, batchSize); |
| totalElapsed += elapsed; |
| totalIterations += batchSize; |
| } |
| |
| return Number(totalElapsed) / totalIterations; |
| } |
| |
| function buildResults(datasets, options) { |
| const benchmarks = []; |
| |
| for (const dataset of datasets) { |
| if (options.data && options.data !== dataset.key) { |
| continue; |
| } |
| for (const serializerName of SERIALIZER_ORDER) { |
| if (options.serializer && options.serializer !== serializerName) { |
| continue; |
| } |
| for (const operation of ["Serialize", "Deserialize"]) { |
| const benchName = `BM_${serializerName[0].toUpperCase()}${serializerName.slice(1)}_${dataset.label}_${operation}`; |
| const fn = createBenchmarkCase(serializerName, dataset, operation); |
| const realTimeNs = benchmark(fn, options.durationSeconds); |
| benchmarks.push({ |
| name: benchName, |
| real_time: realTimeNs, |
| cpu_time: realTimeNs, |
| time_unit: "ns", |
| }); |
| console.log(`${benchName}: ${realTimeNs.toFixed(1)} ns/op`); |
| } |
| } |
| } |
| |
| const sizeCounters = { |
| name: "BM_PrintSerializedSizes", |
| }; |
| for (const dataset of datasets) { |
| const value = dataset.createValue(); |
| for (const serializerName of SERIALIZER_ORDER) { |
| const bytes = serializeBytes(serializerName, dataset, value); |
| sizeCounters[`${serializerName}_${dataset.sizeKey}_size`] = bytes.length; |
| } |
| } |
| benchmarks.push(sizeCounters); |
| return benchmarks; |
| } |
| |
| function main() { |
| const options = parseArgs(process.argv.slice(2)); |
| const root = protobuf.loadSync(path.join(REPO_ROOT, "benchmarks", "proto", "bench.proto")); |
| const datasets = createDatasets(root); |
| datasets.forEach(ensureSerializationWorks); |
| |
| const structSize = serializeBytes("fory", datasets.find((item) => item.key === "struct"), createNumericStruct()).length; |
| console.log(`Fory NumericStruct serialized size: ${structSize} bytes`); |
| |
| const result = { |
| context: { |
| date: new Date().toISOString(), |
| host_name: os.hostname(), |
| executable: process.execPath, |
| num_cpus: os.cpus().length, |
| node_version: process.version, |
| v8_version: process.versions.v8, |
| duration_seconds: options.durationSeconds, |
| }, |
| benchmarks: buildResults(datasets, options), |
| }; |
| |
| fs.writeFileSync(options.output, JSON.stringify(result, null, 2)); |
| console.log(`Saved benchmark results to ${options.output}`); |
| if (blackhole === Number.MIN_SAFE_INTEGER) { |
| console.log("unreachable blackhole guard"); |
| } |
| } |
| |
| main(); |