This is an automated email from the ASF dual-hosted git repository.

tisonkun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datasketches-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new 3d8b664  perf: reduce sketch hashing overhead (#265)
3d8b664 is described below

commit 3d8b6640085ce87198325d5ba76fd5dc7217709f
Author: tison <[email protected]>
AuthorDate: Wed Sep 2 20:46:49 2026 +0800

    perf: reduce sketch hashing overhead (#265)
---
 CHANGELOG.md                                   |  1 +
 benchmarks/Cargo.toml                          | 13 ++++-
 benchmarks/{cpc => bloom}/mod.rs               |  2 +-
 benchmarks/bloom/update.rs                     | 53 +++++++++++++++++++
 benchmarks/{cpc => countmin}/mod.rs            |  2 +-
 benchmarks/countmin/update.rs                  | 49 ++++++++++++++++++
 benchmarks/cpc/mod.rs                          |  1 +
 benchmarks/cpc/update.rs                       | 49 ++++++++++++++++++
 benchmarks/{cpc => frequencies}/mod.rs         |  2 +-
 benchmarks/frequencies/update.rs               | 50 ++++++++++++++++++
 benchmarks/{main.rs => hash_inputs.rs}         | 20 ++++----
 benchmarks/{cpc => hll}/mod.rs                 |  2 +-
 benchmarks/hll/update.rs                       | 50 ++++++++++++++++++
 benchmarks/main.rs                             |  7 +++
 benchmarks/{cpc => theta}/mod.rs               |  2 +-
 benchmarks/theta/update.rs                     | 49 ++++++++++++++++++
 benchmarks/{cpc => tuple}/mod.rs               |  2 +-
 benchmarks/tuple/update.rs                     | 52 +++++++++++++++++++
 datasketches/src/hash/mod.rs                   | 16 +++---
 datasketches/src/hash/murmurhash.rs            | 70 +++++++++++++-------------
 datasketches/src/hash/value/canonical_float.rs |  2 +
 datasketches/src/hash/value/mod.rs             |  1 +
 datasketches/src/hash/value/natural_extend.rs  |  1 +
 datasketches/src/hash/value/raw_bytes.rs       |  1 +
 datasketches/src/hash/value/sign_extend.rs     |  1 +
 datasketches/src/hash/xxhash.rs                | 18 ++++---
 26 files changed, 448 insertions(+), 68 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f65b267..427ec34 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ All significant changes to this project will be documented in 
this file.
 ### Improvements
 
 * Improve truncated-input diagnostics across sketch deserializers.
+* Improve hash-backed sketch update performance for integer and raw-byte 
inputs.
 
 ### Bug fixes
 
diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml
index 3bbbda5..25af32a 100644
--- a/benchmarks/Cargo.toml
+++ b/benchmarks/Cargo.toml
@@ -23,7 +23,18 @@ edition.workspace = true
 rust-version.workspace = true
 
 [dev-dependencies]
-datasketches = { workspace = true, features = ["cpc", "kll", "req", "tdigest"] 
}
+datasketches = { workspace = true, features = [
+  "bloom",
+  "countmin",
+  "cpc",
+  "frequencies",
+  "hll",
+  "kll",
+  "req",
+  "tdigest",
+  "theta",
+  "tuple",
+] }
 divan = { workspace = true }
 rand = { workspace = true }
 
diff --git a/benchmarks/cpc/mod.rs b/benchmarks/bloom/mod.rs
similarity index 98%
copy from benchmarks/cpc/mod.rs
copy to benchmarks/bloom/mod.rs
index 431ad1f..1dea33b 100644
--- a/benchmarks/cpc/mod.rs
+++ b/benchmarks/bloom/mod.rs
@@ -15,4 +15,4 @@
 // specific language governing permissions and limitations
 // under the License.
 
-mod serde;
+mod update;
diff --git a/benchmarks/bloom/update.rs b/benchmarks/bloom/update.rs
new file mode 100644
index 0000000..2aa6b81
--- /dev/null
+++ b/benchmarks/bloom/update.rs
@@ -0,0 +1,53 @@
+// 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 datasketches::bloom::BloomFilterBuilder;
+use datasketches::hash::value::raw_bytes;
+use divan::Bencher;
+use divan::black_box;
+use divan::counter::ItemsCount;
+
+use crate::hash_inputs::ITEMS;
+use crate::hash_inputs::bytes_32_values;
+
+#[divan::bench]
+fn u64(bencher: Bencher) {
+    bencher.counter(ItemsCount::new(ITEMS)).bench_local(|| {
+        let mut filter = BloomFilterBuilder::with_accuracy(ITEMS as u64, 0.01)
+            .build()
+            .unwrap();
+        for value in 0..ITEMS as u64 {
+            filter.insert(black_box(value));
+        }
+        black_box(filter)
+    });
+}
+
+#[divan::bench]
+fn bytes_32(bencher: Bencher) {
+    let values = bytes_32_values();
+
+    bencher.counter(ItemsCount::new(ITEMS)).bench_local(|| {
+        let mut filter = BloomFilterBuilder::with_accuracy(ITEMS as u64, 0.01)
+            .build()
+            .unwrap();
+        for value in &values {
+            filter.insert(raw_bytes::from_slice(black_box(value)));
+        }
+        black_box(filter)
+    });
+}
diff --git a/benchmarks/cpc/mod.rs b/benchmarks/countmin/mod.rs
similarity index 98%
copy from benchmarks/cpc/mod.rs
copy to benchmarks/countmin/mod.rs
index 431ad1f..1dea33b 100644
--- a/benchmarks/cpc/mod.rs
+++ b/benchmarks/countmin/mod.rs
@@ -15,4 +15,4 @@
 // specific language governing permissions and limitations
 // under the License.
 
-mod serde;
+mod update;
diff --git a/benchmarks/countmin/update.rs b/benchmarks/countmin/update.rs
new file mode 100644
index 0000000..f25af1e
--- /dev/null
+++ b/benchmarks/countmin/update.rs
@@ -0,0 +1,49 @@
+// 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 datasketches::countmin::CountMinSketch;
+use datasketches::hash::value::raw_bytes;
+use divan::Bencher;
+use divan::black_box;
+use divan::counter::ItemsCount;
+
+use crate::hash_inputs::ITEMS;
+use crate::hash_inputs::bytes_32_values;
+
+#[divan::bench]
+fn u64(bencher: Bencher) {
+    bencher.counter(ItemsCount::new(ITEMS)).bench_local(|| {
+        let mut sketch = CountMinSketch::<u64>::new(4, 16_384).unwrap();
+        for value in 0..ITEMS as u64 {
+            sketch.update(black_box(value));
+        }
+        black_box(sketch)
+    });
+}
+
+#[divan::bench]
+fn bytes_32(bencher: Bencher) {
+    let values = bytes_32_values();
+
+    bencher.counter(ItemsCount::new(ITEMS)).bench_local(|| {
+        let mut sketch = CountMinSketch::<u64>::new(4, 16_384).unwrap();
+        for value in &values {
+            sketch.update(raw_bytes::from_slice(black_box(value)));
+        }
+        black_box(sketch)
+    });
+}
diff --git a/benchmarks/cpc/mod.rs b/benchmarks/cpc/mod.rs
index 431ad1f..13c2486 100644
--- a/benchmarks/cpc/mod.rs
+++ b/benchmarks/cpc/mod.rs
@@ -16,3 +16,4 @@
 // under the License.
 
 mod serde;
+mod update;
diff --git a/benchmarks/cpc/update.rs b/benchmarks/cpc/update.rs
new file mode 100644
index 0000000..ba532b8
--- /dev/null
+++ b/benchmarks/cpc/update.rs
@@ -0,0 +1,49 @@
+// 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 datasketches::cpc::CpcSketch;
+use datasketches::hash::value::raw_bytes;
+use divan::Bencher;
+use divan::black_box;
+use divan::counter::ItemsCount;
+
+use crate::hash_inputs::ITEMS;
+use crate::hash_inputs::bytes_32_values;
+
+#[divan::bench]
+fn u64(bencher: Bencher) {
+    bencher.counter(ItemsCount::new(ITEMS)).bench_local(|| {
+        let mut sketch = CpcSketch::new(11).unwrap();
+        for value in 0..ITEMS as u64 {
+            sketch.update(black_box(value));
+        }
+        black_box(sketch)
+    });
+}
+
+#[divan::bench]
+fn bytes_32(bencher: Bencher) {
+    let values = bytes_32_values();
+
+    bencher.counter(ItemsCount::new(ITEMS)).bench_local(|| {
+        let mut sketch = CpcSketch::new(11).unwrap();
+        for value in &values {
+            sketch.update(raw_bytes::from_slice(black_box(value)));
+        }
+        black_box(sketch)
+    });
+}
diff --git a/benchmarks/cpc/mod.rs b/benchmarks/frequencies/mod.rs
similarity index 98%
copy from benchmarks/cpc/mod.rs
copy to benchmarks/frequencies/mod.rs
index 431ad1f..1dea33b 100644
--- a/benchmarks/cpc/mod.rs
+++ b/benchmarks/frequencies/mod.rs
@@ -15,4 +15,4 @@
 // specific language governing permissions and limitations
 // under the License.
 
-mod serde;
+mod update;
diff --git a/benchmarks/frequencies/update.rs b/benchmarks/frequencies/update.rs
new file mode 100644
index 0000000..9026b29
--- /dev/null
+++ b/benchmarks/frequencies/update.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 datasketches::frequencies::FrequentItemsSketch;
+use datasketches::hash::value::raw_bytes;
+use datasketches::hash::value::raw_bytes::RawBytes;
+use divan::Bencher;
+use divan::black_box;
+use divan::counter::ItemsCount;
+
+use crate::hash_inputs::ITEMS;
+use crate::hash_inputs::bytes_32_values;
+
+#[divan::bench]
+fn u64(bencher: Bencher) {
+    bencher.counter(ItemsCount::new(ITEMS)).bench_local(|| {
+        let mut sketch = FrequentItemsSketch::<u64>::new(16_384).unwrap();
+        for value in 0..ITEMS as u64 {
+            sketch.update(black_box(value));
+        }
+        black_box(sketch)
+    });
+}
+
+#[divan::bench]
+fn bytes_32(bencher: Bencher) {
+    let values = bytes_32_values();
+
+    bencher.counter(ItemsCount::new(ITEMS)).bench_local(|| {
+        let mut sketch = 
FrequentItemsSketch::<RawBytes<&[u8]>>::new(16_384).unwrap();
+        for value in &values {
+            sketch.update(raw_bytes::from_slice(black_box(value)));
+        }
+        black_box(sketch)
+    });
+}
diff --git a/benchmarks/main.rs b/benchmarks/hash_inputs.rs
similarity index 74%
copy from benchmarks/main.rs
copy to benchmarks/hash_inputs.rs
index 11c9962..073edf4 100644
--- a/benchmarks/main.rs
+++ b/benchmarks/hash_inputs.rs
@@ -15,16 +15,14 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use divan::AllocProfiler;
+pub const ITEMS: usize = 10_000;
 
-#[global_allocator]
-static ALLOC: AllocProfiler = AllocProfiler::system();
-
-mod cpc;
-mod kll;
-mod req;
-mod tdigest;
-
-fn main() {
-    divan::main();
+pub fn bytes_32_values() -> Vec<[u8; 32]> {
+    (0..ITEMS)
+        .map(|value| {
+            let mut bytes = [0; 32];
+            bytes[..8].copy_from_slice(&(value as u64).to_le_bytes());
+            bytes
+        })
+        .collect()
 }
diff --git a/benchmarks/cpc/mod.rs b/benchmarks/hll/mod.rs
similarity index 98%
copy from benchmarks/cpc/mod.rs
copy to benchmarks/hll/mod.rs
index 431ad1f..1dea33b 100644
--- a/benchmarks/cpc/mod.rs
+++ b/benchmarks/hll/mod.rs
@@ -15,4 +15,4 @@
 // specific language governing permissions and limitations
 // under the License.
 
-mod serde;
+mod update;
diff --git a/benchmarks/hll/update.rs b/benchmarks/hll/update.rs
new file mode 100644
index 0000000..88a335c
--- /dev/null
+++ b/benchmarks/hll/update.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 datasketches::hash::value::raw_bytes;
+use datasketches::hll::HllSketch;
+use datasketches::hll::HllType;
+use divan::Bencher;
+use divan::black_box;
+use divan::counter::ItemsCount;
+
+use crate::hash_inputs::ITEMS;
+use crate::hash_inputs::bytes_32_values;
+
+#[divan::bench]
+fn u64(bencher: Bencher) {
+    bencher.counter(ItemsCount::new(ITEMS)).bench_local(|| {
+        let mut sketch = HllSketch::new(12, HllType::Hll8).unwrap();
+        for value in 0..ITEMS as u64 {
+            sketch.update(black_box(value));
+        }
+        black_box(sketch)
+    });
+}
+
+#[divan::bench]
+fn bytes_32(bencher: Bencher) {
+    let values = bytes_32_values();
+
+    bencher.counter(ItemsCount::new(ITEMS)).bench_local(|| {
+        let mut sketch = HllSketch::new(12, HllType::Hll8).unwrap();
+        for value in &values {
+            sketch.update(raw_bytes::from_slice(black_box(value)));
+        }
+        black_box(sketch)
+    });
+}
diff --git a/benchmarks/main.rs b/benchmarks/main.rs
index 11c9962..ee28803 100644
--- a/benchmarks/main.rs
+++ b/benchmarks/main.rs
@@ -20,10 +20,17 @@ use divan::AllocProfiler;
 #[global_allocator]
 static ALLOC: AllocProfiler = AllocProfiler::system();
 
+mod bloom;
+mod countmin;
 mod cpc;
+mod frequencies;
+mod hash_inputs;
+mod hll;
 mod kll;
 mod req;
 mod tdigest;
+mod theta;
+mod tuple;
 
 fn main() {
     divan::main();
diff --git a/benchmarks/cpc/mod.rs b/benchmarks/theta/mod.rs
similarity index 98%
copy from benchmarks/cpc/mod.rs
copy to benchmarks/theta/mod.rs
index 431ad1f..1dea33b 100644
--- a/benchmarks/cpc/mod.rs
+++ b/benchmarks/theta/mod.rs
@@ -15,4 +15,4 @@
 // specific language governing permissions and limitations
 // under the License.
 
-mod serde;
+mod update;
diff --git a/benchmarks/theta/update.rs b/benchmarks/theta/update.rs
new file mode 100644
index 0000000..ca9b94f
--- /dev/null
+++ b/benchmarks/theta/update.rs
@@ -0,0 +1,49 @@
+// 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 datasketches::hash::value::raw_bytes;
+use datasketches::theta::ThetaSketchBuilder;
+use divan::Bencher;
+use divan::black_box;
+use divan::counter::ItemsCount;
+
+use crate::hash_inputs::ITEMS;
+use crate::hash_inputs::bytes_32_values;
+
+#[divan::bench]
+fn u64(bencher: Bencher) {
+    bencher.counter(ItemsCount::new(ITEMS)).bench_local(|| {
+        let mut sketch = 
ThetaSketchBuilder::default().lg_k(14).build().unwrap();
+        for value in 0..ITEMS as u64 {
+            sketch.update(black_box(value));
+        }
+        black_box(sketch)
+    });
+}
+
+#[divan::bench]
+fn bytes_32(bencher: Bencher) {
+    let values = bytes_32_values();
+
+    bencher.counter(ItemsCount::new(ITEMS)).bench_local(|| {
+        let mut sketch = 
ThetaSketchBuilder::default().lg_k(14).build().unwrap();
+        for value in &values {
+            sketch.update(raw_bytes::from_slice(black_box(value)));
+        }
+        black_box(sketch)
+    });
+}
diff --git a/benchmarks/cpc/mod.rs b/benchmarks/tuple/mod.rs
similarity index 98%
copy from benchmarks/cpc/mod.rs
copy to benchmarks/tuple/mod.rs
index 431ad1f..1dea33b 100644
--- a/benchmarks/cpc/mod.rs
+++ b/benchmarks/tuple/mod.rs
@@ -15,4 +15,4 @@
 // specific language governing permissions and limitations
 // under the License.
 
-mod serde;
+mod update;
diff --git a/benchmarks/tuple/update.rs b/benchmarks/tuple/update.rs
new file mode 100644
index 0000000..9041fed
--- /dev/null
+++ b/benchmarks/tuple/update.rs
@@ -0,0 +1,52 @@
+// 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 datasketches::hash::value::raw_bytes;
+use datasketches::tuple::DefaultUpdatePolicy;
+use datasketches::tuple::TupleSketchBuilder;
+use divan::Bencher;
+use divan::black_box;
+use divan::counter::ItemsCount;
+
+use crate::hash_inputs::ITEMS;
+use crate::hash_inputs::bytes_32_values;
+
+#[divan::bench]
+fn u64(bencher: Bencher) {
+    bencher.counter(ItemsCount::new(ITEMS)).bench_local(|| {
+        let policy = DefaultUpdatePolicy::<u64>::default();
+        let mut sketch = 
TupleSketchBuilder::new(policy).lg_k(14).build().unwrap();
+        for value in 0..ITEMS as u64 {
+            sketch.update(black_box(value), 1);
+        }
+        black_box(sketch)
+    });
+}
+
+#[divan::bench]
+fn bytes_32(bencher: Bencher) {
+    let values = bytes_32_values();
+
+    bencher.counter(ItemsCount::new(ITEMS)).bench_local(|| {
+        let policy = DefaultUpdatePolicy::<u64>::default();
+        let mut sketch = 
TupleSketchBuilder::new(policy).lg_k(14).build().unwrap();
+        for value in &values {
+            sketch.update(raw_bytes::from_slice(black_box(value)), 1);
+        }
+        black_box(sketch)
+    });
+}
diff --git a/datasketches/src/hash/mod.rs b/datasketches/src/hash/mod.rs
index c5c26c4..2084902 100644
--- a/datasketches/src/hash/mod.rs
+++ b/datasketches/src/hash/mod.rs
@@ -82,11 +82,12 @@ pub(crate) use self::seed::*;
 ))]
 pub(crate) const DEFAULT_UPDATE_SEED: u64 = 9001;
 
-/// Reads an u64 from a byte slice in little-endian order.
-///
-/// # Panics
-///
-/// Panics if `bytes.len()` is greater than 8.
+#[cfg(feature = "bloom")]
+#[inline(always)]
+fn read_u32_le(bytes: &[u8]) -> u32 {
+    u32::from_le_bytes(bytes.try_into().expect("four-byte hash input"))
+}
+
 #[cfg(any(
     feature = "bloom",
     feature = "countmin",
@@ -96,8 +97,7 @@ pub(crate) const DEFAULT_UPDATE_SEED: u64 = 9001;
     feature = "theta",
     feature = "tuple",
 ))]
