Proof of Concept: Release GVL in Statement#step #528
+128
−28
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Refs #287, but I don't think this is ready to merge. As described below the performance impact is very significant. I think this is something the library should do, but it's a penalty that's hard to just eat.
This allows other threads to execute Ruby code (including performing more work sqlite operations if on another DB connection). This should significantly improve multithreaded performance, however as @tenderlove correctly predicted in #287 adds significant overhead to single-threaded performance.
This required wrapping all callbacks which can happen within statement execution with rb_thread_call_with_gvl.
I've written two benchmarks, one which shows why this could be a good idea and one which shows why it's a bad idea. It's unfortunately not clear cut.
The good
https://gist.github.com/jhawthorn/ae84ee2723ff4930344778cfe40205a2
I made a benchmark based on making sqlite actually do work (a poorly optimized query) before returning, and then having multiple threads perform the same operation. This shows a very significant speed up as we are actually able to run the sqlite operations in parallel.
main
This branch:
The bad
https://gist.github.com/jhawthorn/492b6a9c5fb6bebe1ebfe985f52495de
When running a completely trivial query in a single thread, this is about 60% slower due to the need to release and re-acquire the GVL on every call to
Statement#step
. I don't think there's an easy way around this as even if our API was a more "bulk" operation (ie "return all rows" rather than "return one row") I believe we'll need the GVL every time we want to allocate a Ruby string and our memory buffer returned from SQLite is only good until our next call tosqlite3_step
.I do think this is capturing the absolute worst case as the query is completely trivial and doesn't even make allocations other than the one array per-row.
So that's where we're at. There might be some sort of compromise we could do like making the GVL releasing optional or based on a heuristic. I haven't yet investigated any way to make this faster, but it does seem unlikely we'll find a way to do this without a performance penalty.
TODO
rb_thread_call_without_gvl