Cutting Down Bandwidth with JSON Alternatives

Dou­glas Crock­ford wrote RFC 4627, describ­ing the spec­i­fi­ca­tions for JSON, a “text for­mat for seri­al­iza­tion of struc­tured data.” As a language-agnostic, human-readable open for­mat that has native sup­port for encoding/decoding in browsers, JSON has become the de facto stan­dard for data seri­al­iza­tion on the web. There are draw­backs to using JSON, which became evi­dent when we started to write a net­worked game using Web­Sock­ets. (Check out our pre-alpha teaser if you didn’t get a chance to see us at PAX!)

The Setup

In our 3D game, play­ers have a posi­tion and rota­tion that are updated every sim­u­la­tion step. I will refer to the pair of posi­tion and rota­tion together as a trans­form. The server noti­fies clients of these updated trans­forms over Web­Sock­ets. Here is an exam­ple of a trans­form we might have at a given moment:

Here, we rep­re­sent the rota­tion using a quater­nion, rather than a 3×3 matrix, because it has only four val­ues ver­sus the nine val­ues in the matrix. When using socket.io’s emit func­tion­al­ity, the data argu­ments you pro­vide are seri­al­ized using JSON.stringify and then sent out. Here is the pre­vi­ous trans­form seri­al­ized to JSON:

The result­ing string is 190 char­ac­ters long. So what does that mean for us?

The Prob­lem

This is a mul­ti­player game, so let’s assume we have the bare minimum—two play­ers. The result­ing mes­sage that goes out has 380 char­ac­ters, plus the 3 used by JSON for the array brack­ets and comma, giv­ing us a total of 383 char­ac­ters. Let us assume that our game runs at 30 steps per sec­ond on the server. We are trans­fer­ring 11,490 bytes every sec­ond per player. If we assume a rate of 18¢/GB of data trans­fer from our server provider, we have 0.683¢ per hour of gameplay.

Unfor­tu­nately, we didn’t set out to make a two player game. If we assume a max­i­mum of 12 play­ers per game, we see a cost of roughly 4.15¢ per player per hour, a lin­ear increase in cost per max player cap. This may prove pro­hib­i­tive, so what can we do about this?

Obser­va­tions

The data that the client receives from the server is not truly arbi­trary. We know to expect as many trans­form val­ues as there are play­ers. We also know that a trans­form value con­sists of exactly seven floating-point val­ues. We could rep­re­sent a trans­form with an array of seven num­bers that we would then process on the client to recre­ate the trans­form object. This slims our mes­sage down to 138 char­ac­ters, and our costs for 12 play­ers down to 3.02¢ per player per hour.

We imme­di­ately see that this is much less read­able. With­out con­text, this JSON string has very lit­tle meaning—the data is no longer struc­tured. This is a trade­off that we begin to see regard­ing opti­miz­ing the net­work traffic.

We also observe that dou­ble pre­ci­sion floating-point num­bers are rep­re­sented as up to 19 char­ac­ters as a human-readable string. In binary, these are only 8 bytes. How do we use this infor­ma­tion to our advantage?

A Solu­tion

We can start to drill down into the binary rep­re­sen­ta­tion of a floating-point num­ber using typed arrays. Here is what node.js shows on a little-endian machine:

In the under­ly­ing Array­Buffer, you can see the 1-byte chunks that com­prise the JavaScript num­ber, for a total of 8 bytes. We can then take each of these bytes, and con­vert them into a sin­gle char­ac­ter that rep­re­sents it.

This gives us a string of 8 char­ac­ters. If you are fol­low­ing along, you will see that the seri­al­ized string is extremely unread­able. In fact, chances are that there are con­trol char­ac­ters in the string. Hilar­ity may ensue if you have not dis­abled your ter­mi­nal bell and attempt to print out your net­work mes­sages (which may be arriv­ing 30 times a second).

Caveats

I have been using “char­ac­ters” and “bytes” almost inter­change­ably so far. How­ever, there is a very clear dif­fer­ence and it per­tains to char­ac­ter encod­ing. The key point here is that while JavaScript strings use UTF-16, Web­Sock­ets use UTF-8.1 UTF-8 is a variable-width encod­ing. Exam­i­na­tion of the spec­i­fi­ca­tion shows that code points less than 128 use a sin­gle byte, while code points from 128 to 2047 use two bytes. JSON will only pro­duce ASCII char­ac­ters, and will use one byte per char­ac­ter. How­ever, when encod­ing arbi­trary bytes, we also use char­ac­ters in the 128 to 255 range, which take up two bytes.

Con­clu­sion

When encod­ing the binary rep­re­sen­ta­tions of the val­ues into a string, the data from the trans­form given at the begin­ning of this post can be encoded in 82 bytes. We can also lose the JSON array struc­ture, and sim­ply encode a sin­gle byte up front with the num­ber of play­ers to expect in the rest of the string. This brings us down to roughly 1.78¢ per player per hour for 12 players.

In my next post, I’ll be cov­er­ing alter­nate ways of seri­al­iz­ing the data and dis­cuss send­ing binary data over the Web­Sock­ets protocol.


  1. This is not actu­ally true, as the hybi-07 draft of the Web­Sock­ets pro­to­col intro­duced the option for binary data trans­fer, but I am going to ignore that fact in this post.

Eric Li

Eric Li is a devel­oper at Gra­di­ent Stu­dios. He stud­ied com­puter graph­ics in school and spe­cial­izes in real­is­tic ren­der­ing tech­niques, but also has his hands in every­thing from net­work­ing to physics. When not wield­ing the pix­els, he is a foodie who enjoys trance and throw­ing Frisbees.

More PostsTwit­ter

This entry was posted in HTML5, Technology and tagged , by Eric Li. Bookmark the permalink.

About Eric Li

Eric Li is a developer at Gradient Studios. He studied computer graphics in school and specializes in realistic rendering techniques, but also has his hands in everything from networking to physics. When not wielding the pixels, he is a foodie who enjoys trance and throwing Frisbees.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>