+#[inline(always)]
 fn read_u64_le(bytes: &[u8]) -> u64 {
-    let mut buf = [0u8; 8];
-    buf[..bytes.len()].copy_from_slice(bytes);
-    u64::from_le_bytes(buf)
+    u64::from_le_bytes(bytes.try_into().expect("eight-byte hash input"))
 }
diff --git a/datasketches/src/hash/murmurhash.rs 
b/datasketches/src/hash/murmurhash.rs
index 1e0a383..5f62a40 100644
--- a/datasketches/src/hash/murmurhash.rs
+++ b/datasketches/src/hash/murmurhash.rs
@@ -35,6 +35,7 @@ pub struct MurmurHash3X64128 {
 }
 
 impl MurmurHash3X64128 {
+    #[inline(always)]
     pub fn with_seed(seed: u64) -> Self {
         MurmurHash3X64128 {
             h1: seed,
@@ -45,18 +46,18 @@ impl MurmurHash3X64128 {
         }
     }
 
+    #[inline(always)]
     pub fn finish128(&self) -> (u64, u64) {
         let mut h1 = self.h1;
         let mut h2 = self.h2;
 
-        let total = self.total + self.buf_len as u64;
         let rem = self.buf_len;
 
         // tail
         if rem > 0 {
             if rem > 8 {
                 // read k2 little endian
-                let mut k2 = read_u64_le(&self.buf[8..rem]);
+                let mut k2 = read_u64_le_partial(&self.buf[8..rem]);
                 // mix k2
                 k2 = k2.wrapping_mul(C2);
                 k2 = k2.rotate_left(33);
@@ -66,7 +67,7 @@ impl MurmurHash3X64128 {
 
             // read k1 little endian
             let k1_len = rem.min(8);
-            let mut k1 = read_u64_le(&self.buf[..k1_len]);
+            let mut k1 = read_u64_le_partial(&self.buf[..k1_len]);
             // mix k1
             k1 = k1.wrapping_mul(C1);
             k1 = k1.rotate_left(31);
@@ -74,8 +75,8 @@ impl MurmurHash3X64128 {
             h1 ^= k1;
         }
 
-        h1 ^= total;
-        h2 ^= total;
+        h1 ^= self.total;
+        h2 ^= self.total;
         h1 = h1.wrapping_add(h2);
         h2 = h2.wrapping_add(h1);
         h1 = fmix64(h1);
@@ -85,7 +86,7 @@ impl MurmurHash3X64128 {
         (h1, h2)
     }
 
-    #[inline]
+    #[inline(always)]
     fn update(&mut self, mut k1: u64, mut k2: u64) {
         // k1 *= c1; k1 = MURMUR3_ROTL64(k1, 31); k1 *= c2; out.h1 ^= k1;
         k1 = k1.wrapping_mul(C1);
@@ -108,9 +109,6 @@ impl MurmurHash3X64128 {
         self.h2 = self.h2.rotate_left(31);
         self.h2 = self.h2.wrapping_add(self.h1);
         self.h2 = self.h2.wrapping_mul(5).wrapping_add(0x38495ab5);
-
-        // accumulate total length
-        self.total += 16;
     }
 }
 
@@ -121,54 +119,54 @@ impl Default for MurmurHash3X64128 {
 }
 
 impl Hasher for MurmurHash3X64128 {
+    #[inline(always)]
     fn finish(&self) -> u64 {
         self.finish128().0
     }
 
+    #[inline(always)]
     fn write(&mut self, mut bytes: &[u8]) {
-        if self.buf_len + bytes.len() < 16 {
-            self.buf[self.buf_len..self.buf_len + 
bytes.len()].copy_from_slice(bytes);
-            self.buf_len += bytes.len();
-            return;
-        }
+        self.total = self.total.wrapping_add(bytes.len() as u64);
 
         if self.buf_len != 0 {
-            let wanted = 16 - self.buf_len;
-            self.buf[self.buf_len..].copy_from_slice(&bytes[..wanted]);
+            let copied = (16 - self.buf_len).min(bytes.len());
+            self.buf[self.buf_len..self.buf_len + 
copied].copy_from_slice(&bytes[..copied]);
+            self.buf_len += copied;
+            bytes = &bytes[copied..];
+
+            if self.buf_len < 16 {
+                return;
+            }
 
             let k1 = read_u64_le(&self.buf[0..8]);
             let k2 = read_u64_le(&self.buf[8..16]);
             self.update(k1, k2);
-
-            bytes = &bytes[wanted..];
             self.buf_len = 0;
         }
 
-        // Number of full 128-bit blocks of 16 bytes.
-        // Possible exclusion of a remainder of up to 15 bytes.
-        let blocks = bytes.len() >> 4; // bytes / 16
-
-        // Process the 128-bit blocks (the body) into the hash
-        for i in 0..blocks {
-            let lo = i << 4;
-            let mi = lo + 8;
-            let hi = mi + 8;
-            let k1 = read_u64_le(&bytes[lo..mi]);
-            let k2 = read_u64_le(&bytes[mi..hi]);
+        while bytes.len() >= 16 {
+            let k1 = read_u64_le(&bytes[..8]);
+            let k2 = read_u64_le(&bytes[8..16]);
             self.update(k1, k2);
+            bytes = &bytes[16..];
         }
 
-        // remain bytes
-        let len = bytes.len() % 16;
-        if len > 0 {
-            self.buf[0..len].copy_from_slice(&bytes[blocks << 4..]);
-            self.buf_len = len;
-        }
+        self.buf[..bytes.len()].copy_from_slice(bytes);
+        self.buf_len = bytes.len();
+    }
+}
+
+#[inline(always)]
+fn read_u64_le_partial(bytes: &[u8]) -> u64 {
+    let mut value = 0;
+    for (index, &byte) in bytes.iter().enumerate() {
+        value |= u64::from(byte) << (index * 8);
     }
+    value
 }
 
 /// Finalization mix: force all bits of a hash block to avalanche.
-#[inline]
+#[inline(always)]
 fn fmix64(mut k: u64) -> u64 {
     k ^= k >> 33;
     k = k.wrapping_mul(0xff51afd7ed558ccd);
diff --git a/datasketches/src/hash/value/canonical_float.rs 
b/datasketches/src/hash/value/canonical_float.rs
index 4c29b5b..06edd98 100644
--- a/datasketches/src/hash/value/canonical_float.rs
+++ b/datasketches/src/hash/value/canonical_float.rs
@@ -97,6 +97,7 @@ pub fn from_f64(v: f64) -> CanonicalFloat<f64> {
 }
 
 impl HashStrategy<f32> for CanonicalFloatStrategy {
+    #[inline(always)]
     fn hash<H: Hasher>(value: &f32, state: &mut H) {
         let value = *value as f64;
         let canonical_value = from_f64(value);
@@ -105,6 +106,7 @@ impl HashStrategy<f32> for CanonicalFloatStrategy {
 }
 
 impl HashStrategy<f64> for CanonicalFloatStrategy {
+    #[inline(always)]
     fn hash<H: Hasher>(value: &f64, state: &mut H) {
         let canonical = if value.is_nan() {
             // Java's Double.doubleToLongBits() NaN value.
diff --git a/datasketches/src/hash/value/mod.rs 
b/datasketches/src/hash/value/mod.rs
index ee42e19..88d87e7 100644
--- a/datasketches/src/hash/value/mod.rs
+++ b/datasketches/src/hash/value/mod.rs
@@ -166,6 +166,7 @@ impl<T: fmt::Display, S> fmt::Display for Value<T, S> {
 }
 
 impl<T, S: HashStrategy<T>> Hash for Value<T, S> {
+    #[inline(always)]
     fn hash<H: Hasher>(&self, state: &mut H) {
         S::hash(&self.value, state);
     }
diff --git a/datasketches/src/hash/value/natural_extend.rs 
b/datasketches/src/hash/value/natural_extend.rs
index 1b2ec4d..981c8b1 100644
--- a/datasketches/src/hash/value/natural_extend.rs
+++ b/datasketches/src/hash/value/natural_extend.rs
@@ -133,6 +133,7 @@ pub fn from_u32(v: u32) -> NaturalExtend<u32> {
 macro_rules! impl_natural_extend {
     ($t:ty, |$v:ident| $extended:expr) => {
         impl HashStrategy<$t> for NaturalExtendStrategy {
+            #[inline(always)]
             fn hash<H: Hasher>(value: &$t, state: &mut H) {
                 let $v = *value;
                 let extended = $extended;
diff --git a/datasketches/src/hash/value/raw_bytes.rs 
b/datasketches/src/hash/value/raw_bytes.rs
index 2b41735..0c48139 100644
--- a/datasketches/src/hash/value/raw_bytes.rs
+++ b/datasketches/src/hash/value/raw_bytes.rs
@@ -129,6 +129,7 @@ pub fn from_str(v: &str) -> RawBytes<&str> {
 macro_rules! impl_raw_bytes {
     ($t:ty, |$v:ident| $as_slice:expr) => {
         impl HashStrategy<$t> for RawBytesStrategy {
+            #[inline(always)]
             fn hash<H: Hasher>(value: &$t, state: &mut H) {
                 let $v = value;
                 let slice = $as_slice;
diff --git a/datasketches/src/hash/value/sign_extend.rs 
b/datasketches/src/hash/value/sign_extend.rs
index 561fbd1..cefcafe 100644
--- a/datasketches/src/hash/value/sign_extend.rs
+++ b/datasketches/src/hash/value/sign_extend.rs
@@ -160,6 +160,7 @@ pub fn from_u32(v: u32) -> SignExtend<u32> {
 macro_rules! impl_sign_extend {
     ($t:ty, |$v:ident| $extended:expr) => {
         impl HashStrategy<$t> for SignExtendStrategy {
+            #[inline(always)]
             fn hash<H: Hasher>(value: &$t, state: &mut H) {
                 let $v = *value;
                 let extended = $extended as u64;
diff --git a/datasketches/src/hash/xxhash.rs b/datasketches/src/hash/xxhash.rs
index 04c3e47..1ea5503 100644
--- a/datasketches/src/hash/xxhash.rs
+++ b/datasketches/src/hash/xxhash.rs
@@ -17,6 +17,7 @@
 
 use std::hash::Hasher;
 
+use crate::hash::read_u32_le;
 use crate::hash::read_u64_le;
 
 const DEFAULT_SEED: u64 = 0;
@@ -43,6 +44,7 @@ pub struct XxHash64 {
 }
 
 impl XxHash64 {
+    #[inline(always)]
     pub fn with_seed(seed: u64) -> Self {
         XxHash64 {
             seed,
@@ -56,6 +58,7 @@ impl XxHash64 {
         }
     }
 
+    #[inline(always)]
     pub fn finish64(&self) -> u64 {
         let mut hash = if self.total_len >= 32 {
             let mut acc = self
@@ -88,8 +91,8 @@ impl XxHash64 {
         }
 
         if idx + 4 <= buf.len() {
-            let k1 = read_u64_le(&buf[idx..idx + 4]);
-            hash ^= k1.wrapping_mul(P1);
+            let k1 = read_u32_le(&buf[idx..idx + 4]);
+            hash ^= u64::from(k1).wrapping_mul(P1);
             hash = hash.rotate_left(23).wrapping_mul(P2).wrapping_add(P3);
             idx += 4;
         }
@@ -105,6 +108,7 @@ impl XxHash64 {
     }
 
     #[allow(dead_code)]
+    #[inline(always)]
     pub fn hash_u64(input: u64, seed: u64) -> u64 {
         let mut hash = seed.wrapping_add(P5).wrapping_add(8);
         let mut k1 = input;
@@ -116,7 +120,7 @@ impl XxHash64 {
         finalize(hash)
     }
 
-    #[inline]
+    #[inline(always)]
     fn update(&mut self, chunk: &[u8]) {
         self.v1 = round(self.v1, read_u64_le(&chunk[0..8]));
         self.v2 = round(self.v2, read_u64_le(&chunk[8..16]));
@@ -132,10 +136,12 @@ impl Default for XxHash64 {
 }
 
 impl Hasher for XxHash64 {
+    #[inline(always)]
     fn finish(&self) -> u64 {
         self.finish64()
     }
 
+    #[inline(always)]
     fn write(&mut self, bytes: &[u8]) {
         self.total_len = self.total_len.wrapping_add(bytes.len() as u64);
 
@@ -169,14 +175,14 @@ impl Hasher for XxHash64 {
     }
 }
 
-#[inline]
+#[inline(always)]
 fn round(mut acc: u64, input: u64) -> u64 {
     acc = acc.wrapping_add(input.wrapping_mul(P2));
     acc = acc.rotate_left(31);
     acc.wrapping_mul(P1)
 }
 
-#[inline]
+#[inline(always)]
 fn merge_round(mut acc: u64, val: u64) -> u64 {
     let mut v = val;
     v = v.wrapping_mul(P2);
@@ -186,7 +192,7 @@ fn merge_round(mut acc: u64, val: u64) -> u64 {
     acc.wrapping_mul(P1).wrapping_add(P4)
 }
 
-#[inline]
+#[inline(always)]
 fn finalize(mut hash: u64) -> u64 {
     hash ^= hash >> 33;
     hash = hash.wrapping_mul(P2);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to