Skip to content
This repository has been archived by the owner on Oct 17, 2022. It is now read-only.

spawnfest/json_native

Repository files navigation

Jason.Native

This is an extension of the Jason package - the blazing fast JSON encoder for Elixir.

This package adds a "sprinke of NIFs" to Jason to make it even faster. All you need to do, is just add jason_native as a dependency to your project, and that's it! No code changes are required, things just will be faster.

This is achieved by making jason_native an optional dependency of jason, and switching implementations at compile-time based on the presence (or absence) of this optional library. This supports all features of Jason including the Jason.Encode protocol.

NIFs are implemented respecting all constraints of the BEAM, including periodic yielding to allow scheduler threads to re-schedule processes.

Currently, Jason.Native only adds native routines for encoding strings - one of the most expensive routnes in encoding JSON. In microbenchmarks, this speeds up Jason.encode! about 1.5 times for most inputs, and places it between 30% slower and 40% faster compared to jiffy, which is fully implemented in C. For some specific inputs - e.g. large amounts of Unicode or extremely long strings, Jason.Native is significantly faster - up to 8x faster than regular pure-Elixir Jason and 6x faster than pure-C jiffy.

Project contributions

Beyond the immediate contributions of this project in making Jason faster, with this project, I'd like to promote the "sprinkle of NIFs" technique for optimising Elixir & Erlang libraries. By providing 2 identical implementations: Elixir-based and NIF-based, with an easy & transparent way to switch, projects can achieve both - stability & great performance.

To make this work, only a tiny patch to the Jason library was required. You can find it here, and it's part of the 1.5.0-alpha.1 release.

Installation

The package can be installed by adding jason_native, alongside jason to your list of dependencies in mix.exs:

def deps do
  [
    {:jason, "~> 1.5.0-alpha.1"},
    {:jason_native, "~> 0.1.0"}
  ]
end

Other than Elixir 1.14 and compatible Erlang, this package requires:

  • make
  • A C++20-compatible C++ compiler, e.g. clang or gcc.

Please refer to Makefile to supported options for native compilation.

Full documentation can be found at https://hexdocs.pm/jason_native.

Disclaimer

This package uses NIFs - native functions implemented in C++. I made all efforts to implement things correctly and test them, but native code is tricky. This can cause instability in your systems, please test before deploying in production to your entire fleet of machines.

Benchmarks

Benchmarks can be found in the main Jason library. Please refer to instructions included there.

Basic results from my machine can be found in this gist.

Future work

Similar technique - "sprinkle of NIFs" should be applicable to the parsing-side of Jason. In particular, once again, parsing strings. I believe similar, if not significicantly bigger performance improvements could be achieved.

Furthermore, I believe some "to string" routines inside of the BEAM could be considerably improved, in particular: integer_to_binary and <<_::utf8>> binary patterns.

FAQ

Why not Rust?

I'd love to write this in Rust, rather than C++ and rustler is an awesome project. In fact, I started with a Rust implementation. Unfortunately, rustler doesn't support enif_schedule_nif API, which is crucial for implementing well-behaved CPU-intensive yielding NIFs. Therefore, C++ it is.

License

Jason.Native is part of Jason and is released under the Apache License 2.0 - see the LICENSE file.

Jason.Native contains unicode-encoding routines with Copyright (c) 2008-2009 Bjoern Hoehrmann bjoern@hoehrmann.de under the MIT license, see http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.