Flutter JSON vs Protocol Buffer: Benefits and performance
As discussed in my last post on Flutter JSON decoding performance, we saw that parsing JSON on the main thread is usually the right choice for decoding data.
Today, we'll be looking at Protocol Buffers, Google's data serialization format. Protocol Buffers serves the same purpose as JSON, with the following differences:
- It is structured. You create
.proto
files which define the schema of the data, and generate code for encoding/decoding - It is a binary format. This means that it doesn't need to encode to a string (like JSON). For example, "123" can encode to 1 byte instead of 3.
Protocol Buffers claims to be faster to encode/decode and provide a small message size. Let's test this out in the context of Flutter.
The data
At first, I wanted to reuse the JSON messages from my previous post for direct comparison, but since Protocol Buffer schemas are stricter, some of the messages were impossible to convert to schemas (dissimilar objects in an array).
Instead, I've created 2 schemas (Short
& Long
), one which would be your typical API response, and one with more data (with some other types).
syntax = "proto3";
message Link {
int64 id = 1;
string text = 2;
string url = 3;
}
message Person {
int64 id = 1;
string name = 2;
}
message Short {
int64 id = 1;
string firstName = 2;
string lastName = 3;
repeated Link links = 4;
repeated Person friends = 5;
}
message Long {
Short short1 = 1;
Short short2 = 2;
Short short3 = 3;
bytes raw = 4;
string text = 5;
repeated float numbers = 6;
enum ContentType {
IMAGE = 0;
VIDEO = 1;
DOCUMENT = 2;
}
repeated ContentType contentTypes = 7;
}
We'll be using the following JSON representations for this version (with bytes
as base64):
Next, I created the Protocol Buffer messages with the same data (uploaded as base64):
Great. Now we'll set up our parsing. We'll be reusing code from the last post, and will be doing all parsing on the main thread. We'll also be using the minified JSON.
void parseJson() {
json.decode(jsonToParse);
}
void parseProtocolBuffer() {
Short.fromBuffer(shortProtocolBuffer);
// or
Long.fromBuffer(longProtocolBuffer);
}
We'll be using the same devices as our last post:
- OnePlus 8 Pro using a 865 (2020), in release mode
- Pixel 3XL using a 855 (2018), in release mode
- Local emulator running on a 9700K (3 dedicated CPUs), in debug mode (evaluated), because I'm curious
...and the same versions:
- Flutter: 1.19.0-1.0.pre
- Dart: 2.9.0-7.0.dev
To measure, we'll use the rough average of the output burst. This gives us a pretty good idea of the relative performance and excludes outliers.
Results
Device | Short JSON | Long JSON | Short Protobuf | Long Protobuf |
---|---|---|---|---|
OnePlus 8 Pro (865) | ~151 000 | ~15 500 | ~175 000 | ~19 200 |
Pixel 3XL (855) | ~99 000 | ~9 200 | ~115 000 | ~12 000 |
Desktop (9700K) | ~313 000 | ~32 200 | ~339 000 | ~35 800 |
Here, we see a clear performance win. We see a 15-23% win on the OnePlus and a 16-30% win on the Pixel.
Conclusion
Protocol Buffers definitely seem like a better option over JSON. Let's look over it again:
- ✓ Smaller size, with a 29.8-48.0% reduction in size compared in our examples.
- ✓ Better performance, with an average 21% speedup in parsing.
- ✓ Schema definition, allowing for static typing and easier backward-compatibility
- ✗ Harder to use, setting up the testing took me almost an hour. This is usually a one-time cost though.
My recommendation would be to use Protocol Buffers if you need (or want) better parsing performance and can accept the added complexity of Protobufs. You can learn more about them here, along with it's Dart tutorial.
About me
I am Cretezy, author of the redux_persist
,
flutter_linkify
,
and flutter_super_state
(and more!) libraries for Flutter/Dart.
Who am I?
Hi, I'm @Cretezy (also known as Charles), a software developer. I write about programming, personal projects, and more. I also make YouTube videos.
See more articles here, or view the home page