Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Substantial performance penalty caused by polymorphic input adapter #1457

Closed
FrancoisChabot opened this issue Jan 25, 2019 · 18 comments · Fixed by #1950
Closed

Substantial performance penalty caused by polymorphic input adapter #1457

FrancoisChabot opened this issue Jan 25, 2019 · 18 comments · Fixed by #1950
Assignees
Labels
kind: enhancement/improvement release item: ⚡ improvement solution: proposed fix a fix for the issue has been proposed and waits for confirmation
Milestone

Comments

@FrancoisChabot
Copy link
Contributor

FrancoisChabot commented Jan 25, 2019

  • Describe the feature in as much detail as possible.

I ran a quick-and-dirty-ish devirtualization test by

  1. templating the lexer and parser on the input adapter subclass (defaulting to the base class)
  2. mark the input adapter subclasses as final
  3. Update the benchmark code to use the correct subclass directly

Before:

ParseFile/jeopardy            1024201192 ns 1022001731 ns          1   51.8404MB/s
ParseFile/canada                55346558 ns   55270439 ns         11   38.8412MB/s
ParseFile/citm_catalog          18555197 ns   18534212 ns         36    88.873MB/s
ParseFile/twitter                8912834 ns    8901508 ns         75   67.6581MB/s
ParseFile/floats               436844896 ns  436478028 ns          2   49.5332MB/s
ParseFile/signed_ints          320553309 ns  320362389 ns          2   72.5731MB/s
ParseFile/unsigned_ints        303152901 ns  302953363 ns          2   76.8034MB/s
ParseFile/small_signed_ints    169066171 ns  168948784 ns          4   69.9311MB/s
ParseString/jeopardy           795351806 ns  794811339 ns          1   66.6586MB/s
ParseString/canada              54493790 ns   54445449 ns         12   39.4297MB/s
ParseString/citm_catalog        19724418 ns   19707415 ns         36   83.5822MB/s
ParseString/twitter              9150619 ns    9144419 ns         77   65.8608MB/s
ParseString/floats             421472923 ns  421149121 ns          2   51.3361MB/s
ParseString/signed_ints        306999160 ns  306754574 ns          2   75.7924MB/s
ParseString/unsigned_ints      297069396 ns  296783050 ns          2   78.4002MB/s
ParseString/small_signed_ints  165137695 ns  164944718 ns          4   71.6287MB/s

After:

ParseFile/jeopardy             959820102 ns  958613968 ns          1   55.2684MB/s
ParseFile/canada                53412539 ns   53372580 ns         11   40.2223MB/s
ParseFile/citm_catalog          17567669 ns   17552345 ns         40   93.8444MB/s
ParseFile/twitter                8587726 ns    8581012 ns         80    70.185MB/s
ParseFile/floats               419401114 ns  419091029 ns          2   51.5882MB/s
ParseFile/signed_ints          315569793 ns  315206557 ns          2   73.7601MB/s
ParseFile/unsigned_ints        312478482 ns  312195920 ns          2   74.5296MB/s
ParseFile/small_signed_ints    157348675 ns  157226418 ns          4    75.145MB/s
ParseString/jeopardy           765028598 ns  764315312 ns          1   69.3183MB/s
ParseString/canada              52469064 ns   52414274 ns         13   40.9577MB/s
ParseString/citm_catalog        18649653 ns   18632984 ns         37   88.4018MB/s
ParseString/twitter              8899843 ns    8890877 ns         79    67.739MB/s
ParseString/floats             398706517 ns  398208512 ns           2   54.2936MB/s
ParseString/signed_ints        296954004 ns  296550211 ns          2   78.4005MB/s
ParseString/unsigned_ints      288880534 ns  288440238 ns          2   80.6678MB/s
ParseString/small_signed_ints  157066249 ns  156828785 ns          4   75.3355MB/s

Which represents a 3-10% speedup across the board.

  • Include sample usage where appropriate.

Once correctly implemented, this should not cause any API change whatsoever.

Is this something worth pursuing? If there's interest, I'll draft a clean PR for it.

@nlohmann
Copy link
Owner

Yes, such a PR would be greatly appreciated!!!

@FrancoisChabot
Copy link
Contributor Author

I've just spent a bit of time working on this, and I've hit a pretty big snag: Making the json.parse({ptr, len}) syntax work is proving to be a massive pain.

In c++17, with template deduction guides, it would be much more straightforward. But for c++11, I think the best I could do is create a few specific overloads for common use-cases like parse(string), parse(istream&) etc..., and let the current polymorphic code take care of the rest...

@nickaein
Copy link
Contributor

This might be unrelated LALR parsers has been been suggested in contribution guideline but it is probably outdated (e.g. the parser is not recursive anymore).

