This is an automated email from the ASF dual-hosted git repository.
kazuyukitanimura 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 ffb96c37 fix: optimize some bit functions (#718)
ffb96c37 is described below
commit ffb96c375be9bf1fbce5cf805ffd5f35667a223b
Author: KAZUYUKI TANIMURA <[email protected]>
AuthorDate: Fri Aug 2 14:36:09 2024 -0700
fix: optimize some bit functions (#718)
## Which issue does this PR close?
Part of #679 and #670
## Rationale for this change
The improvement could be negligible in real use cases, but I see some
improvements in micro benchmarks
## What changes are included in this PR?
Optimizations in some bit functions
## How are these changes tested?
Existing tests
---
native/core/benches/bit_util.rs | 35 ++++++++++-
native/core/src/common/bit.rs | 96 ++++++++++++++++++++-----------
native/core/src/parquet/mutable_vector.rs | 2 +-
native/core/src/parquet/read/levels.rs | 4 +-
4 files changed, 98 insertions(+), 39 deletions(-)
diff --git a/native/core/benches/bit_util.rs b/native/core/benches/bit_util.rs
index e92dd637..91471a89 100644
--- a/native/core/benches/bit_util.rs
+++ b/native/core/benches/bit_util.rs
@@ -21,7 +21,8 @@ use rand::{thread_rng, Rng};
use arrow::buffer::Buffer;
use comet::common::bit::{
- log2, read_num_bytes_u32, read_num_bytes_u64, set_bits, BitReader,
BitWriter,
+ log2, read_num_bytes_u32, read_num_bytes_u64, read_u32, read_u64,
set_bits, trailing_bits,
+ BitReader, BitWriter,
};
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId,
Criterion};
@@ -159,6 +160,38 @@ fn criterion_benchmark(c: &mut Criterion) {
);
}
+ // trailing_bits
+ for length in (0..=64).step_by(32) {
+ let x = length;
+ group.bench_with_input(
+ BenchmarkId::new("trailing_bits", format!("num_bits_{}", x)),
+ &x,
+ |b, &x| {
+ b.iter(|| trailing_bits(black_box(1234567890), black_box(x)));
+ },
+ );
+ }
+
+ // read_u64
+ group.bench_function("read_u64", |b| {
+ b.iter(|| read_u64(black_box(&[0u8; 8])));
+ });
+
+ // read_u32
+ group.bench_function("read_u32", |b| {
+ b.iter(|| read_u32(black_box(&[0u8; 4])));
+ });
+
+ // get_u32_value
+ group.bench_function("get_u32_value", |b| {
+ b.iter(|| {
+ let mut reader: BitReader = BitReader::new_all(buffer.slice(0));
+ for _ in 0..(buffer.len() * 8 / 31) {
+ black_box(reader.get_u32_value(black_box(31)));
+ }
+ })
+ });
+
group.finish();
}
diff --git a/native/core/src/common/bit.rs b/native/core/src/common/bit.rs
index 59b9e4e4..e3937b4a 100644
--- a/native/core/src/common/bit.rs
+++ b/native/core/src/common/bit.rs
@@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.
-use std::{cmp, cmp::min, mem::size_of};
+use std::{cmp::min, mem::size_of};
use arrow::buffer::Buffer;
@@ -131,6 +131,18 @@ pub fn read_num_bytes_u32(size: usize, src: &[u8]) -> u32 {
trailing_bits(v as u64, size * 8) as u32
}
+#[inline]
+pub fn read_u64(src: &[u8]) -> u64 {
+ let in_ptr = src.as_ptr() as *const u64;
+ unsafe { in_ptr.read_unaligned() }
+}
+
+#[inline]
+pub fn read_u32(src: &[u8]) -> u32 {
+ let in_ptr = src.as_ptr() as *const u32;
+ unsafe { in_ptr.read_unaligned() }
+}
+
/// Similar to the `read_num_bytes` but read nums from bytes in big-endian
order
/// This is used to read bytes from Java's OutputStream which writes bytes in
big-endian
macro_rules! read_num_be_bytes {
@@ -189,8 +201,7 @@ pub fn trailing_bits(v: u64, num_bits: usize) -> u64 {
if unlikely(num_bits >= 64) {
return v;
}
- let n = 64 - num_bits;
- (v << n) >> n
+ v & ((1 << num_bits) - 1)
}
#[inline]
@@ -555,8 +566,11 @@ pub struct BitReader {
/// either byte aligned or not.
impl BitReader {
pub fn new(buf: Buffer, len: usize) -> Self {
- let num_bytes = cmp::min(8, len);
- let buffered_values = read_num_bytes_u64(num_bytes, buf.as_slice());
+ let buffered_values = if size_of::<u64>() > len {
+ read_num_bytes_u64(len, buf.as_slice())
+ } else {
+ read_u64(buf.as_slice())
+ };
BitReader {
buffer: buf,
buffered_values,
@@ -574,8 +588,11 @@ impl BitReader {
pub fn reset(&mut self, buf: Buffer) {
self.buffer = buf;
self.total_bytes = self.buffer.len();
- let num_bytes = cmp::min(8, self.total_bytes);
- self.buffered_values = read_num_bytes_u64(num_bytes,
self.buffer.as_slice());
+ self.buffered_values = if size_of::<u64>() > self.total_bytes {
+ read_num_bytes_u64(self.total_bytes, self.buffer.as_slice())
+ } else {
+ read_u64(self.buffer.as_slice())
+ };
self.byte_offset = 0;
self.bit_offset = 0;
}
@@ -597,19 +614,7 @@ impl BitReader {
return None;
}
- let mut v =
- trailing_bits(self.buffered_values, self.bit_offset + num_bits) >>
self.bit_offset;
- self.bit_offset += num_bits;
-
- if self.bit_offset >= 64 {
- self.byte_offset += 8;
- self.bit_offset -= 64;
-
- self.reload_buffer_values();
- v |= trailing_bits(self.buffered_values, self.bit_offset)
- .wrapping_shl((num_bits - self.bit_offset) as u32);
- }
-
+ let v = self.get_u64_value(num_bits);
Some(T::from(v))
}
@@ -625,20 +630,26 @@ impl BitReader {
/// Undefined behavior will happen if any of the above assumptions is
violated.
#[inline]
pub fn get_u32_value(&mut self, num_bits: usize) -> u32 {
- let mut v =
- trailing_bits(self.buffered_values, self.bit_offset + num_bits) >>
self.bit_offset;
- self.bit_offset += num_bits;
-
- if self.bit_offset >= 64 {
- self.byte_offset += 8;
- self.bit_offset -= 64;
+ self.get_u64_value(num_bits) as u32
+ }
- self.reload_buffer_values();
- v |= trailing_bits(self.buffered_values, self.bit_offset)
- .wrapping_shl((num_bits - self.bit_offset) as u32);
+ #[inline(always)]
+ fn get_u64_value(&mut self, num_bits: usize) -> u64 {
+ if unlikely(num_bits == 0) {
+ 0
+ } else {
+ let v = self.buffered_values >> self.bit_offset;
+ let mask = u64::MAX >> (64 - num_bits);
+ self.bit_offset += num_bits;
+ if self.bit_offset < 64 {
+ v & mask
+ } else {
+ self.byte_offset += 8;
+ self.bit_offset -= 64;
+ self.reload_buffer_values();
+ ((self.buffered_values << (num_bits - self.bit_offset)) | v) &
mask
+ }
}
-
- v as u32
}
/// Gets at most `num` bits from this reader, and append them to the `dst`
byte slice, starting
@@ -974,9 +985,12 @@ impl BitReader {
}
fn reload_buffer_values(&mut self) {
- let bytes_to_read = cmp::min(self.total_bytes - self.byte_offset, 8);
- self.buffered_values =
- read_num_bytes_u64(bytes_to_read,
&self.buffer.as_slice()[self.byte_offset..]);
+ let bytes_to_read = self.total_bytes - self.byte_offset;
+ self.buffered_values = if 8 > bytes_to_read {
+ read_num_bytes_u64(bytes_to_read,
&self.buffer.as_slice()[self.byte_offset..])
+ } else {
+ read_u64(&self.buffer.as_slice()[self.byte_offset..])
+ };
}
}
@@ -1019,6 +1033,12 @@ mod tests {
}
}
+ #[test]
+ fn test_read_u64() {
+ let buffer: Vec<u8> = vec![0, 1, 2, 3, 4, 5, 6, 7];
+ assert_eq!(read_u64(&buffer), read_num_bytes!(u64, 8, &buffer),);
+ }
+
#[test]
fn test_read_num_bytes_u32() {
let buffer: Vec<u8> = vec![0, 1, 2, 3];
@@ -1030,6 +1050,12 @@ mod tests {
}
}
+ #[test]
+ fn test_read_u32() {
+ let buffer: Vec<u8> = vec![0, 1, 2, 3];
+ assert_eq!(read_u32(&buffer), read_num_bytes!(u32, 4, &buffer),);
+ }
+
#[test]
fn test_ceil() {
assert_eq!(ceil(0, 1), 0);
diff --git a/native/core/src/parquet/mutable_vector.rs
b/native/core/src/parquet/mutable_vector.rs
index f1428fd3..7f30d7d8 100644
--- a/native/core/src/parquet/mutable_vector.rs
+++ b/native/core/src/parquet/mutable_vector.rs
@@ -154,7 +154,7 @@ impl ParquetMutableVector {
// We need to update offset buffer for binary.
if Self::is_binary_type(&self.arrow_type) {
let mut offset = self.num_values * 4;
- let prev_offset_value = bit::read_num_bytes_u32(4,
&self.value_buffer[offset..]);
+ let prev_offset_value =
bit::read_u32(&self.value_buffer[offset..]);
offset += 4;
(0..n).for_each(|_| {
bit::memcpy_value(&prev_offset_value, 4, &mut
self.value_buffer[offset..]);
diff --git a/native/core/src/parquet/read/levels.rs
b/native/core/src/parquet/read/levels.rs
index 303db54c..3d74b277 100644
--- a/native/core/src/parquet/read/levels.rs
+++ b/native/core/src/parquet/read/levels.rs
@@ -22,7 +22,7 @@ use parquet::schema::types::ColumnDescPtr;
use super::values::Decoder;
use crate::{
- common::bit::{self, read_num_bytes_u32, BitReader},
+ common::bit::{self, read_u32, BitReader},
parquet::ParquetMutableVector,
unlikely,
};
@@ -89,7 +89,7 @@ impl LevelDecoder {
0
} else if self.need_length {
let u32_size = mem::size_of::<u32>();
- let data_size = read_num_bytes_u32(u32_size, page_data.as_slice())
as usize;
+ let data_size = read_u32(page_data.as_slice()) as usize;
self.bit_reader = Some(BitReader::new(page_data.slice(u32_size),
data_size));
u32_size + data_size
} else {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]