本文檢查並比較了資料序列化和反序列化方法/格式: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中文網其他相關文章!