Heim  >  Artikel  >  Web-Frontend  >  Leistungsanalyse von JSON, Buffer/Custom Binary Protocol, Protobuf und MessagePack für Websockets

Leistungsanalyse von JSON, Buffer/Custom Binary Protocol, Protobuf und MessagePack für Websockets

DDD
DDDOriginal
2024-11-03 07:28:03229Durchsuche

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)

JSON

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)
})

Benutzerdefiniertes Binärprotokoll

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.

Protobuf

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);
    });
})

MessagePack

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);
})

Leistungsbenchmark

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

geschrieben

Diese Benchmarks wurden in der Zeit bis zum Abschluss (ms) aufgezeichnet, also kleiner ist schneller.

Kleiner Daten-Benchmark

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

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

Bytegröße jedes Serialisierungsformats

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

Gesamtzeit (ms)

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

Serialisierungszeit (ms)

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

Deserialisierungszeit (ms)

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

Mittlerer Datenbenchmark

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);
});

Bytegröße der Nachricht in jedem Serialisierungsformat

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

Gesamtzeit (ms)

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

Serialisierung (ms)

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

Deserialisierung (ms)

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

Big-Data-Benchmark

syntax = "proto3";

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

Bytegröße der Nachricht in jedem Serialisierungsformat

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

Gesamtzeit (ms)

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

Serialisierung (ms)

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

Deserialisierung (ms)

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

Nach etwa 6.600 gesendeten Nachrichten funktionierte MessagePack plötzlich nicht mehr.

Analyse von Benchmarks

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!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn