We benchmarked the latest JsonDataObjects library against SuperObject, standard DBXJson, dwsJSON, QDAC and mORMot.

We benchmarked the latest JsonDataObjects library against SuperObject, standard DBXJson, dwsJSON, QDAC and mORMot.
This is the fastest node-based JSON library around for reading.
Of course, a SAX approach is missing (as we do use in mORMot for even greater speed), but the Andreas' record-based storage and several optimization tricks in its code do wonders.
Only missing feature is IMHO a "path query" method.
Here are some numbers!
http://blog.synopse.info/post/2015/02/16/Benchmarking-JsonDataObjects-JSON-parser

Comments

  1. Nice job Andreas!

    On the path query syntax, if you ever do a shootout of path query, there is now a dwsJSONPath (100% tested but not much integrated right now)

    A. Bouchez  any link to the big test JSON? (did not see it in the article)

    ReplyDelete
  2. Alex Egorov XSuperObject has been tested in our previous post, but it was so slow and incompatible with SuperObject (we did not succeed using both libraries at once) that we did not include XSuperObject in this post.
    To summarize: XSuperObject = crossplatform but slower SuperObject.
    JsonDataObjects or our SynCrossPlatformJSON unit are much better choices for cross-platform JSON than XSuperObject.

    ReplyDelete
  3. A. Bouchez Thanks for your answer and your tests

    ReplyDelete
  4. Thanks for benchmarking it and pointing out the temporary string allocation for numbers in the writer. It should be easily possible to change that to a stack allocated char buffer (TextToFloat, CvtInt). This should even speed up the writer in single threaded mode.

    ReplyDelete
  5. A. Bouchez Thanks :)

    I see the big JSON is mostly stressing string -> float conversions, while the depth one is stressing basic parser & strings. Together they make a good benchmark balance.

    ReplyDelete
  6. Eric Grange Yes, I wanted to stress the libraries as much as I could, in addition to simple benchmark of small objects.
    BTW those external huge JSON files did reveal some issues with our own implementation, some time ago. :)

    ReplyDelete
  7. Thanks. If I may suggest - a table would be more readable for the benchmark statistics :)

    ReplyDelete
  8. A chart could also help, especially when one wants to get a quick idea about the rough performance figure.
    Nevertheless a good job, both of you!

    ReplyDelete
  9. Andreas Hausladen Yes a local buffer is always better than a memory allocation. Also consider writing the content directly as UTF-8, if you can.
    In the tests, I was not able to keep the generated JSON (with formatting) in memory, due to the UTF-16 overhead.
    But you did a great work: your unit is the fastest for DOM/node oriented JSON process. Eric Grange 's dwsJSON was the closer, but not cross-platform, and more memory consuming.
    Our variant-based units (also cross platform) are slower than your optimized record-based value storage.
    But a SAX approach gives the best results: when reading a huge array of JSON objects (as we do for our ORM), our TTestTableContent.SynopseTableCached method is 5 times faster than TTestTableContent._JsonDataObjects. But this is a special case - even if pretty common for our ORM.

    ReplyDelete
  10. I like XSuperObject for this feature:
    var
      Rec: TTestRec;
      S: string;
    begin
      Rec := TSuperRecord.FromJSON('JSON string');  
      S := TSuperRecord.AsJSON(Rec);
    end;
    Which parsers from your list have the same functionality (and crossplatform)?

    ReplyDelete
  11. Yes, for streaming SAX is the way to go.

    DOM shines for browsing/searching/querying/creating complex non-linear JSON. dwsJSON boxes everything, including values, hence the higher memory usage and lower performance. On the other hand that allows to browse through non-existent branches and paths in a JSON without having to pepper the code with existence tests.

    ReplyDelete
  12. Alex Egorov Direct record loading is supported by SuperObject and mORMot's SynCommons.pas (with much higher speed for the 2nd) but I may be missing some others.
    No crossplatform JSON library  supports this form yet, AFAIK.

    ReplyDelete
  13. I am absolutely stunned by the performance of both mORMot JSON and Andy's JsonDataObjects!

    ReplyDelete
  14. I wonder how these numbers compare with the fastest libraries available for languages other than Delphi

    ReplyDelete
  15. Update: ToUtf8JSON is now available and the "beautifier" should use a lot less memory.

    ReplyDelete
  16. Erik Salaj You can try to add it to the sample source in our framework, if you wish. But I would not spend my time investigating a non Open Source library.

    ReplyDelete
  17. Erik Salaj Arnaud has produced a superb benchmarking suite that will allow you to compare the performance of your code with others. We'd certainly be interested to know how you get on.

    ReplyDelete
  18. Andreas Hausladen Nice!
    The "beautifier" does not use a lot less memory (just 10%), but is noticeably faster: from 7 seconds to 5 seconds (our SAX based version does it in 1 second, preserving the floating point precision, whereas you are rounding everything to double).
    In practice, your new ToUTF8Json() is slower (107 ms) than calling ToJSON then our very optimized StringToUTF8() function (91 ms).
    BTW I am doubtful that your TJsonOutputWriter class will work when a UTF-16 surrogate appears between two internal buffers: a flush between a UTF-16 surrogate pair would lead into wrong UTF-8 generation. It may be very rare, but may happen AFAIK from your current interpretation.
    In all cases, RTL's TEncoding is not very optimized for speed, not easy to work with.

    ReplyDelete
  19. The UTF16 surrogates are no problem because the buffer flush is only done after a whole string was written to it. It doesn't make a hard cut at 128K bytes (64K WideChar). It flushes the buffer after the last Append() call that caused it to grow beyond 64K WideChars.

    For the beautifier's memory consumption I need to find out where that memory goes, but the beautifier isn't high priority for me.

    I doubt that I can ever beat a SAX parser with a Node based parser :-).

    ReplyDelete
  20. About the beautifier's memory consumption. In the JsonDataObjectsBeautifier method you destroy the TJsonObject after measuring the memory consumption and you also keep the UTF8 "json" string alive. While in SynopseBeautifier you clear the "json" variable before measuring the memory consumption and you don't have a JSON object.

    My last commits reduced the memory consumption during the string-creation. Before it used a TStringList and now is uses my TJsonStringBuilder.
    Maybe I should write a real beautifier (which would be a SAX parser).

    ReplyDelete
  21. Andreas Hausladen​ yes we measure the memory used, so the whole tree is part of it.
    What happens for a string bigger than 64kb (e.g. base64 encoded)?

    ReplyDelete
  22. JsonDataObject will reallocate the buffer to that size. It only writes complete strings, no truncation happens.

    ReplyDelete
  23. Andreas Hausladen Reallocating a huge buffer sound like always a wrong idea, IMHO.
    But congrats for the overall performance!

    ReplyDelete

Post a Comment