/json_performance

Performance profiling of JSON libraries

Primary LanguageCMakeMIT LicenseMIT

json_performance

Performance profiling of JSON libraries

Latest results (November 4, 2022):

Library Roundtrip Time (s) Write (MB/s) Read (MB/s)
Glaze 1.47 987 689
simdjson (on demand) N/A N/A 1260
daw_json_link 3.21 308 464
json_struct 4.52 220 321
nlohmann 18.64 77 67

1,000,000 iterations on a single core (MacBook Pro M1)

Test object:

{
   "fixed_object": {
      "int_array": [0, 1, 2, 3, 4, 5, 6],
      "float_array": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6],
      "double_array": [3288398.238, 233e22, 289e-1, 0.928759872, 0.22222848, 0.1, 0.2, 0.3, 0.4]
   },
   "fixed_name_object": {
      "name0": "James",
      "name1": "Abraham",
      "name2": "Susan",
      "name3": "Frank",
      "name4": "Alicia"
   },
   "another_object": {
      "string": "here is some text",
      "another_string": "Hello World",
      "boolean": false,
      "nested_object": {
         "v3s": [[0.12345, 0.23456, 0.001345],
                  [0.3894675, 97.39827, 297.92387],
                  [18.18, 87.289, 2988.298]],
         "id": "298728949872"
      }
   },
   "string_array": ["Cat", "Dog", "Elephant", "Tiger"],
   "string": "Hello world",
   "number": 3.14,
   "boolean": true,
   "another_bool": false
}

ABC Test

In this test the JSON document has keys from "a" to "z", where each key refers to an array of integers from [0, 999]. The document orders the keys from "z" to "a", in reverse order to the expected "a" to "z" layout.

This test demonstrates problems with simdjson and iterative parsers that cannot hash for memory locations. If keys are not in the expected sequence performance seriously suffers, and problem increases as the size of the document increases.

Hash based solutions avoid this problem and do not suffer performance loss as the JSON document grows in size.

Library Roundtrip Time (s) Write (MB/s) Read (MB/s)
Glaze 3.77 691 412
simdjson (on demand) N/A N/A 116

Registration Comparison

Even with Glaze being the fastest, it is also the cleanest (without macros) for registration and automatic type deduction. Glaze doesn't require an additional to_json definition.

Glaze

template <>
struct glz::meta<obj_t> {
   using T = obj_t;
   static constexpr auto value = object(
      "fixed_object", &T::fixed_object,
      "fixed_name_object", &T::fixed_name_object,
      "another_object", &T::another_object,
      "string_array", &T::string_array,
      "string", &T::string,
      "number", &T::number,
      "boolean", &T::boolean,
      "another_bool", &T::another_bool
   );
};

Or using macros:

GLZ_META(obj_t, fixed_object, fixed_name_object, another_object,
         string_array, string, number, boolean, another_bool);

daw_json_link

template<>
struct daw::json::json_data_contract<obj_t> {
  using type = json_member_list<
   json_class<"fixed_object", fixed_object_t>,
   json_class<"fixed_name_object", fixed_name_object_t>,
   json_class<"another_object", another_object_t>,
   json_array<"string_array", std::string>,
   json_string<"string", std::string>,
   json_number<"number", double>,
   json_bool<"boolean", bool>,
   json_bool<"another_bool", bool>>;
   
   static constexpr auto to_json_data(const obj_t& v ) {
         return std::forward_as_tuple(v.fixed_object,
                                      v.fixed_name_object,
                                      v.another_object,
                                      v.string_array,
                                      v.string,
                                      v.number,
                                      v.boolean,
                                      v.another_bool);
   }
};

json_struct

JS_OBJ_EXT(obj_t, fixed_object, fixed_name_object, another_object, string_array, string, number, boolean, another_bool);

Nlohmann JSON

void to_json(json& j, const obj_t& v) {
   j = json{{"fixed_object", v.fixed_object},
            {"fixed_name_object", v.fixed_name_object},
            {"another_object", v.another_object},
            {"string_array", v.string_array},
            {"string", v.string},
            {"number", v.number},
            {"boolean", v.boolean},
            {"another_bool", v.another_bool}};
}

void from_json(const json& j, obj_t& v) {
   j.at("fixed_object").get_to(v.fixed_object);
   j.at("fixed_name_object").get_to(v.fixed_name_object);
   j.at("another_object").get_to(v.another_object);
   j.at("string_array").get_to(v.string_array);
   j.at("string").get_to(v.string);
   j.at("number").get_to(v.number);
   j.at("boolean").get_to(v.boolean);
   j.at("another_bool").get_to(v.another_bool);
}