이 문서에서는 데이터 직렬화 및 역직렬화 메서드/형식(JSON, 버퍼(사용자 정의 바이너리 프로토콜), 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); });
이 함수는 두 가지 속성(텍스트 속성과 숫자 속성)을 배열 버퍼로 직렬화합니다.
이 코드 예제에서는 protobufs의 자바스크립트 구현인 protobuf.js를 사용합니다. 나는 런타임에 protobuf 코드를 생성하기 위해 리플렉션을 사용합니다. 코드를 정적으로 생성할 수도 있지만 protobuf.js wiki에 따르면 성능에 영향을 주지 않습니다. 하지만 protobuf 코드를 더 빠르게 로드하지만 웹소켓 메시지를 보낼 때 성능에 전혀 영향을 주지 않습니다.
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); })
각 데이터 직렬화 형식/방법의 성능을 비교하기 위해 웹소켓을 통해 데이터를 전송할 때 성능을 측정하는 벤치마크를 작성했습니다.
벤치마크를 여러 그룹으로 나누었습니다.
작은 데이터 입력
중간 데이터 입력
빅데이터 입력
이는 다양한 데이터 크기에 대한 데이터 직렬화 성능을 측정하기 위한 것입니다. 그룹별로 직렬화, 역직렬화, 총 시간의 성능도 녹음했습니다. 이러한 테스트의 신뢰성을 보장하기 위해 각 그룹에 대해 정확한 벤치마크를 5회 실행하고 평균을 계산했습니다.
벤치마크는 100,000번의 반복으로 Websocket 메시지를 보냅니다. 코드는 Bun.js로 작성되었습니다
이 벤치마크는 완료 시간(ms)에 기록되었으므로 작을수록 빠릅니다.
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의 직렬화보다 훨씬 빠릅니다 사용자 정의 바이너리 프로토콜. 이는 아마도 JSON.stringify()가 Node로 네이티브 c를 구현하고 Bun으로 네이티브 zig를 구현했기 때문일 것입니다. Bun이 포함된 JSON.stringify()가 Node보다 3.5배 빠르기 때문에 Node를 사용할 때 결과가 달라질 수도 있습니다.
이 벤치마크에서는 공식 javascript MessagePack 구현을 사용했기 때문에 MessagePack이 잠재적으로 더 빠를 수 있습니다. MessagePackr와 같이 잠재적으로 더 빠른 다른 MessagePack 구현이 있습니다.
읽어주셔서 감사합니다!
벤치마크(타입스크립트로 작성): https://github.com/nate10j/buffer-vs-json-websocket-benchmark.git
Google 시트에서 결과를 확인하세요.
위 내용은 웹소켓용 JSON, 버퍼/사용자 정의 바이너리 프로토콜, Protobuf 및 MessagePack의 성능 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!