This is an automated email from the ASF dual-hosted git repository.
agrove pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-comet.git
The following commit(s) were added to refs/heads/main by this push:
new 2fc45a2f fix: Re-implement some Parquet decode methods without
`copy_nonoverlapping` (#558)
2fc45a2f is described below
commit 2fc45a2faea3c6dc4c2a5517d6e1e6b22b8ef1a0
Author: Andy Grove <[email protected]>
AuthorDate: Thu Jun 13 15:38:42 2024 -0600
fix: Re-implement some Parquet decode methods without `copy_nonoverlapping`
(#558)
* Re-implement int decode methods using safe code
* fix
* fix tests
* more tests
* fix
* fix
* re-implement using unsafe read/write unaligned and add benchmark
* lint
* macros
* more macros
* combine macros
* replace another impl with the macro
* fix a regression
* remove zero_value arg from generate_cast_to_signed and rename
impl_plain_decoding_int to the original name of make_int_variant_impl
---
core/Cargo.lock | 1 +
core/Cargo.toml | 5 ++
core/benches/parquet_decode.rs | 50 +++++++++++
core/src/parquet/read/values.rs | 191 +++++++++++++++++++++++++++++++---------
4 files changed, 203 insertions(+), 44 deletions(-)
diff --git a/core/Cargo.lock b/core/Cargo.lock
index 217ab98c..eca8b97d 100644
--- a/core/Cargo.lock
+++ b/core/Cargo.lock
@@ -878,6 +878,7 @@ dependencies = [
"futures",
"half",
"hashbrown",
+ "hex",
"itertools 0.11.0",
"jni",
"lazy_static",
diff --git a/core/Cargo.toml b/core/Cargo.toml
index fe74b355..04a45f6c 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -93,6 +93,7 @@ criterion = "0.5.1"
jni = { version = "0.21", features = ["invocation"] }
lazy_static = "1.4"
assertables = "7"
+hex = "0.4.3"
[features]
default = []
@@ -136,3 +137,7 @@ harness = false
[[bench]]
name = "shuffle_writer"
harness = false
+
+[[bench]]
+name = "parquet_decode"
+harness = false
diff --git a/core/benches/parquet_decode.rs b/core/benches/parquet_decode.rs
new file mode 100644
index 00000000..7176bf9c
--- /dev/null
+++ b/core/benches/parquet_decode.rs
@@ -0,0 +1,50 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use arrow_buffer::ToByteSlice;
+use comet::parquet::read::values::{copy_i32_to_i16, copy_i32_to_u16};
+use criterion::{criterion_group, criterion_main, Criterion};
+
+fn criterion_benchmark(c: &mut Criterion) {
+ let num = 1000;
+ let source = vec![78_i8; num * 4];
+ let mut group = c.benchmark_group("parquet_decode");
+ group.bench_function("decode_i32_to_i16", |b| {
+ let mut dest: Vec<u8> = vec![b' '; num * 2];
+ b.iter(|| {
+ copy_i32_to_i16(source.to_byte_slice(), dest.as_mut_slice(), num);
+ });
+ });
+ group.bench_function("decode_i32_to_u16", |b| {
+ let mut dest: Vec<u8> = vec![b' '; num * 4];
+ b.iter(|| {
+ copy_i32_to_u16(source.to_byte_slice(), dest.as_mut_slice(), num);
+ });
+ });
+}
+
+// Create UTF8 batch with strings representing ints, floats, nulls
+fn config() -> Criterion {
+ Criterion::default()
+}
+
+criterion_group! {
+ name = benches;
+ config = config();
+ targets = criterion_benchmark
+}
+criterion_main!(benches);
diff --git a/core/src/parquet/read/values.rs b/core/src/parquet/read/values.rs
index 76c8a4a1..7f1195fa 100644
--- a/core/src/parquet/read/values.rs
+++ b/core/src/parquet/read/values.rs
@@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.
-use std::{marker::PhantomData, mem, ptr::copy_nonoverlapping};
+use std::{marker::PhantomData, mem};
use arrow::buffer::Buffer;
use bytes::Buf;
@@ -182,25 +182,6 @@ make_plain_dict_impl! { Int8Type, UInt8Type, Int16Type,
UInt16Type, Int32Type, U
make_plain_dict_impl! { Int32DateType, Int64Type, FloatType, FLBAType }
make_plain_dict_impl! { DoubleType, Int64TimestampMillisType,
Int64TimestampMicrosType }
-impl PlainDecoding for Int32To64Type {
- fn decode(src: &mut PlainDecoderInner, dst: &mut ParquetMutableVector,
num: usize) {
- let src_ptr = src.data.as_ptr() as *const i32;
- let dst_ptr = dst.value_buffer.as_mut_ptr() as *mut i64;
- unsafe {
- for i in 0..num {
- dst_ptr
- .add(dst.num_values + i)
- .write_unaligned(src_ptr.add(src.offset +
i).read_unaligned() as i64);
- }
- }
- src.offset += 4 * num;
- }
-
- fn skip(src: &mut PlainDecoderInner, num: usize) {
- src.offset += 4 * num;
- }
-}
-
impl PlainDictDecoding for Int32To64Type {
fn decode_dict_one(
idx: usize,
@@ -438,41 +419,92 @@ impl PlainDictDecoding for BoolType {
}
}
-// Shared implementation for int variants such as Int8 and Int16
macro_rules! make_int_variant_impl {
- ($ty: ident, $native_ty: ty, $type_size: expr) => {
- impl PlainDecoding for $ty {
+ ($dst_type:ty, $copy_fn:ident, $type_width:expr) => {
+ impl PlainDecoding for $dst_type {
fn decode(src: &mut PlainDecoderInner, dst: &mut
ParquetMutableVector, num: usize) {
- let src_data = &src.data;
let dst_slice = dst.value_buffer.as_slice_mut();
- let mut dst_offset = dst.num_values * $type_size;
- for _ in 0..num {
- unsafe {
- copy_nonoverlapping(
- &src_data[src.offset..] as *const [u8] as *const u8
- as *const $native_ty,
- &mut dst_slice[dst_offset] as *mut u8 as *mut
$native_ty,
- 1,
- );
- }
- src.offset += 4; // Parquet stores Int8/Int16 using 4 bytes
- dst_offset += $type_size;
- }
+ let dst_offset = dst.num_values * $type_width;
+ $copy_fn(&src.data[src.offset..], &mut
dst_slice[dst_offset..], num);
+ src.offset += 4 * num; // Parquet stores Int8/Int16 using 4
bytes
}
fn skip(src: &mut PlainDecoderInner, num: usize) {
- let num_bytes = 4 * num; // Parquet stores Int8/Int16 using 4
bytes
- src.offset += num_bytes;
+ src.offset += 4 * num; // Parquet stores Int8/Int16 using 4
bytes
}
}
};
}
-make_int_variant_impl!(Int8Type, i8, 1);
-make_int_variant_impl!(UInt8Type, u8, 2);
-make_int_variant_impl!(Int16Type, i16, 2);
-make_int_variant_impl!(UInt16Type, u16, 4);
-make_int_variant_impl!(UInt32Type, u32, 8);
+make_int_variant_impl!(Int8Type, copy_i32_to_i8, 1);
+make_int_variant_impl!(Int16Type, copy_i32_to_i16, 2);
+make_int_variant_impl!(Int32To64Type, copy_i32_to_i64, 4);
+
+// unsigned type require double the width and zeroes are written for the
second half
+// perhaps because they are implemented as the next size up signed type?
+make_int_variant_impl!(UInt8Type, copy_i32_to_u8, 2);
+make_int_variant_impl!(UInt16Type, copy_i32_to_u16, 4);
+make_int_variant_impl!(UInt32Type, copy_i32_to_u32, 8);
+
+macro_rules! generate_cast_to_unsigned {
+ ($name: ident, $src_type:ty, $dst_type:ty, $zero_value:expr) => {
+ pub fn $name(src: &[u8], dst: &mut [u8], num: usize) {
+ debug_assert!(
+ src.len() >= num * std::mem::size_of::<$src_type>(),
+ "Source slice is too small"
+ );
+ debug_assert!(
+ dst.len() >= num * std::mem::size_of::<$dst_type>() * 2,
+ "Destination slice is too small"
+ );
+
+ let src_ptr = src.as_ptr() as *const $src_type;
+ let dst_ptr = dst.as_mut_ptr() as *mut $dst_type;
+ unsafe {
+ for i in 0..num {
+ dst_ptr
+ .add(2 * i)
+ .write_unaligned(src_ptr.add(i).read_unaligned() as
$dst_type);
+ // write zeroes
+ dst_ptr.add(2 * i + 1).write_unaligned($zero_value);
+ }
+ }
+ }
+ };
+}
+
+generate_cast_to_unsigned!(copy_i32_to_u8, i32, u8, 0_u8);
+generate_cast_to_unsigned!(copy_i32_to_u16, i32, u16, 0_u16);
+generate_cast_to_unsigned!(copy_i32_to_u32, i32, u32, 0_u32);
+
+macro_rules! generate_cast_to_signed {
+ ($name: ident, $src_type:ty, $dst_type:ty) => {
+ pub fn $name(src: &[u8], dst: &mut [u8], num: usize) {
+ debug_assert!(
+ src.len() >= num * std::mem::size_of::<$src_type>(),
+ "Source slice is too small"
+ );
+ debug_assert!(
+ dst.len() >= num * std::mem::size_of::<$dst_type>(),
+ "Destination slice is too small"
+ );
+
+ let src_ptr = src.as_ptr() as *const $src_type;
+ let dst_ptr = dst.as_mut_ptr() as *mut $dst_type;
+ unsafe {
+ for i in 0..num {
+ dst_ptr
+ .add(i)
+ .write_unaligned(src_ptr.add(i).read_unaligned() as
$dst_type);
+ }
+ }
+ }
+ };
+}
+
+generate_cast_to_signed!(copy_i32_to_i8, i32, i8);
+generate_cast_to_signed!(copy_i32_to_i16, i32, i16);
+generate_cast_to_signed!(copy_i32_to_i64, i32, i64);
// Shared implementation for variants of Binary type
macro_rules! make_plain_binary_impl {
@@ -974,3 +1006,74 @@ impl Decoder for DictDecoder {
Encoding::RLE_DICTIONARY
}
}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use parquet::data_type::AsBytes;
+
+ #[test]
+ fn test_i32_to_i8() {
+ let source =
+
hex::decode("8a000000dbffffff1800000034ffffff300000001d000000abffffff37fffffff1000000")
+ .unwrap();
+ let expected = hex::decode("8adb1834301dab37f1").unwrap();
+ let num = source.len() / 4;
+ let mut dest: Vec<u8> = vec![b' '; num];
+ copy_i32_to_i8(&source.as_bytes(), dest.as_mut_slice(), num);
+ assert_eq!(expected.as_bytes(), dest.as_bytes());
+ }
+
+ #[test]
+ fn test_i32_to_u8() {
+ let source =
+
hex::decode("8a000000dbffffff1800000034ffffff300000001d000000abffffff37fffffff1000000")
+ .unwrap();
+ let expected =
hex::decode("8a00db001800340030001d00ab003700f100").unwrap();
+ let num = source.len() / 4;
+ let mut dest: Vec<u8> = vec![b' '; num * 2];
+ copy_i32_to_u8(&source.as_bytes(), dest.as_mut_slice(), num);
+ assert_eq!(expected.as_bytes(), dest.as_bytes());
+ }
+
+ #[test]
+ fn test_i32_to_i16() {
+ let source =
+
hex::decode("8a0e0000db93ffff1826000034f4ffff300200001d2b0000abe3ffff378dfffff1470000")
+ .unwrap();
+ let expected =
hex::decode("8a0edb93182634f430021d2babe3378df147").unwrap();
+ let num = source.len() / 4;
+ let mut dest: Vec<u8> = vec![b' '; num * 2];
+ copy_i32_to_i16(&source.as_bytes(), dest.as_mut_slice(), num);
+ assert_eq!(expected.as_bytes(), dest.as_bytes());
+ }
+
+ #[test]
+ fn test_i32_to_u16() {
+ let source = hex::decode(
+
"ff7f0000008000000180000002800000038000000480000005800000068000000780000008800000",
+ )
+ .unwrap();
+ let expected = hex::decode(
+
"ff7f0000008000000180000002800000038000000480000005800000068000000780000008800000",
+ )
+ .unwrap();
+ let num = source.len() / 4;
+ let mut dest: Vec<u8> = vec![b' '; num * 4];
+ copy_i32_to_u16(&source.as_bytes(), dest.as_mut_slice(), num);
+ assert_eq!(expected.as_bytes(), dest.as_bytes());
+ }
+
+ #[test]
+ fn test_i32_to_u32() {
+ let source = hex::decode(
+
"ffffff7f000000800100008002000080030000800400008005000080060000800700008008000080",
+ )
+ .unwrap();
+ let expected =
hex::decode("ffffff7f00000000000000800000000001000080000000000200008000000000030000800000000004000080000000000500008000000000060000800000000007000080000000000800008000000000").unwrap();
+ let num = source.len() / 4;
+ let mut dest: Vec<u8> = vec![b' '; num * 8];
+ copy_i32_to_u32(&source.as_bytes(), dest.as_mut_slice(), num);
+ assert_eq!(expected.as_bytes(), dest.as_bytes());
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]