Skip to content

object::extract: get a bunch of keys in one go#2247

Open
the-moisrex wants to merge 18 commits intosimdjson:masterfrom
the-moisrex:extractor
Open

object::extract: get a bunch of keys in one go#2247
the-moisrex wants to merge 18 commits intosimdjson:masterfrom
the-moisrex:extractor

Conversation

@the-moisrex
Copy link
Member

@the-moisrex the-moisrex commented Sep 12, 2024

This is the bare minimum implementation of this idea.

This makes this syntax possible:

    Car car{};
    error = obj.extract(
      to{"wheels", sub{
        to{"front", car.wheels.front},
        to{"back", car.wheels.back},
      }},
      to{"make", car.make},
      to{"model", car.model},
      to{"year", [&car](auto val) {
        car.year = val;
      }});
    if (error) {
      return error;
    }

Its performance is better than #2235, and we're pretty much on par with the traditional way of doing this; but still there are things we could do as well to make it even faster.

	findall_consume                              :  0.14821 GB/s  
	findall_consume_sv                           :  0.14996 GB/s  <-- object::find_all
	standard_consume                             :  0.30775 GB/s  <-- traditional way
	findall_extract                              :  0.30347 GB/s  <-- this PR

I tried to make to{...} unnecessary, but it doesn't seem to work. If you have a way that the user wouldn't need to keep typing to would be great.

This is the bare minimum implementation of this idea.
@the-moisrex the-moisrex changed the title object::extract: get a bunch of values together object::extract: get a bunch of keys in one go Sep 12, 2024
@lemire
Copy link
Member

lemire commented Sep 12, 2024

@the-moisrex Don't worry about the fuzzer failing, it seems like an obsolete config.

@lemire
Copy link
Member

lemire commented Sep 12, 2024

Exciting work.

@the-moisrex
Copy link
Member Author

Here are some ideas, @lemire what do you think?

    tweet t;
    tweet_value.extract(
      to{"created_at", t.created_at},
      to{"id", t.id},
      to{"text", t.result},
      to{"user", sub{
          to{"id", t.user.id},
          to{"screen_name", t.user.screen_name}
      }},
      to{"aliases", range(1, 4, aliases.begin())},
      to{"array", range(arr.begin(), arr.end())},
      to{"array2", range(1, 3, back_inserter(vec))},
    );

@lemire
Copy link
Member

lemire commented Sep 12, 2024

@the-moisrex I generally like the sub idea as it simplifies a pain point.

@the-moisrex
Copy link
Member Author

Added the sub idea + fixed noexcepts + made endpoints "callables" that have a .key().

Now this is working:

    tweet_value.extract(
      to{"created_at", t.created_at},
      to{"id", t.id},
      to{"text", t.result},
      to{"in_reply_to_status_id", [&](auto value) {
        t.in_reply_to_status_id = nullable_int(value);
      }},
      to{"retweet_count", t.retweet_count},
      to{"favorite_count", t.favorite_count},
      to{"user", sub{
        to{"id", t.user.id},
        to{"screen_name", t.user.screen_name}
      }});

@the-moisrex
Copy link
Member Author

the-moisrex commented Sep 13, 2024

image
I'm pretty sure it's a msvc bug; I fix it by in-class definition, gcc breaks due to object_iterator is not implemented.

I had to do this ridiculous thing.

namespace ondemand {

#if defined(__cpp_concepts) && defined(__cpp_consteval)
#define SIMDJSON_SUPPORTS_EXTRACT 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be enabled only when SIMDJSON_EXCEPTIONS is true? Systems such as Node.js use simdjson, they compile with C++20, but they disable exceptions (at the compiler level). All of the simdjson library is designed to work well with both exceptions and no exception. It would be ok to have features requiring exception support, although this should be explicit.

For reference, here is how it uses in Node.js (without exceptions)
https://github.com/nodejs/node/blob/b887942e6b430e5f28ea7cb6c43841730161641c/src/node_modules.cc#L137

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We wouldn't need to specify that. Other than the one that you mentioned above (which is an easy fix), in extract, any type of error should be silently ignored; all the noexcept business is for the user's own exceptions (inside lambdas for example) not invalid json inputs.

@lemire
Copy link
Member

lemire commented Sep 13, 2024

I had to do this ridiculous thing.

I am not too worried about such issues, we can always figure it out in the end. :-) Let us make sure that we have the right design before worrying about a specific compiler.

@the-moisrex
Copy link
Member Author

@lemire Is it a good idea to rename extract to on to make it more clear that it's event-like member function call?

