首页  >  文章  >  web前端  >  Websockets 的 JSON、Buffer/自定义二进制协议、Protobuf 和 MessagePack 的性能分析

Websockets 的 JSON、Buffer/自定义二进制协议、Protobuf 和 MessagePack 的性能分析

DDD
DDD原创
2024-11-03 07:28:03230浏览

本文检查并比较了数据序列化和反序列化方法/格式:JSON、Buffers(自定义二进制协议)、Protobuf 和 MessagePack,并提供有关如何实现它们的指导。 (最后的性能基准)

JSON

这是发送消息的最常见方法是 JSON。将数据编码为字符串,以便可以通过 Websocket 消息传递并将其解析回来。

ws.send(JSON.stringify({greeting: "Hello World"]}))

ws.on("message", (message) => {
    const data = JSON.parse(message);
    console.log(data)
})

自定义二进制协议

自定义二进制协议序列化反序列化数据的轻量级自定义实现。当速度性能低延迟至关重要时,通常使用它,例如在线多人游戏以及更多(或者如果您想优化您的应用程序)。在构建自定义二进制协议时,您使用缓冲区和二进制文件,这可能很难实现,但是如果您了解缓冲区和二进制文件,那么应该没问题。

const encoder = new TextEncoder();
const decoder = new TextDecoder();

function binary(text, num) {
    const messageBytes = encoder.encode(text);
    // array buffers cannot store strings, so you must encode
    // the text first into an array of binary values
    // e.g. "Hello World!" -> [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]

    const buffer = new ArrayBuffer(1 + messageBytes.length);
    // when creating array buffers,
    //you must predetermine the size of your array buffer

    const view = new DataView(buffer);
    // create a view to interact with the buffer

    view.setUint8(0, num);

    const byteArray = new Uint8Array(buffer);
    byteArray.set(messageBytes, 1);

    return buffer;
}

ws.on("open", () => {
    const msg = binary("Hello World!", 123);
    ws.send(msg);
})

ws.on("message", (message) => {
    const buffer = message.buffer;
    const view = new DataView(buffer);
    const num = view.getUint8(0);
    const textLength = buffer.byteLength - 1
    const textBytes = new Uint8Array(buffer, 1, textLength);
    const text = decoder.decode(textBytes);

    console.log(text, num);
});

此函数序列化两个属性,一个是文本,另一个是数字到数组缓冲区

原始缓冲区

在此代码示例中,我使用 protobuf.js,protobufs 的 JavaScript 实现。我使用反射在运行时生成 protobuf 代码。您还可以静态生成代码,但根据 protobuf.js wiki,它对性能没有影响,但是它确实更快地加载 protobuf 代码,但这根本不会影响发送 websocket 消息时的性能。

syntax = "proto3";

message TestMessage {
    string text = 1;
    uint32 num = 2;
}
import protobuf from "protobufjs";

const ws = new Websocket("ws://localhost:3000");
ws.binaryType = "arraybuffer";

protobuf.load("testmessage.proto", function (err, root) {
    if (err)
        throw err;

    if (root === undefined) return;

    const TestMessage = root.lookupType("TestMessage")

    ws.on("open", () => {
        const message = TestMessage.create({text: "Hello World!", num: 12345});
        const buffer = TestMessage.encode(message).finish();
        ws.send(buffer);
    });

    ws.on("message", (msg) => {
        const buffer = new Uint8Array(msg);
        const data = TestMessage.decode(buffer);
        console.log(data.text);
        console.log(data.num);
    });
})

消息包

import { encode, decode } from "@msgpack/msgpack";

ws.binaryType = "arraybuffer"

ws.on("open", () => {
    const msg = encode({"Hello World!", 123});
    ws.send(msg);
})

ws.on("message", (msg) => {
    const data = decode(msg);
    console.log(data);
})

性能基准

为了比较每种数据序列化格式/方法的性能,我编写了一个基准测试来衡量通过Websockets发送数据时的性能。

我已将基准测试分为不同的组。

  • 小数据输入

  • 中等数据输入

  • 大数据输入

这是为了衡量这些数据序列化在不同数据大小上的性能。我还记录了每组序列化、反序列化和总时间的表现。我为每组运行了 5 次精确基准测试并计算平均值以确保这些测试的可靠性。

基准测试以 100,000 次迭代的方式发送 Websocket 消息。代码是用Bun.js

编写的

这些基准测试是在完成时间(毫秒)内记录的,因此 越小越好

小数据基准

ws.send(JSON.stringify({greeting: "Hello World"]}))

ws.on("message", (message) => {
    const data = JSON.parse(message);
    console.log(data)
})

每种序列化格式的字节大小

Method Byte size (bytes)
JSON 33
Custom Binary Protocol 13
Protobuf 17
MessagePack 24

总时间(毫秒)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

序列化时间(毫秒)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

反序列化时间(毫秒)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

中等数据基准

const encoder = new TextEncoder();
const decoder = new TextDecoder();

function binary(text, num) {
    const messageBytes = encoder.encode(text);
    // array buffers cannot store strings, so you must encode
    // the text first into an array of binary values
    // e.g. "Hello World!" -> [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]

    const buffer = new ArrayBuffer(1 + messageBytes.length);
    // when creating array buffers,
    //you must predetermine the size of your array buffer

    const view = new DataView(buffer);
    // create a view to interact with the buffer

    view.setUint8(0, num);

    const byteArray = new Uint8Array(buffer);
    byteArray.set(messageBytes, 1);

    return buffer;
}

ws.on("open", () => {
    const msg = binary("Hello World!", 123);
    ws.send(msg);
})

ws.on("message", (message) => {
    const buffer = message.buffer;
    const view = new DataView(buffer);
    const num = view.getUint8(0);
    const textLength = buffer.byteLength - 1
    const textBytes = new Uint8Array(buffer, 1, textLength);
    const text = decoder.decode(textBytes);

    console.log(text, num);
});

每种序列化格式中消息的字节大小

Method Byte size (bytes)
JSON 117
Custom Binary Protocol 70
Protobuf 75
MessagePack 102

总时间(毫秒)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

序列化(毫秒)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

反序列化(毫秒)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

大数据基准

syntax = "proto3";

message TestMessage {
    string text = 1;
    uint32 num = 2;
}

每种序列化格式中消息的字节大小

Method Byte size (bytes)
JSON 329
Custom Binary Protocol 220
Protobuf 229
MessagePack 277

总时间(毫秒)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

序列化(毫秒)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

反序列化(毫秒)

Performance Analysis of JSON, Buffer / Custom Binary Protocol, Protobuf, and MessagePack for Websockets

MessagePack 在发送了大约 6600 条消息时突然停止工作。

基准分析

在所有基准测试中,自定义二进制协议 总时间最快,并且在序列化时具有最小/最高效的字节大小消息。然而,性能差异是显着的。

令人惊讶的是,JSON 的的序列化时间 明显快于 的序列化 自定义二进制协议。这可能是因为 JSON.stringify() 是使用 Node 实现的原生 c 以及使用 Bun 实现的原生 zig。使用 Node 时结果也可能会有所不同,因为使用 Bun 的 JSON.stringify() 比 Node 快 3.5 倍。

MessagePack 可能会更快,因为在此基准测试中,我使用了官方的 javascript MessagePack 实现。还有其他可能更快的 MessagePack 实现,例如 MessagePackr。

感谢您的阅读!


基准(用打字稿编写):https://github.com/nate10j/buffer-vs-json-websocket-benchmark.git

在 Google 表格中查看结果。

以上是Websockets 的 JSON、Buffer/自定义二进制协议、Protobuf 和 MessagePack 的性能分析的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn