Skip to content

Commit

Permalink
feat(JavaScript): Make PlatformBuffer available if has Buffer polyfill (
Browse files Browse the repository at this point in the history
#1373)

If user has a polyfill(process/buffer) in browser, furyjs cannot work.
  • Loading branch information
bytemain authored Feb 19, 2024
1 parent a6986ec commit c2238b7
Show file tree
Hide file tree
Showing 11 changed files with 297 additions and 26 deletions.
102 changes: 102 additions & 0 deletions javascript/benchmark/platform-buffer-draw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# 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.

import matplotlib.pyplot as plt
import sys

[
_,
browser_utf8_write,
browser_write,
browser_write_1,
native_write,
browser_to_string,
native_to_string,
] = sys.argv[0:7]

# 创建图形和子图
fig, axs = plt.subplots(nrows=1, ncols=3, figsize=(15, 5), sharey=True)

# 绘制第一部分比较:browser utf8Write 和 browser write
axs[0].bar(
["browser utf8Write", "browser write"],
[browser_utf8_write, browser_write],
color=["b", "g"],
)
axs[0].set_title("Browser UTF8 Write vs Browser Write")
axs[0].set_xlabel("Operation Type")
axs[0].set_xticklabels(["browser utf8Write", "browser write"])
axs[0].set_ylabel("Tps")


# 在柱形图上添加数值标签
for p in axs[0].patches:
axs[0].annotate(
format(p.get_height(), ".0f"),
(p.get_x() + p.get_width() / 2.0, p.get_height()),
ha="center",
va="center",
xytext=(0, 9),
textcoords="offset points",
)

# 绘制第二部分比较:browser write 和 native write
axs[1].bar(
["browser write", "native write"], [browser_write_1, native_write], color=["g", "r"]
)
axs[1].set_title("Browser Write vs Native Write")
axs[1].set_xlabel("Operation Type")
axs[1].set_xticklabels(["browser write", "native write"])

# 在柱形图上添加数值标签
for p in axs[1].patches:
axs[1].annotate(
format(p.get_height(), ".0f"),
(p.get_x() + p.get_width() / 2.0, p.get_height()),
ha="center",
va="center",
xytext=(0, 9),
textcoords="offset points",
)

# 绘制第三部分比较:browser toString 和 native toString
axs[2].bar(
["browser toString", "native toString"],
[browser_to_string, native_to_string],
color=["b", "r"],
)
axs[2].set_title("Browser ToString vs Native ToString")
axs[2].set_xlabel("Operation Type")
axs[2].set_xticklabels(["browser toString", "native toString"])

# 在柱形图上添加数值标签
for p in axs[2].patches:
axs[2].annotate(
format(p.get_height(), ".0f"),
(p.get_x() + p.get_width() / 2.0, p.get_height()),
ha="center",
va="center",
xytext=(0, 9),
textcoords="offset points",
)

# 调整布局以避免重叠
plt.tight_layout()

# 显示图形
plt.show()
fig.savefig("./platform-buffer.jpg")
Binary file added javascript/benchmark/platform-buffer.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
113 changes: 113 additions & 0 deletions javascript/benchmark/platform-buffer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* 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 { BrowserBuffer } = require('@furyjs/fury/dist/lib/platformBuffer')
const Benchmark = require("benchmark");
const { spawn } = require("child_process");

const jsonString = JSON.stringify({
id: 12346,
name: "John Doe",
email: "[email protected]",
});

async function start() {
const result = {
browserCompare: {},
browserVsNativeWrite: {},
browserVsNativeToString: {},
}

{
const platformBufferA = new BrowserBuffer(jsonString.length);
const platformBufferB = new BrowserBuffer(jsonString.length);

var suite = new Benchmark.Suite();
suite
.add("browser utf8Write", function () {
platformBufferA.utf8Write(jsonString, 0);
})
.add("browser write", function () {
platformBufferB.write(jsonString, 0, 'utf8');
})
.on("complete", function (e) {
e.currentTarget.forEach(({ name, hz }) => {
result.browserCompare[name] = Math.ceil(hz);
});
})
.run({ async: false });
console.log("Write operation per second")
console.table(result.browserCompare);
}

{
const browserBuffer = new BrowserBuffer(jsonString.length);
const nativeBuffer = Buffer.alloc(jsonString.length);

var suite = new Benchmark.Suite();
suite
.add("browser write", function () {
browserBuffer.write(jsonString, 0, 'utf8');
})
.add("native write", function () {
nativeBuffer.write(jsonString, 0, 'utf8');
})
.on("complete", function (e) {
e.currentTarget.forEach(({ name, hz }) => {
result.browserVsNativeWrite[name] = Math.ceil(hz);
});
})
.run({ async: false });
console.log("Write operation per second")
console.table(result.browserVsNativeWrite);
}

{
const browserBuffer = new BrowserBuffer(jsonString.length);
const nativeBuffer = Buffer.alloc(jsonString.length);
browserBuffer.write(jsonString, 0, 'utf8');
nativeBuffer.write(jsonString, 0, 'utf8');

var suite = new Benchmark.Suite();
suite
.add("browser toString", function () {
browserBuffer.toString('utf8', 0, jsonString.length);
})
.add("native toString", function () {
nativeBuffer.toString('utf8', 0, jsonString.length);
})
.on("complete", function (e) {
e.currentTarget.forEach(({ name, hz }) => {
result.browserVsNativeToString[name] = Math.ceil(hz);
});
})
.run({ async: false });
console.log("toString operation per second")
console.table(result.browserVsNativeToString);
}

spawn(
`python3`,
['platform-buffer-draw.py', result.browserCompare["browser utf8Write"], result.browserCompare["browser write"], result.browserVsNativeWrite["browser write"], result.browserVsNativeWrite["native write"], result.browserVsNativeToString["browser toString"], result.browserVsNativeToString["native toString"]],
{
cwd: __dirname,
}
)
}
start();
35 changes: 25 additions & 10 deletions javascript/packages/fury/lib/platformBuffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,38 @@
* under the License.
*/

import { isNodeEnv } from "./util";
import { hasBuffer } from "./util";

export type SupportedEncodings = "latin1" | "utf8";

export interface PlatformBuffer extends Uint8Array {
latin1Slice(start: number, end: number): string;
utf8Slice(start: number, end: number): string;
latin1Write(v: string, offset: number): void;
utf8Write(v: string, offset: number): void;
toString(encoding?: SupportedEncodings, start?: number, end?: number): string;
write(string: string, offset: number, encoding?: SupportedEncodings): void;
copy(target: Uint8Array, targetStart?: number, sourceStart?: number, sourceEnd?: number): void;
}

export class BrowserBuffer extends Uint8Array implements PlatformBuffer {
write(string: string, offset: number, encoding: SupportedEncodings = "utf8"): void {
if (encoding === "latin1") {
return this.latin1Write(string, offset);
}
return this.utf8Write(string, offset);
}

toString(encoding: SupportedEncodings = "utf8", start = 0, end = this.length): string {
if (encoding === "latin1") {
return this.latin1Slice(start, end);
}
return this.utf8Slice(start, end);
}

static alloc(size: number) {
return new BrowserBuffer(new Uint8Array(size));
}

latin1Write(string: string, offset: number) {
for (let index = 0; index < string.length; index++) {
let index = 0;
for (; index < string.length; index++) {
this[offset++] = string.charCodeAt(index);
}
}
Expand Down Expand Up @@ -122,7 +137,7 @@ export class BrowserBuffer extends Uint8Array implements PlatformBuffer {
}
}

export const fromUint8Array = isNodeEnv
export const fromUint8Array = hasBuffer
? (ab: Buffer | Uint8Array) => {
if (!Buffer.isBuffer(ab)) {
return (Buffer.from(ab) as unknown as PlatformBuffer);
Expand All @@ -132,14 +147,14 @@ export const fromUint8Array = isNodeEnv
}
: (ab: Buffer | Uint8Array) => new BrowserBuffer(ab);

export const alloc = (isNodeEnv ? Buffer.allocUnsafe : BrowserBuffer.alloc) as unknown as (size: number) => PlatformBuffer;
export const alloc = (hasBuffer ? Buffer.allocUnsafe : BrowserBuffer.alloc) as unknown as (size: number) => PlatformBuffer;

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

let utf8Encoder: TextEncoder | null;

export const fromString
= isNodeEnv
= hasBuffer
? (str: string) => Buffer.from(str) as unknown as PlatformBuffer
: (str: string) => {
if (!utf8Encoder) {
Expand Down
6 changes: 3 additions & 3 deletions javascript/packages/fury/lib/reader/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const BinaryReader = (config: Config) => {
byteLength = buffer.byteLength;
dataView = new DataView(buffer.buffer, buffer.byteOffset);
if (sliceStringEnable) {
bigString = buffer.latin1Slice(0, byteLength);
bigString = buffer.toString("latin1", 0, byteLength);
}
cursor = 0;
}
Expand Down Expand Up @@ -113,11 +113,11 @@ export const BinaryReader = (config: Config) => {
}

function stringUtf8At(start: number, len: number) {
return buffer.utf8Slice(start, start + len);
return buffer.toString("utf8", start, start + len);
}

function stringUtf8(len: number) {
const result = buffer.utf8Slice(cursor, cursor + len);
const result = buffer.toString("utf8", cursor, cursor + len);
cursor += len;
return result;
}
Expand Down
2 changes: 1 addition & 1 deletion javascript/packages/fury/lib/reader/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,6 @@ export const readLatin1String = (buffer: PlatformBuffer, len: number, cursor: nu
case 15:
return read15(buffer, cursor);
default:
return buffer.latin1Slice(cursor, cursor + len);
return buffer.toString("latin1", cursor, cursor + len,);
}
};
2 changes: 2 additions & 0 deletions javascript/packages/fury/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ export const isNodeEnv: boolean
&& process.versions != null
&& process.env.ECMA_ONLY !== "true"
&& process.versions.node != null;

export const hasBuffer = isNodeEnv && typeof Buffer !== "undefined";
6 changes: 3 additions & 3 deletions javascript/packages/fury/lib/writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ export const BinaryWriter = (config: Config) => {
if (len < 40) {
fastWriteStringUtf8(v, arrayBuffer, cursor);
} else {
arrayBuffer.utf8Write(v, cursor);
arrayBuffer.write(v, cursor, "utf8");
}
}
cursor += len;
Expand All @@ -213,13 +213,13 @@ export const BinaryWriter = (config: Config) => {
arrayBuffer[cursor + index] = v.charCodeAt(index);
}
} else {
arrayBuffer.latin1Write(v, cursor);
arrayBuffer.write(v, cursor, "latin1");
}
} else {
if (len < 40) {
fastWriteStringUtf8(v, arrayBuffer, cursor);
} else {
arrayBuffer.utf8Write(v, cursor);
arrayBuffer.write(v, cursor, "utf8");
}
}
cursor += len;
Expand Down
4 changes: 2 additions & 2 deletions javascript/packages/fury/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@furyjs/fury",
"version": "0.5.6-beta",
"version": "0.5.7-beta",
"description": "A blazing fast multi-language serialization framework powered by jit and zero-copy",
"main": "dist/index.js",
"scripts": {
Expand All @@ -17,7 +17,7 @@
"@typescript-eslint/parser": "^5.40.0",
"benchmark": "^2.1.4",
"eslint": "^8.25.0",
"protobufjs": "^7.2.4"
"protobufjs": "^7.2.4"
},
"dependencies": {
"node-gyp": "^9.4.0",
Expand Down
Loading

0 comments on commit c2238b7

Please sign in to comment.