Enhancement: Multi-threading deserialization
- Dominant language
- Java
- Stars
- 24.2k
- Forks
- 4.5k
- Avg merge
- 6d 4h
- Merged PRs (30d)
- 12
Description
# **TL;DR:**
Instead of deserializing in one thread, kick off subtasks to parse big objects on separate threads and then merge everything back together
# **Introduction:**
Over the past week I've been struggling with optimizing slow deserialization of big (27k+ lines) JSON file on relatively low-end device (FireTV Stick Gen 2).
Overall structure of my POJO is something like this:
```java
Response {
List tiles;
}
Tile {
String id;
//10 more fields, including strings, long, List, etc
Asset asset;
}
Asset {
//40 different fields, including strings, long, List, other nested classes
}
```
Parsing json into `Asset` object takes most of the time. If removed from `Tile` - then total time needed goes down from **700ms** to ~**100ms**. But, obviously, removing data is not a solution 😄
While trying out different approaches I found out that splitting raw json string into substrings and deserializing those into `Tile` objects in parallel gives a significant boost - total time went down from **700ms** to ~**350ms**. I had total of 12 tiles, so those got paralleled pretty nicely.
The downside of approach I took is that it's hacky and unreliable 😢
So, I started digging into implementation of `JsonReader`, thinking whether it is possible to come up with more reliable mechanism of parallelizing work without sacrificing the reliability. What I'm going to propose below might be completely impossible to do or sound too ambitious, but please give it a thought.
# **Multi-threading API**
User can be able to annotate fields in his POJOs that he wants to parse in parallel. It is on user to decide whether it worth using this experimental API. New annotation that can be used is called, let's say `@Parallel`:
```java
Tile {
String id;
//10 more fields, including strings, long, List, etc...
@Parallel
Asset asset;
List categories;
....
}
```
This annotation will tell `JsonReader` (or some other component) that when deserializing json into `Tile` object and stumbling upon an `Asset` chunk - this is what should be done instead of regular parsing:
1. Json data, that was supposed to be converted into `Asset` should be stored as one chunk in separate buffer.
2. Main thread that does parsing skips that json chunk, as if `Tile` object didn't had a field named `asset`.
3. Main thread remembers that he skipped a field and makes a note of it somewhere, assigning `null` for that field for now.
4. A separate task should be launched or another thread (thread pool may be defined by amount of CPUs available - 1 (main thread)) to deserialize that json chunk in `Asset` object.
5. Meanwhile main thread continues working through his data stream kicking off new tasks if needed.
6. Once subtask for `Asset` is completed the object is used to replace previously set `null`.
After main thread completed his work it waits for any other subtasks it kicked off (if any) and only then returns the object.
The main doubt I have here is whether actually skipping the json chunk and parsing it separately will give the required boost. But as I mentioned before - commenting away `asset` field gave a visible improvement, so I assumed that deserializing data into object is definitely slower than just skipping over it. I would be happy to hear opinion of people familiar with details of implementation of gson on this topic. Thanks!
Contributor guide
Assessment
This issue has not been assessed yet.