Parsing Gigabytes of JSON per second
By: Rajat Srivastava
Tags: Json, Programming, Efficiency, Parsing, Research

JSON is a popular language-independent data exchange format. It is supported by many database systems and data science frameworks. Also, several document-oriented databases are centred around JSON.

JSON has four primitive types (number, boolean, string, null) that can be embedded within composed types (arrays and objects).

An object is a series of key-value pairs surrounded by braces. An array is a list of comma-separated values surrounded by brackets.

JSON Object: { “name”: “Bill Gates”, “age”: 63 } .

JSON Array: [ 123, null, “abc ] .

JSON objects or arrays can contain primitive types or arbitrarily deeply nested objects/arrays as their values.

JSON example

To read a JSON document, software needs to transform the JSON text into a tree-like structure. This process is called JSON parsing. Parsing large JSON documents is a common but heavy-duty task.

Big-data applications can spend 80–90% of their time parsing JSON documents.

JSON parsing is an intensive process as it involves error checking like:

Objects must start and end with a brace.

Arrays must start and end with a bracket.

Objects must be made of comma-separated pairs of values where all keys are strings.

Outside of string values, only a few ASCII characters are allowed. Within string values, several characters must be escaped.

Numbers must follow the specification and fit within a valid range.

It requires that documents use a Unicode character encoding.

To accelerate processing, we should use our processors as efficiently as possible.

Popular processors (Intel, ARM, AMD) support single-instruction-multiple-data (SIMD) instructions. Hence, on x64 processors, we can compare two strings of 32 characters in a single instruction. We can make use of SIMD instructions to locate significant characters in a JSON document (eg, “ , = ) using a few instructions. Application of SIMD instruction can be referred to as vectorization. Code that generates fewer instructions is faster. Usually, vectorized software uses fewer instructions than conventional software.

Whenever the processor chooses between two code paths, there is a risk of incurring several cycles of penalty, due to a wrongly predicted branch on current pipelined processors. This is referred to as branching.

SIMD instructions combined with minimal branching can lead to new speed records for JSON parsing.

This article summarizes the design and implementation technique of simdjson JSON parser based on this research paper. The simdjson parser in question is implemented in C++ language but the architecture can be implemented in other languages as well.

How fast can this parser be?

The performance of simdjson compared to other JSON parsers is multifold. As you can see from the table, simdjson can achieve speeds of up to 2.2 GB/s on the twitter.json file while others are unable to cross the 1 GB/s speed.

It performs well on a variety of JSON inputs.

Here’s the performance, against the closest C/C++ competitors, on a range of inputs on an Intel Skylake processor (an i7–6700 running at 3.4 GHz in 64-bit mode).

Why is it fast?

The main advantage of this parsing architecture lies in Stage 1. At this stage, it detects the location of structural and pseudo-structural characters from the input. This system operates with large inputs at once and takes full advantage of SIMD operations to perform faster processing. Although the application of SIMD instructions for parsing is not novel, it is underutilized in popular JSON parsers.

Parsing architecture

Most parsers use a top-down recursive single pass technique. This parsing technique uses two distinct passes and at the end of the two stages, all strings are normalized, all numbers have been parsed and validated, and validity check of JSON document is done as well.

The first stage operates directly on the input, processing the data in batches of 64 bytes, making full use of SIMD instructions which is the key to its good performance. It deliberately delays number and string validation to the next stage.

Stage 1 — Structural and Pseudo-Structural Elements

This stage identifies the key points in the input — the structural characters of JSON (braces, brackets, colon and comma), the start and end of strings and other JSON atoms ( true , false , null and numbers). Following are the steps performed at this stage:

Identification of the quoted substrings

This step focuses on identifying strings and not confusing the quotes surrounding the string with the quotes that are meant to be part of the string.

The parser finds all the quote characters ( “” ) and odd-length sequences of backslashes (that will result in escaping the subsequent character). The string inside the quotes is then detected, by filtering out escaped quote characters.

For eg: [ “That was a \“LOL\” moment” ]

The parser will not confuse with quotes around the word LOL because those quotes are escaped using the backslash.

Identification of structural characters

The parser uses SIMD comparison to create a lookup table. This allows it to separate the characters into one of the two categories:

Structural characters ( {} , [] , : and , ). White-space characters.

The algorithm of this step uses only two SIMD instructions and a few logical instructions. No branching is required which makes this parser way faster than others.

Identification of Pseudo-Structural Characters

Pseudo-structural characters are non-white-space characters that are:

Outside quotes Have a predecessor that is either a white-space character or a structural character.

This step uses a feature of JSON: the legal atoms can all be distinguished from each other by their first character: t for true , f for false , n for null , 0–9 for numerical values.

As a side effect, identifying pseudo-structural characters helps the parser validate the input JSON document.

Index Extraction

While identifying all of the above elements and structural characters, the parser keeps on building indexes corresponding to each character. This indexing helps in creating a navigable tree of JSON elements in stage 2.

Character-Encoding Validation

This parser architecture assumes the input data to be UTF-8 character encoded across the whole input. The liberty to discard other encodings is taken because there is hardly any JSON document published out there which doesn’t follow UTF-8 character encoding.

Stage 2 — Building the Tape

At this stage, the parser iterates through the indexes found in the first stage. A goto-based state machine with a stack is used to handle objects and arrays that can be nested. It handles JSON element, number and string validation and, as it goes, constructs a navigable tree of JSON elements called a tape representation.

The JSON Tape — A tree of navigable JSON elements

After the two stages, a JSON input is parsed to a JSON tape which can be easily read by any software. All of this is done efficiently taking advantage of the processor architecture as much as possible.