Heim > Artikel > Web-Frontend > Leistungsanalyse von JSON, Buffer/Custom Binary Protocol, Protobuf und MessagePack für Websockets
Dieser Artikel untersucht und vergleicht Methoden/Formate der Datenserialisierung und -deserialisierung: JSON, Buffers (benutzerdefiniertes Binärprotokoll), Protobuf und MessagePack und bietet Anleitungen zu deren Implementierung . (Leistungsbenchmark am Ende)
Dies ist die gebräuchlichste Methode zum Senden von Nachrichten: JSON. Wo Sie Daten in eine Zeichenfolge kodieren, damit sie über eine Websocket-Nachricht weitergeleitet und zurückgeparst werden können.
ws.send(JSON.stringify({greeting: "Hello World"]})) ws.on("message", (message) => { const data = JSON.parse(message); console.log(data) })
Ein benutzerdefiniertes Binärprotokoll ist eine einfache benutzerdefinierte Implementierung der Serialisierung und Deserialisierung von Daten. Es wird häufig verwendet, wenn Geschwindigkeit, Leistung und geringe Latenz entscheidend sind, z. B. Online-Multiplayer-Spiele und mehr (oder wenn Sie Ihre App optimieren möchten). Beim Erstellen eines benutzerdefinierten Binärprotokolls arbeiten Sie mit Puffern und Binärdateien, was möglicherweise schwierig zu implementieren ist. Wenn Sie jedoch Kenntnisse über Puffer und Binärdateien haben, sollte dies kein Problem sein.
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); });
Diese Funktion serialisiert zwei Eigenschaften, eine davon ist Text und die andere eine Zahl, in einen Array-Puffer.
In diesem Codebeispiel verwende ich protobuf.js, eine Javascript-Implementierung von protobufs. Ich verwende Reflection, um den Protobuf-Code zur Laufzeit zu generieren. Sie können Code auch statisch generieren, aber laut protobuf.js-Wiki hat dies keine Auswirkungen auf die Leistung. Protobuf-Code wird jedoch schneller geladen, aber das hat keinerlei Auswirkungen auf die Leistung beim Senden von Websocket-Nachrichten.
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); })
Um die Leistung der einzelnen Formate/Methoden der Datenserialisierung zu vergleichen, habe ich einen Benchmark geschrieben, der die Leistung beim Senden von Daten über Websockets misst.
Ich habe die Benchmarks in verschiedene Gruppen aufgeteilt.
Kleine Dateneingabe
Mittlere Dateneingabe
Big-Data-Eingabe
Dies dient dazu, die Leistung dieser Datenserialisierung über verschiedene Datengrößen hinweg zu messen. Ich habe auch die Leistung der Serialisierung, Deserialisierung und Gesamtzeit für jede Gruppe aufgezeichnet. Ich habe die genauen Benchmarks fünfmal für jede Gruppe durchgeführt und den Durchschnitt berechnet, um die Zuverlässigkeit dieser Tests sicherzustellen.
Der Benchmark sendet Websocket-Nachrichten in 100.000 Iterationen. Der Code ist in Bun.js
geschriebenDiese Benchmarks wurden in der Zeit bis zum Abschluss (ms) aufgezeichnet, also kleiner ist schneller.
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 |
Nach etwa 6.600 gesendeten Nachrichten funktionierte MessagePack plötzlich nicht mehr.
In allen Benchmarks ist das benutzerdefinierte Binärprotokoll das schnellste in der Gesamtzeit und hat die kleinste/effizienteste Bytegröße bei der Serialisierung Nachrichten. Der Leistungsunterschied ist jedoch erheblich.
Überraschenderweise ist JSONss Serialisierungszeit deutlich schneller als die Serialisierung von Benutzerdefiniertes Binärprotokoll. Dies liegt wahrscheinlich daran, dass JSON.stringify() nativ c mit Node und nativ zig mit Bun implementiert ist. Die Ergebnisse können auch bei Verwendung von Node variieren, da JSON.stringify() mit Bun 3,5-mal schneller ist als Node.
MessagePack könnte möglicherweise schneller sein, da ich in diesem Benchmark die offizielle Javascript-MessagePack-Implementierung verwendet habe. Es gibt andere potenziell schnellere MessagePack-Implementierungen wie MessagePackr.
Danke fürs Lesen!
Benchmark (in Typoskript geschrieben): https://github.com/nate10j/buffer-vs-json-websocket-benchmark.git
Ergebnisse finden Sie hier in Google Sheets.
Das obige ist der detaillierte Inhalt vonLeistungsanalyse von JSON, Buffer/Custom Binary Protocol, Protobuf und MessagePack für Websockets. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!