@the-moisrex
Copy link
Member Author

For handling of errors, we could make endpoints operator() return error_code, or we could make to have an optional error handling callback, or we could have an optional callback for extract itself.

@lemire Which one do you prefer?

I think the first one looks better, but it does mean the user will lose the information on where the error occurred.

@lemire
Copy link
Member

lemire commented Sep 13, 2024

it does mean the user will lose the information on where the error occurred

We have to mindful of quality-of-life. If it becomes too hard to debug, it might be annoying to users.

I am neutral about how we go about it, we just have to have some way to debug the issue that is not insane. ❤️

@the-moisrex
Copy link
Member Author

@lemire I went with the first way. extract, to, and sub return error_code now and we bail-out on the first error found.

}});
if (error) {
return error;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lemire Now this should be equivalent.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

@lemire
Copy link
Member

lemire commented Sep 13, 2024

@the-moisrex I will dig into it soon.

I invite the entire community to help review.

@lemire lemire changed the base branch from master to builder_development_branch September 21, 2024 00:08
@lemire lemire changed the base branch from builder_development_branch to master September 21, 2024 00:10
@lemire
Copy link
Member

lemire commented Sep 21, 2024

@the-moisrex I have added the extractor to a few of our standard benchmarks, please see...

the-moisrex#1

@the-moisrex
Copy link
Member Author

the-moisrex commented Sep 21, 2024

@lemire
./benchmark/bench_ondemand --benchmark_filter=partial_tweets:

partial_tweets<simdjson_ondemand>/manual_time            1366963 ns      1380328 ns          510 best_branch_miss=11.944k best_bytes_per_sec=471.224M best_cache_miss=0 best_cache_ref=1038 best_cycles=7.36541M best_cycles_per_byte=11.6631 best_docs_per_sec=746.18 best_frequency=5.49592G best_instructions=24.8535M best_instructions_per_byte=39.3554 best_instructions_per_cycle=3.37435 best_items_per_sec=74.618k branch_miss=11.853k bytes=631.515k bytes_per_second=440.582M/s cache_miss=37.8157 cache_ref=1.1462k cycles=7.50829M cycles_per_byte=11.8893 docs_per_sec=731.549/s frequency=5.49268G/s instructions=24.8535M instructions_per_byte=39.3554 instructions_per_cycle=3.31014 items=100 items_per_second=73.1549k/s [BEST: throughput=  0.47 GB/s doc_throughput=   746 docs/s instructions=    24853514 cycles=     7365413 branch_miss=   11944 cache_miss=       0 cache_ref=      1038 items=       100 avg_time=   1366962 ns]
partial_tweets<simdjson_ondemand_extract>/manual_time    1328019 ns      1341717 ns          526 best_branch_miss=10.227k best_bytes_per_sec=488.184M best_cache_miss=3 best_cache_ref=591 best_cycles=7.1093M best_cycles_per_byte=11.2575 best_docs_per_sec=773.036 best_frequency=5.49574G best_instructions=23.3974M best_instructions_per_byte=37.0496 best_instructions_per_cycle=3.2911 best_items_per_sec=77.3036k branch_miss=10.2793k bytes=631.515k bytes_per_second=453.502M/s cache_miss=40.1749 cache_ref=651.648 cycles=7.29438M cycles_per_byte=11.5506 docs_per_sec=753.001/s frequency=5.49268G/s instructions=23.3974M instructions_per_byte=37.0496 instructions_per_cycle=3.20759 items=100 items_per_second=75.3001k/s [BEST: throughput=  0.49 GB/s doc_throughput=   773 docs/s instructions=    23397398 cycles=     7109297 branch_miss=   10227 cache_miss=       3 cache_ref=       591 items=       100 avg_time=   1328019 ns]

benchmark/bench_ondemand --benchmark_filter=kostya:

kostya<simdjson_ondemand>/manual_time          522043315 ns    527987141 ns            1 best_branch_miss=2.54012M best_bytes_per_sec=263.015M best_cache_miss=3.29804M best_cache_ref=6.47693M best_cycles=2.86767G best_cycles_per_byte=20.8854 best_docs_per_sec=1.91555 best_frequency=5.49316G best_instructions=8.94647G best_instructions_per_byte=65.1577 best_instructions_per_cycle=3.11977 best_items_per_sec=1004.3k branch_miss=2.54012M bytes=137.305M bytes_per_second=250.83M/s cache_miss=3.29804M cache_ref=6.47693M cycles=2.86767G cycles_per_byte=20.8854 docs_per_sec=1.91555/s frequency=5.49316G/s instructions=8.94647G instructions_per_byte=65.1577 instructions_per_cycle=3.11977 items=524.288k items_per_second=1004.3k/s [BEST: throughput=  0.26 GB/s doc_throughput=     1 docs/s instructions=  8946474135 cycles=  2867668781 branch_miss= 2540120 cache_miss= 3298038 cache_ref=   6476929 items=    524288 avg_time= 522043315 ns]
kostya<simdjson_ondemand_extract>/manual_time  488893132 ns    494300433 ns            2 best_branch_miss=1.30157M best_bytes_per_sec=281.194M best_cache_miss=3.25937M best_cache_ref=6.48623M best_cycles=2.6822G best_cycles_per_byte=19.5346 best_docs_per_sec=2.04795 best_frequency=5.49302G best_instructions=8.47092G best_instructions_per_byte=61.6942 best_instructions_per_cycle=3.1582 best_items_per_sec=1073.72k branch_miss=1.24931M bytes=137.305M bytes_per_second=267.838M/s cache_miss=3.26884M cache_ref=6.48735M cycles=2.6818G cycles_per_byte=19.5317 docs_per_sec=2.04544/s frequency=5.48545G/s instructions=8.47146G instructions_per_byte=61.6981 instructions_per_cycle=3.15887 items=524.288k items_per_second=1072.4k/s [BEST: throughput=  0.28 GB/s doc_throughput=     2 docs/s instructions=  8470922758 cycles=  2682202017 branch_miss= 1301570 cache_miss= 3259367 cache_ref=   6486226 items=    524288 avg_time= 488893132 ns]

on_field_raw way is now beating the manual way.

@lemire
Copy link
Member

lemire commented Sep 21, 2024

@the-moisrex Very nice.

Visual Studio appears to complain...

D:\a\simdjson\simdjson\build\ALL_BUILD.vcxproj" (default target) (1) ->
"D:\a\simdjson\simdjson\build\benchmark\bench_ondemand.vcxproj" (default target) (12) ->
(ClCompile target) -> 
  D:\a\simdjson\simdjson\include\simdjson\generic\ondemand\object-inl.h(28,57): error C2039: '__this': is not a member of 'simdjson::fallback::ondemand::to<StringType>' [D:\a\simdjson\simdjson\build\benchmark\bench_ondemand.vcxproj]
D:\a\simdjson\simdjson\include\simdjson\generic\ondemand\object-inl.h(28,57): error C2039:         with [D:\a\simdjson\simdjson\build\benchmark\bench_ondemand.vcxproj]
D:\a\simdjson\simdjson\include\simdjson\generic\ondemand\object-inl.h(28,57): error C2039:         [ [D:\a\simdjson\simdjson\build\benchmark\bench_ondemand.vcxproj]
D:\a\simdjson\simdjson\include\simdjson\generic\ondemand\object-inl.h(28,57): error C2039:             StringType=std::string_view [D:\a\simdjson\simdjson\build\benchmark\bench_ondemand.vcxproj]
D:\a\simdjson\simdjson\include\simdjson\generic\ondemand\object-inl.h(28,57): error C2039:         ] [D:\a\simdjson\simdjson\build\benchmark\bench_ondemand.vcxproj]
  D:\a\simdjson\simdjson\include\simdjson\generic\ondemand\object-inl.h(28,57): error C2039: '__this': is not a member of 'simdjson::fallback::ondemand::to<double>' [D:\a\simdjson\simdjson\build\benchmark\bench_ondemand.vcxproj]

:-/

@lemire
Copy link
Member

lemire commented Sep 21, 2024

It is likely that this PR is as good as it is going to guess on the short term. This is very good code and it is a very good design, in my opinion.

I am still opening this up to the community... please do comment if you are reading this!!!

Comment on lines +26 to +27
std::apply([&](auto &...endpoints) {
std::ignore = ((field_key.unsafe_is_equal(endpoints.key()) ? (error = endpoints(value(iter.child()))) == SUCCESS : true) && ...);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea why MSVC gives that error; I fixed it the way programmers fix everything, adding another layer of indirection.

I could not see any visible performance drop after this though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see the-moisrex#2 for a slight tweak.

extractor PR with clangcl tweaks
@lemire
Copy link
Member

lemire commented Oct 8, 2024

We need documentation before merging this, but I am still opening up this issue for comments. If you use simdjson, please consider reviewing.

@lemire lemire mentioned this pull request Oct 10, 2024
@lemire
Copy link
Member

lemire commented Oct 10, 2024

@the-moisrex I am not verifying your good results. See

#2274

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants