I have web applications with JavaScript business objects coming from constructors, like:
function Animal() { this.type = null; // 'Cat' this.color = null; // 'Black' this.legs = 0; // 4 this.carnivore = false // true }
These objects may be created on the web, but quite quickly this happens:
- Client
- creates an Object
- serializes Object with JSON.stringify()
- The JSON text is sent over the network from client to the server
- Server
- uses JSON.parse() to get the object back
- validates the object
- stores it (perhaps by doing JSON.stringify())
- Client (perhaps the same) asks the server which sends it over the network
- Client
- uses JSON.parse to get the object back
- keeps the object in memory for application logic use
This works fine! If you do not add prototypes/functions to your constructors (like Animal.prototype.eat()) the client can use the object it got from JSON.
Since I send millions of such objects in this way over the network every day, I can’t help asking myself if what I do is reasonably efficient, or rather wasteful?
One idea I have had is that for this purpose I could turn Objects into Arrays (when shipping over the network, or perhaps storing them), like:
// As Object // As Array { [ type : 'Cat', 'Cat', color : 'Black', 'Black', legs : 4, 4, carnivore : true true } ]
Another idea has been to create an object with “new Animal()” rather than using the raw Object I get from JSON.parse().
Possible benefits could be
- Arrays are smaller to store on disk
- Arrays are smaller to send over the network
- A real Animal Object may be stored more efficiently in client RAM than the raw Object
- A real Animal Object may be faster to operate on, in the client, than the raw Object
So rather than just sending, receiving and processing raw JSON, I could be sending and receiving Arrays, and create objects using their constructors.
Test Results
I implemented some Node.js code to test my ideas. I was using objects like:
// As Object // As Array { [ "theWorst":0.1560387568813406, 0.1560387568813406, "lowerQuartile":0.2984895507275531, 0.2984895507275531, "median":0.47865973555734964, 0.47865973555734964, "higherQuartile":0.7832137265963346, 0.7832137265963346, "theBest":0.8893834668143412 0.8893834668143412 } ]
When there is a memory range (below), the low value is after the GC has run, and the high value is the peak value. JSON means an object received from JSON.parse. Object means an Object created with a constructor.
Intel i7-8809G | RAM/Disk | CPU |
1M Arrays | 94MB | |
-> gzip | 43MB | 7.7s |
-> gunzip | 1.1s | |
1M Objects | 154MB | |
-> gzip | 48MB | 5.6s |
-> gunzip | 1.3s | |
Receive & Convert data | ||
Arrays->Arrays | 100-240MB | 0ms |
Arrays->Objects | 76-240MB | 334ms |
JSONs->JSONs | 123-310MB | 0ms |
JSONs->Objects | 76-382MB | 280ms |
Access & Use data | ||
Arrays | 21ms | |
JSONs | 25ms | |
Objects | 9-11ms |
Well, I find that:
- It is surprising that GZIP is more expensive on the smaller array than the larger object file.
- Costs (CPU) to compress/decompress much higher (~10x) than the cost of “packing/unpacking” JSON-data in JavaScript code.
- If we are using gzip for network traffic the benefit of sending the more compact arrays rather than the more wordy objects, is questionanable (higher CPU cost, 10% smaller end result).
- Arrays like this require basically the same amount of RAM in Node.js as disk space.
- Objects like this require less RAM in Node.js than the corresponding JSON file.
- Both when it comes to RAM usage and performance on the client side, Arrays are better than raw JSON objects, but worse than real objects from a Constructor.
- Unless an object is used many times on the client (10+) it is not worth it from a strict performance perspective to make it with its constructor, instead of the raw JSON.
When it comes to the different strategies I thus find:
IO/Stored format | JavaScript format | Conclusion |
Array | Array | animal[1] or animal[COLOR] (where COLOR is a global constant) is generally not acceptable compared to animal.color. And it is not justified from performance perspective either. |
Array | JSON | This would not happen |
Array | Object | Given the extra cost of gzip, and the significant complexity of serializing/deserializing, this is hardly a good general strategy. It requires the least disk space, the least network traffic, and the least RAM on the client though. |
JSON | Array | This would not happen |
JSON | JSON | This is the most simple, readable way of doing things, at a surprisingly low overhead. You can not use prototype though. |
JSON | Object | If you find a simple and reliable way to create objects with a construtor and populate it from a JSON object, this method is probably best for performance and efficiency. |
Conclusion
JSON is very simple and using it thoughout your full stack is very productive. Unless you really need to optimize things, write your code for pure raw JSON objects.
0 Comments.