Skip to content

Commit f272983

Browse files
committed
Optimize sip::Hasher::short_write.
This commit changes `sip::Hasher` to use the faster `short_write` approach that was used for `SipHasher128` in #68914. This has no effect because `sip::Hasher::short_write` is currently unused. See the next commit for more details, and a fix. (One difference with #68914 is that this commit doesn't apply the `u8to64_le` change from that PR, because I found it is slower, because it introduces `memcpy` calls with non-statically-known lengths. Therefore, this commit also undoes the `u8to64_le` change in `SipHasher128` for this reason. This doesn't affect `SipHasher128` much because it doesn't use `u8to64_le` much, but I am making the change to keep the two implementations consistent.)
1 parent b1f395d commit f272983

File tree

1 file changed

+66
-30
lines changed

1 file changed

+66
-30
lines changed

src/libcore/hash/sip.rs

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -221,35 +221,76 @@ impl<S: Sip> Hasher<S> {
221221
self.ntail = 0;
222222
}
223223

224-
// Specialized write function that is only valid for buffers with len <= 8.
225-
// It's used to force inlining of write_u8 and write_usize, those would normally be inlined
226-
// except for composite types (that includes slices and str hashing because of delimiter).
227-
// Without this extra push the compiler is very reluctant to inline delimiter writes,
228-
// degrading performance substantially for the most common use cases.
224+
// A specialized write function for values with size <= 8.
225+
//
226+
// The hashing of multi-byte integers depends on endianness. E.g.:
227+
// - little-endian: `write_u32(0xDDCCBBAA)` == `write([0xAA, 0xBB, 0xCC, 0xDD])`
228+
// - big-endian: `write_u32(0xDDCCBBAA)` == `write([0xDD, 0xCC, 0xBB, 0xAA])`
229+
//
230+
// This function does the right thing for little-endian hardware. On
231+
// big-endian hardware `x` must be byte-swapped first to give the right
232+
// behaviour. After any byte-swapping, the input must be zero-extended to
233+
// 64-bits. The caller is responsible for the byte-swapping and
234+
// zero-extension.
229235
#[inline]
230-
fn short_write(&mut self, msg: &[u8]) {
231-
debug_assert!(msg.len() <= 8);
232-
let length = msg.len();
233-
self.length += length;
236+
fn short_write<T>(&mut self, _x: T, x: u64) {
237+
let size = mem::size_of::<T>();
238+
self.length += size;
234239

240+
// The original number must be zero-extended, not sign-extended.
241+
debug_assert!(if size < 8 { x >> (8 * size) == 0 } else { true });
242+
243+
// The number of bytes needed to fill `self.tail`.
235244
let needed = 8 - self.ntail;
236-
let fill = cmp::min(length, needed);
237-
if fill == 8 {
238-
self.tail = unsafe { load_int_le!(msg, 0, u64) };
239-
} else {
240-
self.tail |= unsafe { u8to64_le(msg, 0, fill) } << (8 * self.ntail);
241-
if length < needed {
242-
self.ntail += length;
243-
return;
244-
}
245+
246+
// SipHash parses the input stream as 8-byte little-endian integers.
247+
// Inputs are put into `self.tail` until 8 bytes of data have been
248+
// collected, and then that word is processed.
249+
//
250+
// For example, imagine that `self.tail` is 0x0000_00EE_DDCC_BBAA,
251+
// `self.ntail` is 5 (because 5 bytes have been put into `self.tail`),
252+
// and `needed` is therefore 3.
253+
//
254+
// - Scenario 1, `self.write_u8(0xFF)`: we have already zero-extended
255+
// the input to 0x0000_0000_0000_00FF. We now left-shift it five
256+
// bytes, giving 0x0000_FF00_0000_0000. We then bitwise-OR that value
257+
// into `self.tail`, resulting in 0x0000_FFEE_DDCC_BBAA.
258+
// (Zero-extension of the original input is critical in this scenario
259+
// because we don't want the high two bytes of `self.tail` to be
260+
// touched by the bitwise-OR.) `self.tail` is not yet full, so we
261+
// return early, after updating `self.ntail` to 6.
262+
//
263+
// - Scenario 2, `self.write_u32(0xIIHH_GGFF)`: we have already
264+
// zero-extended the input to 0x0000_0000_IIHH_GGFF. We now
265+
// left-shift it five bytes, giving 0xHHGG_FF00_0000_0000. We then
266+
// bitwise-OR that value into `self.tail`, resulting in
267+
// 0xHHGG_FFEE_DDCC_BBAA. `self.tail` is now full, and we can use it
268+
// to update `self.state`. (As mentioned above, this assumes a
269+
// little-endian machine; on a big-endian machine we would have
270+
// byte-swapped 0xIIHH_GGFF in the caller, giving 0xFFGG_HHII, and we
271+
// would then end up bitwise-ORing 0xGGHH_II00_0000_0000 into
272+
// `self.tail`).
273+
//
274+
self.tail |= x << (8 * self.ntail);
275+
if size < needed {
276+
self.ntail += size;
277+
return;
245278
}
279+
280+
// `self.tail` is full, process it.
246281
self.state.v3 ^= self.tail;
247282
S::c_rounds(&mut self.state);
248283
self.state.v0 ^= self.tail;
249284

250-
// Buffered tail is now flushed, process new input.
251-
self.ntail = length - needed;
252-
self.tail = unsafe { u8to64_le(msg, needed, self.ntail) };
285+
// Continuing scenario 2: we have one byte left over from the input. We
286+
// set `self.ntail` to 1 and `self.tail` to `0x0000_0000_IIHH_GGFF >>
287+
// 8*3`, which is 0x0000_0000_0000_00II. (Or on a big-endian machine
288+
// the prior byte-swapping would leave us with 0x0000_0000_0000_00FF.)
289+
//
290+
// The `if` is needed to avoid shifting by 64 bits, which Rust
291+
// complains about.
292+
self.ntail = size - needed;
293+
self.tail = if needed < 8 { x >> (8 * needed) } else { 0 };
253294
}
254295
}
255296

@@ -280,19 +321,14 @@ impl super::Hasher for SipHasher13 {
280321
}
281322

282323
impl<S: Sip> super::Hasher for Hasher<S> {
283-
// see short_write comment for explanation
284324
#[inline]
285-
fn write_usize(&mut self, i: usize) {
286-
let bytes = unsafe {
287-
crate::slice::from_raw_parts(&i as *const usize as *const u8, mem::size_of::<usize>())
288-
};
289-
self.short_write(bytes);
325+
fn write_u8(&mut self, i: u8) {
326+
self.short_write(i, i as u64);
290327
}
291328

292-
// see short_write comment for explanation
293329
#[inline]
294-
fn write_u8(&mut self, i: u8) {
295-
self.short_write(&[i]);
330+
fn write_usize(&mut self, i: usize) {
331+
self.short_write(i, i.to_le() as u64);
296332
}
297333

298334
#[inline]

0 commit comments

Comments
 (0)