-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Arrow: Pad decimal bytes before passing to decimal vector #5168
Conversation
I'd be interested to see what results others are seeing with the benchmarks. |
((DecimalVector) vector).setBigEndian(idx, vectorBytes); | ||
ByteBuffer buffer = dict.decodeToBinary(currentVal).toByteBuffer(); | ||
vector.getDataBuffer().setBytes(idx, buffer); |
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.
@bryanck, was this really setting the value twice? It looks like it was calling setBigEndian
on the vector and then setBytes
on the backing buffer. That could explain a lot of the slowness as well?
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.
It looks like that's what it was doing.
} else if (bigEndianBytes.length < newLength) { | ||
byte[] result = new byte[newLength]; | ||
if (bigEndianBytes.length == 0) { | ||
return result; |
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.
@bryanck, is this hit? It looks like an invalid case because the decimal precision would need to be 0, but we're choosing to return 0 for it.
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.
Probably not, I was mimicking the behavior in DecimalVector.setBigEndian()
to be on the safe side.
byte[] vectorBytes = | ||
DecimalVectorUtil.padBigEndianBytes( | ||
dict.decodeToBinary(currentVal).getBytesUnsafe(), | ||
DecimalVector.TYPE_WIDTH); |
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.
Is typeWidth
going to be the same as DecimalVector.TYPE_WIDTH
?
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.
typeWidth
is the Parquet width, I believe, which is variable depending on the precision of the decimal, but the Arrow width is always 16.
@@ -358,7 +358,8 @@ class FixedLengthDecimalReader extends BaseReader { | |||
protected void nextVal( | |||
FieldVector vector, int idx, ValuesAsBytesReader valuesReader, int typeWidth, byte[] byteArray) { | |||
valuesReader.getBuffer(typeWidth).get(byteArray, 0, typeWidth); | |||
((DecimalVector) vector).setBigEndian(idx, byteArray); | |||
byte[] vectorBytes = DecimalVectorUtil.padBigEndianBytes(byteArray, DecimalVector.TYPE_WIDTH); |
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.
This looks like a place where we could reuse a buffer rather than allocating in padBigEndianBytes
every time.
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.
I did some testing, and reusing the buffer was a little bit slower, partly because we need to always to fill the buffer to zero out the last value.
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.
One thing that was a little bit faster was to bypass DecimalVector.setBigEndian()
, convert to little endian (if needed) and copy the bytes directly to the value buffer.
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.
Also one thing to note is that the benchmark isn't quite right. Decimal(20,5) will end up taking 9 bytes and will thus use a fixed length byte array instead of long or int encoding. And fixed length byte arrays aren't dictionary encoded in Parquet v1. That explains why the decimal benchmark is much slower than the other data types (which are dictionary encoded).
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.
(It looks like dictionary encoding for fixed length byte arrays wouldn't work correctly anyway, I may follow up with a fix for that)
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.
On second thought about reusing the buffer, we could create a buffer per value reader so the width of the value is the same, then skip the array fill (if you have 2 buffers, one for negative and one for positive values)
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.
Here's the PR that has a fix for the dictionary encoding
@@ -369,9 +370,10 @@ protected void nextDictEncodedVal( | |||
reader.fixedLengthDecimalDictEncodedReader() | |||
.nextBatch(vector, idx, numValuesToRead, dict, nullabilityHolder, typeWidth); | |||
} else if (Mode.PACKED.equals(mode)) { | |||
ByteBuffer decimalBytes = dict.decodeToBinary(reader.readInteger()).toByteBuffer(); | |||
byte[] vectorBytes = new byte[typeWidth]; | |||
System.arraycopy(decimalBytes, 0, vectorBytes, 0, typeWidth); |
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.
Was this correct before? It looks like it was trying to use System.arraycopy
with a ByteBuffer
!
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.
I believe this would have thrown an ArrayStoreException
* Arrow: Pad decimal bytes before passing to vector * comment clarification * optimize fill for neg numbers * Add overflow check
* Arrow: Pad decimal bytes before passing to vector * comment clarification * optimize fill for neg numbers * Add overflow check
The vectorized reader benchmarks showed that the Iceberg Parquet vectorized reader falls behind the one in Spark when reading decimal types. When profiling the code, a bottleneck was discovered in a method in Arrow that pads the byte buffer when setting a value in the DecimalVector, specifically this operation.
Runs of this benchmark showed that calling
Unsafe.setMemory()
can be slower than Java array operations. Results of a run are here.This PR adds a workaround that pads the byte buffer before calling
setBigEndian()
to avoidUnsafe.setMemory()
from being called.Here are the results of a run of the
VectorizedReadDictionaryEncodedFlatParquetDataBenchmark
benchmark without this change:Here are the results of a run with this change: