-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
perf: indexing: Introduce a bulk getValuesInto function to read values #12105
Conversation
{ | ||
lock.readLock().lock(); | ||
try { | ||
for (int i = 0; i < ids.length; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since here you're copying the whole ids
array to the input array without checking if the input values
length matches the length of ids
, I'm suggesting that this new method only accepts ids
as parameter, and returns T[]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point on checking array lengths match.
I did explore returning value only (that's my internal impl actually), but it requires wider changes. Specifically, since Java erases generics, it's impossible to do new T[]
. So, we have to either do something like:
StringDimensionIndexer() constructor {
super(String.class);
}
// and then propagate this Class<T> to DimensionDictionary, and in DimensionDictionary, do a:
T[] out = (T[]) Array.newInstance(typeFromConstructor, keys.length);
To me, both are viable and work fine, this approach seems less invasive.
So, we could either: (1) check array length explicitly or (2) create new array inside getValues()
call.
What do you suggest?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, handling generic in java is a little complicated. How about using List<T>
as the returning type? This won't introduce any magic code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#12073 is adding a StringDimensionDictionary
, so this method might be able to be a bit cleaner in the future, https://github.com/apache/druid/pull/12073/files#diff-f8b1f0a09275a697d95c1d716e7c9f7650cdd2d7021497018df75e4635e4377cR28
@@ -132,8 +132,10 @@ public long estimateEncodedKeyComponentSize(int[] key) | |||
// even though they are stored just once. It may overestimate the size by a bit, but we wanted to leave | |||
// more buffer to be safe | |||
long estimatedSize = key.length * Integer.BYTES; | |||
for (int element : key) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: it's better to rename the original variable name key
to keys
to make it intuitive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in d166364
hey @FrankChen021 , feedback covered from your previous review |
while we are here, could you share an opinion on this implementation, it's faster again but with a different API: |
As a public API that accepts a callback, I'm worried about if user provides a time-consuming callback, which means the read lock will be held for a long time, and the write lock will be blocked. So I think current solution in this PR might be better. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. +1 after CI.
@FrankChen021 It seems the CI failure on this PR is unrelated area in stage 2 tests. Any suggestions? I can close/open to trigger CI? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
{ | ||
lock.readLock().lock(); | ||
try { | ||
for (int i = 0; i < ids.length; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#12073 is adding a StringDimensionDictionary
, so this method might be able to be a bit cleaner in the future, https://github.com/apache/druid/pull/12073/files#diff-f8b1f0a09275a697d95c1d716e7c9f7650cdd2d7021497018df75e4635e4377cR28
@jasonk000 Thanks for another optimization! Do you think you could clean up the conflicts in this PR so that it can be merged? Sorry it fell off the radar |
…s in bulk If large number of values are required from DimensionDictionary during indexing, fetch them all in a single lock/unlock instead of lock/unlock each individual item.
… a new T[] rather than filling
f91f709
to
2a1d051
Compare
@suneet-s rebased!
Rebased! Thanks. |
Thanks @jasonk000 ! |
Description
If large number of values are required from
DimensionDictionary
during indexing, fetch them all in a single lock/unlock call instead of lock/unlock each individual item.During indexing there are repeated lock/unlock boundary crossing. In a sample application (57 fields to index), this consumes ~9% of the taskrunner CPU.
Depending on indexing row configuration specifics, the indexer usage of
DimensionDictionary
can consume anywhere from 1-20% of the CPU time during processing. This PR addresses one aspect of the processing, specifically the getValues. (I'll introduce another PR on the add/size change).Introduce a
getValuesInto()
call to theDimensionDictionary
, and use itThis introduces a
getValuesInto
call, which accepts an array of IDs to fetch, and performs the equivalent of manygetValue()
calls.Expand benchmarks to cover wider rows and some concurrency
Design option
There is one other design option, that is in fact much faster again, but is a bit less intuitive.
Here are the benchmark results across all three:
Alternatives (faster!)
The alternative,
doInsideReadLoop()
is to pass a lambda / closure into theDimensionDictionary
boundary and have it perform locking and execute on the other side. This is likely to be faster for most cases, however, it's a bit less clear in the API (I'll leave an extra comment).The solution involves similar changes, and passing a closure across the boundary. However, it does leak implementation concerns...
Otherwise, I think the changes are straightforward!
This PR has: