speice.io/assets/js/39c8d8a0.85cdb3d2.js

1 line
23 KiB
JavaScript

"use strict";(self.webpackChunkspeice_io=self.webpackChunkspeice_io||[]).push([["156"],{5100:function(e,t,n){n.r(t),n.d(t,{assets:function(){return l},contentTitle:function(){return o},default:function(){return d},frontMatter:function(){return r},metadata:function(){return s},toc:function(){return h}});var s=n(13303),i=n(85893),a=n(50065);let r={slug:"2019/09/binary-format-shootout",title:"Binary format shootout",date:new Date("2019-09-28T12:00:00.000Z"),authors:["bspeice"],tags:[]},o=void 0,l={authorsImageUrls:[void 0]},h=[{value:"Prologue: Binary Parsing with Nom",id:"prologue-binary-parsing-with-nom",level:2},{value:"Cap&#39;n Proto",id:"capn-proto",level:2},{value:"Flatbuffers",id:"flatbuffers",level:2},{value:"Simple Binary Encoding",id:"simple-binary-encoding",level:2},{value:"Results",id:"results",level:2},{value:"Serialization",id:"serialization",level:3},{value:"Deserialization",id:"deserialization",level:3},{value:"Conclusion",id:"conclusion",level:2}];function c(e){let t={a:"a",code:"code",del:"del",h2:"h2",h3:"h3",li:"li",ol:"ol",p:"p",pre:"pre",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,a.a)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)(t.p,{children:["I've found that in many personal projects,\n",(0,i.jsx)(t.a,{href:"https://en.wikipedia.org/wiki/Analysis_paralysis",children:"analysis paralysis"})," is particularly deadly.\nMaking good decisions in the beginning avoids pain and suffering later; if extra research prevents\nfuture problems, I'm happy to continue ",(0,i.jsx)(t.del,{children:"procrastinating"})," researching indefinitely."]}),"\n",(0,i.jsx)(t.p,{children:"So let's say you're in need of a binary serialization format. Data will be going over the network,\nnot just in memory, so having a schema document and code generation is a must. Performance is\ncrucial, so formats that support zero-copy de/serialization are given priority. And the more\nlanguages supported, the better; I use Rust, but can't predict what other languages this could\ninteract with."}),"\n",(0,i.jsx)(t.p,{children:"Given these requirements, the candidates I could find were:"}),"\n",(0,i.jsxs)(t.ol,{children:["\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.a,{href:"https://capnproto.org/",children:"Cap'n Proto"})," has been around the longest, and is the most established"]}),"\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.a,{href:"https://google.github.io/flatbuffers/",children:"Flatbuffers"})," is the newest, and claims to have a simpler\nencoding"]}),"\n",(0,i.jsxs)(t.li,{children:[(0,i.jsx)(t.a,{href:"https://github.com/real-logic/simple-binary-encoding",children:"Simple Binary Encoding"})," has the simplest\nencoding, but the Rust implementation is unmaintained"]}),"\n"]}),"\n",(0,i.jsx)(t.p,{children:"Any one of these will satisfy the project requirements: easy to transmit over a network, reasonably\nfast, and polyglot support. But how do you actually pick one? It's impossible to know what issues\nwill follow that choice, so I tend to avoid commitment until the last possible moment."}),"\n",(0,i.jsxs)(t.p,{children:['Still, a choice must be made. Instead of worrying about which is "the best," I decided to build a\nsmall proof-of-concept system in each format and pit them against each other. All code can be found\nin the ',(0,i.jsx)(t.a,{href:"https://github.com/speice-io/marketdata-shootout",children:"repository"})," for this post."]}),"\n",(0,i.jsx)(t.p,{children:"We'll discuss more in detail, but a quick preview of the results:"}),"\n",(0,i.jsxs)(t.ul,{children:["\n",(0,i.jsx)(t.li,{children:"Cap'n Proto: Theoretically performs incredibly well, the implementation had issues"}),"\n",(0,i.jsx)(t.li,{children:'Flatbuffers: Has some quirks, but largely lived up to its "zero-copy" promises'}),"\n",(0,i.jsx)(t.li,{children:"SBE: Best median and worst-case performance, but the message structure has a limited feature set"}),"\n"]}),"\n",(0,i.jsx)(t.h2,{id:"prologue-binary-parsing-with-nom",children:"Prologue: Binary Parsing with Nom"}),"\n",(0,i.jsxs)(t.p,{children:["Our benchmark system will be a simple data processor; given depth-of-book market data from\n",(0,i.jsx)(t.a,{href:"https://iextrading.com/trading/market-data/#deep",children:"IEX"}),", serialize each message into the schema\nformat, read it back, and calculate total size of stock traded and the lowest/highest quoted prices.\nThis test isn't complex, but is representative of the project I need a binary format for."]}),"\n",(0,i.jsxs)(t.p,{children:["But before we make it to that point, we have to actually read in the market data. To do so, I'm\nusing a library called ",(0,i.jsx)(t.a,{href:"https://github.com/Geal/nom",children:(0,i.jsx)(t.code,{children:"nom"})}),". Version 5.0 was recently released and\nbrought some big changes, so this was an opportunity to build a non-trivial program and get\nfamiliar."]}),"\n",(0,i.jsxs)(t.p,{children:["If you don't already know about ",(0,i.jsx)(t.code,{children:"nom"}),', it\'s a "parser generator". By combining different smaller\nparsers, you can assemble a parser to handle complex structures without writing tedious code by\nhand. For example, when parsing\n',(0,i.jsx)(t.a,{href:"https://www.winpcap.org/ntar/draft/PCAP-DumpFileFormat.html#rfc.section.3.3",children:"PCAP files"}),":"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{children:" 0 1 2 3\n 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n +---------------------------------------------------------------+\n 0 | Block Type = 0x00000006 |\n +---------------------------------------------------------------+\n 4 | Block Total Length |\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n 8 | Interface ID |\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n12 | Timestamp (High) |\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n16 | Timestamp (Low) |\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n20 | Captured Len |\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n24 | Packet Len |\n +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n | Packet Data |\n | ... |\n"})}),"\n",(0,i.jsxs)(t.p,{children:["...you can build a parser in ",(0,i.jsx)(t.code,{children:"nom"})," that looks like\n",(0,i.jsx)(t.a,{href:"https://github.com/speice-io/marketdata-shootout/blob/369613843d39cfdc728e1003123bf87f79422497/src/parsers.rs#L59-L93",children:"this"}),":"]}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{className:"language-rust",children:"const ENHANCED_PACKET: [u8; 4] = [0x06, 0x00, 0x00, 0x00];\npub fn enhanced_packet_block(input: &[u8]) -> IResult<&[u8], &[u8]> {\n let (\n remaining,\n (\n block_type,\n block_len,\n interface_id,\n timestamp_high,\n timestamp_low,\n captured_len,\n packet_len,\n ),\n ) = tuple((\n tag(ENHANCED_PACKET),\n le_u32,\n le_u32,\n le_u32,\n le_u32,\n le_u32,\n le_u32,\n ))(input)?;\n\n let (remaining, packet_data) = take(captured_len)(remaining)?;\n Ok((remaining, packet_data))\n}\n"})}),"\n",(0,i.jsxs)(t.p,{children:["While this example isn't too interesting, more complex formats (like IEX market data) are where\n",(0,i.jsxs)(t.a,{href:"https://github.com/speice-io/marketdata-shootout/blob/369613843d39cfdc728e1003123bf87f79422497/src/iex.rs",children:[(0,i.jsx)(t.code,{children:"nom"})," really shines"]}),"."]}),"\n",(0,i.jsxs)(t.p,{children:["Ultimately, because the ",(0,i.jsx)(t.code,{children:"nom"})," code in this shootout was the same for all formats, we're not too\ninterested in its performance. Still, it's worth mentioning that building the market data parser was\nactually fun; I didn't have to write tons of boring code by hand."]}),"\n",(0,i.jsx)(t.h2,{id:"capn-proto",children:"Cap'n Proto"}),"\n",(0,i.jsxs)(t.p,{children:["Now it's time to get into the meaty part of the story. Cap'n Proto was the first format I tried\nbecause of how long it has supported Rust (thanks to ",(0,i.jsx)(t.a,{href:"https://github.com/dwrensha",children:"dwrensha"})," for\nmaintaining the Rust port since\n",(0,i.jsx)(t.a,{href:"https://github.com/capnproto/capnproto-rust/releases/tag/rustc-0.10",children:"2014!"}),"). However, I had a ton\nof performance concerns once I started using it."]}),"\n",(0,i.jsxs)(t.p,{children:['To serialize new messages, Cap\'n Proto uses a "builder" object. This builder allocates memory on the\nheap to hold the message content, but because builders\n',(0,i.jsx)(t.a,{href:"https://github.com/capnproto/capnproto-rust/issues/111",children:"can't be re-used"}),", we have to allocate a\nnew buffer for every single message. I was able to work around this with a\n",(0,i.jsx)(t.a,{href:"https://github.com/speice-io/marketdata-shootout/blob/369613843d39cfdc728e1003123bf87f79422497/src/capnp_runner.rs#L17-L51",children:"special builder"}),"\nthat could re-use the buffer, but it required reading through Cap'n Proto's\n",(0,i.jsx)(t.a,{href:"https://github.com/capnproto/capnproto-rust/blob/master/benchmark/benchmark.rs#L124-L156",children:"benchmarks"}),"\nto find an example, and used\n",(0,i.jsx)(t.a,{href:"https://doc.rust-lang.org/std/mem/fn.transmute.html",children:(0,i.jsx)(t.code,{children:"std::mem::transmute"})})," to bypass Rust's borrow\nchecker."]}),"\n",(0,i.jsxs)(t.p,{children:["The process of reading messages was better, but still had issues. Cap'n Proto has two message\nencodings: a ",(0,i.jsx)(t.a,{href:"https://capnproto.org/encoding.html#packing",children:'"packed"'})," representation, and an\n\"unpacked\" version. When reading \"packed\" messages, we need a buffer to unpack the message into\nbefore we can use it; Cap'n Proto allocates a new buffer for each message we unpack, and I wasn't\nable to figure out a way around that. In contrast, the unpacked message format should be where Cap'n\nProto shines; its main selling point is that there's ",(0,i.jsx)(t.a,{href:"https://capnproto.org/",children:"no decoding step"}),".\nHowever, accomplishing zero-copy deserialization required code in the private API\n(",(0,i.jsx)(t.a,{href:"https://github.com/capnproto/capnproto-rust/issues/148",children:"since fixed"}),"), and we allocate a vector on\nevery read for the segment table."]}),"\n",(0,i.jsx)(t.p,{children:"In the end, I put in significant work to make Cap'n Proto as fast as possible, but there were too\nmany issues for me to feel comfortable using it long-term."}),"\n",(0,i.jsx)(t.h2,{id:"flatbuffers",children:"Flatbuffers"}),"\n",(0,i.jsxs)(t.p,{children:["This is the new kid on the block. After a\n",(0,i.jsx)(t.a,{href:"https://github.com/google/flatbuffers/pull/3894",children:"first attempt"})," didn't pan out, official support\nwas ",(0,i.jsx)(t.a,{href:"https://github.com/google/flatbuffers/pull/4898",children:"recently launched"}),". Flatbuffers intends to\naddress the same problems as Cap'n Proto: high-performance, polyglot, binary messaging. The\ndifference is that Flatbuffers claims to have a simpler wire format and\n",(0,i.jsx)(t.a,{href:"https://google.github.io/flatbuffers/flatbuffers_benchmarks.html",children:"more flexibility"}),"."]}),"\n",(0,i.jsxs)(t.p,{children:["On the whole, I enjoyed using Flatbuffers; the ",(0,i.jsx)(t.a,{href:"https://crates.io/crates/flatc-rust",children:"tooling"})," is\nnice, and unlike Cap'n Proto, parsing messages was actually zero-copy and zero-allocation. However,\nthere were still some issues."]}),"\n",(0,i.jsx)(t.p,{children:"First, Flatbuffers (at least in Rust) can't handle nested vectors. This is a problem for formats\nlike the following:"}),"\n",(0,i.jsx)(t.pre,{children:(0,i.jsx)(t.code,{children:"table Message {\n symbol: string;\n}\ntable MultiMessage {\n messages:[Message];\n}\n"})}),"\n",(0,i.jsxs)(t.p,{children:["We want to create a ",(0,i.jsx)(t.code,{children:"MultiMessage"})," which contains a vector of ",(0,i.jsx)(t.code,{children:"Message"}),", and each ",(0,i.jsx)(t.code,{children:"Message"})," itself\ncontains a vector (the ",(0,i.jsx)(t.code,{children:"string"})," type). I was able to work around this by\n",(0,i.jsxs)(t.a,{href:"https://github.com/speice-io/marketdata-shootout/blob/e9d07d148bf36a211a6f86802b313c4918377d1b/src/flatbuffers_runner.rs#L83",children:["caching ",(0,i.jsx)(t.code,{children:"Message"})," elements"]}),"\nin a ",(0,i.jsx)(t.code,{children:"SmallVec"})," before building the final ",(0,i.jsx)(t.code,{children:"MultiMessage"}),", but it was a painful process that I\nbelieve contributed to poor serialization performance."]}),"\n",(0,i.jsxs)(t.p,{children:["Second, streaming support in Flatbuffers seems to be something of an\n",(0,i.jsx)(t.a,{href:"https://github.com/google/flatbuffers/issues/3898",children:"afterthought"}),". Where Cap'n Proto in Rust handles\nreading messages from a stream as part of the API, Flatbuffers just sticks a ",(0,i.jsx)(t.code,{children:"u32"})," at the front of\neach message to indicate the size. Not specifically a problem, but calculating message size without\nthat tag is nigh on impossible."]}),"\n",(0,i.jsx)(t.p,{children:"Ultimately, I enjoyed using Flatbuffers, and had to do significantly less work to make it perform\nwell."}),"\n",(0,i.jsx)(t.h2,{id:"simple-binary-encoding",children:"Simple Binary Encoding"}),"\n",(0,i.jsxs)(t.p,{children:["Support for SBE was added by the author of one of my favorite\n",(0,i.jsx)(t.a,{href:"https://web.archive.org/web/20190427124806/https://polysync.io/blog/session-types-for-hearty-codecs/",children:"Rust blog posts"}),".\nI've ",(0,i.jsx)(t.a,{href:"/2019/06/high-performance-systems",children:"talked previously"})," about how important\nvariance is in high-performance systems, so it was encouraging to read about a format that\n",(0,i.jsx)(t.a,{href:"https://github.com/real-logic/simple-binary-encoding/wiki/Why-Low-Latency",children:"directly addressed"})," my\nconcerns. SBE has by far the simplest binary format, but it does make some tradeoffs."]}),"\n",(0,i.jsxs)(t.p,{children:["Both Cap'n Proto and Flatbuffers use ",(0,i.jsx)(t.a,{href:"https://capnproto.org/encoding.html#structs",children:"message offsets"}),"\nto handle variable-length data, ",(0,i.jsx)(t.a,{href:"https://capnproto.org/language.html#unions",children:"unions"}),", and various\nother features. In contrast, messages in SBE are essentially\n",(0,i.jsx)(t.a,{href:"https://github.com/real-logic/simple-binary-encoding/blob/master/sbe-samples/src/main/resources/example-schema.xml",children:"just structs"}),";\nvariable-length data is supported, but there's no union type."]}),"\n",(0,i.jsxs)(t.p,{children:["As mentioned in the beginning, the Rust port of SBE works well, but is\n",(0,i.jsx)(t.a,{href:"https://users.rust-lang.org/t/zero-cost-abstraction-frontier-no-copy-low-allocation-ordered-decoding/11515/9",children:"essentially unmaintained"}),".\nHowever, if you don't need union types, and can accept that schemas are XML documents, it's still\nworth using. SBE's implementation had the best streaming support of all formats I tested, and\ndoesn't trigger allocation during de/serialization."]}),"\n",(0,i.jsx)(t.h2,{id:"results",children:"Results"}),"\n",(0,i.jsxs)(t.p,{children:["After building a test harness\n",(0,i.jsx)(t.a,{href:"https://github.com/speice-io/marketdata-shootout/blob/master/src/capnp_runner.rs",children:"for"}),"\n",(0,i.jsx)(t.a,{href:"https://github.com/speice-io/marketdata-shootout/blob/master/src/flatbuffers_runner.rs",children:"each"}),"\n",(0,i.jsx)(t.a,{href:"https://github.com/speice-io/marketdata-shootout/blob/master/src/sbe_runner.rs",children:"format"}),", it was\ntime to actually take them for a spin. I used\n",(0,i.jsx)(t.a,{href:"https://github.com/speice-io/marketdata-shootout/blob/master/run_shootout.sh",children:"this script"})," to run\nthe benchmarks, and the raw results are\n",(0,i.jsx)(t.a,{href:"https://github.com/speice-io/marketdata-shootout/blob/master/shootout.csv",children:"here"}),". All data reported\nbelow is the average of 10 runs on a single day of IEX data. Results were validated to make sure\nthat each format parsed the data correctly."]}),"\n",(0,i.jsx)(t.h3,{id:"serialization",children:"Serialization"}),"\n",(0,i.jsxs)(t.p,{children:["This test measures, on a\n",(0,i.jsx)(t.a,{href:"https://github.com/speice-io/marketdata-shootout/blob/master/src/main.rs#L268-L272",children:"per-message basis"}),",\nhow long it takes to serialize the IEX message into the desired format and write to a pre-allocated\nbuffer."]}),"\n",(0,i.jsxs)(t.table,{children:[(0,i.jsx)(t.thead,{children:(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.th,{style:{textAlign:"left"},children:"Schema"}),(0,i.jsx)(t.th,{style:{textAlign:"left"},children:"Median"}),(0,i.jsx)(t.th,{style:{textAlign:"left"},children:"99th Pctl"}),(0,i.jsx)(t.th,{style:{textAlign:"left"},children:"99.9th Pctl"}),(0,i.jsx)(t.th,{style:{textAlign:"left"},children:"Total"})]})}),(0,i.jsxs)(t.tbody,{children:[(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"Cap'n Proto Packed"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"413ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"1751ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"2943ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"14.80s"})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"Cap'n Proto Unpacked"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"273ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"1828ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"2836ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"10.65s"})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"Flatbuffers"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"355ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"2185ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"3497ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"14.31s"})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"SBE"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"91ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"1535ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"2423ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"3.91s"})]})]})]}),"\n",(0,i.jsx)(t.h3,{id:"deserialization",children:"Deserialization"}),"\n",(0,i.jsxs)(t.p,{children:["This test measures, on a\n",(0,i.jsx)(t.a,{href:"https://github.com/speice-io/marketdata-shootout/blob/master/src/main.rs#L294-L298",children:"per-message basis"}),",\nhow long it takes to read the previously-serialized message and perform some basic aggregation. The\naggregation code is the same for each format, so any performance differences are due solely to the\nformat implementation."]}),"\n",(0,i.jsxs)(t.table,{children:[(0,i.jsx)(t.thead,{children:(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.th,{style:{textAlign:"left"},children:"Schema"}),(0,i.jsx)(t.th,{style:{textAlign:"left"},children:"Median"}),(0,i.jsx)(t.th,{style:{textAlign:"left"},children:"99th Pctl"}),(0,i.jsx)(t.th,{style:{textAlign:"left"},children:"99.9th Pctl"}),(0,i.jsx)(t.th,{style:{textAlign:"left"},children:"Total"})]})}),(0,i.jsxs)(t.tbody,{children:[(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"Cap'n Proto Packed"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"539ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"1216ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"2599ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"18.92s"})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"Cap'n Proto Unpacked"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"366ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"737ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"1583ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"12.32s"})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"Flatbuffers"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"173ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"421ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"1007ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"6.00s"})]}),(0,i.jsxs)(t.tr,{children:[(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"SBE"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"116ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"286ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"659ns"}),(0,i.jsx)(t.td,{style:{textAlign:"left"},children:"4.05s"})]})]})]}),"\n",(0,i.jsx)(t.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,i.jsx)(t.p,{children:'Building a benchmark turned out to be incredibly helpful in making a decision; because a "union"\ntype isn\'t important to me, I can be confident that SBE best addresses my needs.'}),"\n",(0,i.jsx)(t.p,{children:"While SBE was the fastest in terms of both median and worst-case performance, its worst case\nperformance was proportionately far higher than any other format. It seems to be that\nde/serialization time scales with message size, but I'll need to do some more research to understand\nwhat exactly is going on."})]})}function d(e={}){let{wrapper:t}={...(0,a.a)(),...e.components};return t?(0,i.jsx)(t,{...e,children:(0,i.jsx)(c,{...e})}):c(e)}},50065:function(e,t,n){n.d(t,{Z:function(){return o},a:function(){return r}});var s=n(67294);let i={},a=s.createContext(i);function r(e){let t=s.useContext(a);return s.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function o(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:r(e.components),s.createElement(a.Provider,{value:t},e.children)}},13303:function(e){e.exports=JSON.parse('{"permalink":"/2019/09/binary-format-shootout","source":"@site/blog/2019-09-28-binary-format-shootout/index.mdx","title":"Binary format shootout","description":"I\'ve found that in many personal projects,","date":"2019-09-28T12:00:00.000Z","tags":[],"readingTime":8.37,"hasTruncateMarker":true,"authors":[{"name":"Bradlee Speice","socials":{"github":"https://github.com/bspeice"},"key":"bspeice","page":null}],"frontMatter":{"slug":"2019/09/binary-format-shootout","title":"Binary format shootout","date":"2019-09-28T12:00:00.000Z","authors":["bspeice"],"tags":[]},"unlisted":false,"lastUpdatedAt":1731207983000,"prevItem":{"title":"Release the GIL","permalink":"/2019/12/release-the-gil"},"nextItem":{"title":"On building high performance systems","permalink":"/2019/06/high-performance-systems"}}')}}]);