本文检查并比较了数据序列化和反序列化方法/格式:JSON、Buffers(自定义二进制协议)、Protobuf 和 MessagePack,并提供有关如何实现它们的指导。 (最后的性能基准)
这是发送消息的最常见方法是 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 |
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 |
syntax = "proto3"; message TestMessage { string text = 1; uint32 num = 2; }
Method | Byte size (bytes) |
---|---|
JSON | 329 |
Custom Binary Protocol | 220 |
Protobuf | 229 |
MessagePack | 277 |
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中文网其他相关文章!