simdjson / simdjson/simdjson

Use bitstack for depth array

Open
#1,102 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

performance research
Dominant language
C++
Stars
24.3k
Forks
1.3k
Avg merge
1d 3h
Merged PRs (30d)
16

Description

Right now we maintain a stack of booleans to find out whether we are in an array or an object. This means that on every [ { } ] we incur a write or read. It is generally only used to decide where to branch; so it's only relevant when the branch might be mispredicted (particularly on read). This happens in the scope_end label, and means a 3 cycle delay before we know if the branch is wrong. We could save space by storing these as bits instead of bytes, but size isn't the problem.

We could, however, store it as bits and keep the top 32 or 64 bits of the bit stack in a register, making the check very fast (<< 1 | is_array to push, >> 1 and check the overflow bit to pop). We can steal the register from depth, in fact: when it's needed, you can calculate depth by keeping a 1 "marker bit" at the top of the bitstack: depth is then 32 - leading_zeroes(bitstack) - 1. We can check if we have exceeded the register's capacity if the << 1 carries the high bit out or if the >> 1 yields 0 (because we shifted out the high bit).

When you start a JSON array, I'd imagine assembly like this:

shl <BitStack>, 1 ; push
jc store_stack ; Size exceeded: push <BitStack> into memory and repeat.
or <BitStack>, 1  ; is_array = true

When you pop, I'd imagine assembly like this:

shr <BitStack>, 1 ; pop and check the parent
jz read_stack ; pop memory into <BitStack> and repeat. If nothing left to read, goes to document_end.
jc array_continue ; if the bottom bit was 1 (is_array = true), we're in an array
jmp object_continue ; if the bottom bit was 0 (is_array = false), we're in an object

This also has the nice property that you only have to check for depth == 0 and max_depth when you run out of bits. 99% of JSON files have shallow nesting and will only ever have to check depth == 0 once, at the very end. The overflow branches are 100% predictable for most files, therefore.

Does this data structure exist that anyone knows of? I was trying to find it but couldn't see anything. I also haven't figured out how to make C++ generate the above assembly. It particularly doesn't have a way to give you the overflow, and the naive "read the bit I want before shifting it out" didn't produce the assembly I wanted.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating the boolean depth stack and the scope_end label mentioned in the issue, then inspect how depth and array/object branching are currently maintained. Compare a register-backed bit stack with the existing implementation and verify the generated assembly and parser behavior; done means the proposed optimization works without breaking nesting or depth handling.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
performance
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.