Can re-implementing the parser in some other form help to improve the performance? What is the reason to believe they can the improve the performance compared to current hard-crafted parser? Are they theoretically more efficient than current parser or it is a matter of chance/code generation that we end up with a faster parser?

@nlohmann
Copy link
Owner

Yes, the comment is outdated. The parser now is not recursive any more. I used parser generators like bison in the past and I did not like the fact that this library parsed JSON in such a "manual" way. I guess now parsing performance could be rather improved by avoiding copies or allocations.

@stale
Copy link

stale bot commented Apr 9, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label Apr 9, 2019
@stale stale bot closed this as completed Apr 16, 2019
@abrownsword
Copy link

Has any more attention been put into this? I was running some profiles recently, and the polymorphic calls to the input/output adapters was showing up on the profile higher than I would like.

@nlohmann
Copy link
Owner

No unfortunately not. I will reopoen and hope for a PR.

@nlohmann nlohmann reopened this Feb 15, 2020
@stale stale bot removed the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label Feb 15, 2020
@FrancoisChabot
Copy link
Contributor Author

FrancoisChabot commented Feb 18, 2020

My PR (for the input part of the problem) is still almost ready to go. The remaining problem is still supporting json.parse({ptr, len}).

The issue is that json.parse() needs to be templated on its input parameters, but brace-enclosed lists cannot participate in template resolution.

Edit: I've managed to work around the issue without reverting to the polymorphic interface by adding a new overload to the main API functions, relying on the fact that the parse({A, B}) syntax only ever operates on contiguous containers.

@FrancoisChabot
Copy link
Contributor Author

FrancoisChabot commented Feb 19, 2020

For reference, in my opinion, a nicer fix would have been to deprecate the bracket enclosed syntax, and add the two-parameter overload to parse() in order to match the from_*() functions. Agressively relying on implicit casting into a detail type like the current interface is not exactly great in the first place.

@abrownsword
Copy link

Can we add the overload function even if not deprecating the old one? As a faster path for those who care?

@FrancoisChabot
Copy link
Contributor Author

FrancoisChabot commented Feb 19, 2020

My old comment about having a fast path vs a slow path does not apply to my current PR. All code paths are "fast" while maintaining the current API, it's just that the user-facing interface is a little clumsy if you look at it from too close.

@abrownsword
Copy link

Yeah, I'm not a fan of that pattern. Okay, thanks. Would be nice to get this into the library. :)

@bazineta
Copy link

We've been testing this on our very busy implementation (JSON parsing time is about 20% of the total profile), and found it to do exactly as advertised; noticeable boost in performance, no code changes required. Would indeed be nice to see this PR accepted.

@bazineta
Copy link

bazineta commented Mar 2, 2020

One report; we're in the process of moving from clang 7.0.1 to 9.0.1 here. Apropos to this change, while 7.0.1 compiled clean, we see the following warning with 9.0.1:

In file included from Version.cpp:14:
nlohmann/json.hpp:4004:27: warning: explicitly defaulted move assignment operator is implicitly deleted [-Wdefaulted-function-deleted]
    input_buffer_adapter& operator=(input_buffer_adapter&&) = default;
                          ^
nlohmann/json.hpp:4021:23: note: move assignment operator of 'input_buffer_adapter' is implicitly deleted because field 'limit' is of const-qualified type 'const char *const'
    const char* const limit;
                      ^
1 warning generated.

And that's valid; the const data member prevents the type from being movable.

@FrancoisChabot
Copy link
Contributor Author

Good catch. It's unfortunate that no test caught this, despite coverage staying at 100%...

@bazineta
Copy link

bazineta commented Mar 3, 2020

The latest update resolves the warning for me on the 9.0.1 compiler

@stale
Copy link

stale bot commented Apr 2, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label Apr 2, 2020
@nlohmann nlohmann removed the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label Apr 5, 2020
@stale
Copy link

stale bot commented May 6, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label May 6, 2020
@stale stale bot closed this as completed May 13, 2020
@nlohmann nlohmann reopened this May 13, 2020
@stale stale bot removed the state: stale the issue has not been updated in a while and will be closed automatically soon unless it is updated label May 13, 2020
@nlohmann nlohmann linked a pull request May 13, 2020 that will close this issue
4 tasks
@nlohmann nlohmann self-assigned this May 13, 2020
@nlohmann nlohmann added kind: enhancement/improvement release item: ⚡ improvement solution: proposed fix a fix for the issue has been proposed and waits for confirmation and removed state: waiting for PR labels May 13, 2020
@nlohmann nlohmann added this to the Release 3.8.0 milestone May 13, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind: enhancement/improvement release item: ⚡ improvement solution: proposed fix a fix for the issue has been proposed and waits for confirmation
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants