Skip to content

optimzed SipHash implementation #13522

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

Merged
merged 1 commit into from
Apr 16, 2014
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 143 additions & 42 deletions src/libstd/hash/sip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@
use clone::Clone;
use container::Container;
use default::Default;
use int;
use io::{IoResult, Writer};
use iter::Iterator;
use result::Ok;
use slice::ImmutableVector;
use uint;

use super::{Hash, Hasher};

Expand All @@ -43,7 +45,7 @@ pub struct SipState {
v1: u64,
v2: u64,
v3: u64,
tail: [u8, ..8], // unprocessed bytes
tail: u64, // unprocessed bytes le
ntail: uint, // how many bytes in tail are valid
}

Expand All @@ -60,7 +62,17 @@ macro_rules! u8to64_le (
$buf[4+$i] as u64 << 32 |
$buf[5+$i] as u64 << 40 |
$buf[6+$i] as u64 << 48 |
$buf[7+$i] as u64 << 56)
$buf[7+$i] as u64 << 56);
($buf:expr, $i:expr, $len:expr) =>
({
let mut t = 0;
let mut out = 0u64;
while t < $len {
out |= $buf[t+$i] as u64 << t*8;
t += 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not a for loop? (And why not use the vector iterator + .enumerate() to avoid the bounds checks?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought of usingusing a range iter, but a cursory glance made me think it brought more overhead and branches. I'll try a bench using for, and if it's faster, I can switch it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking something to avoid the indexing with the associated bounds checks and unwinding, something like:

for (t, x) in $buf.slice($i, $i + $len).enumerate() {
    out |= *x << t * 8
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing to that added 2ns/iter to each bench.

}
out
});
)

macro_rules! rotl (
Expand Down Expand Up @@ -98,7 +110,7 @@ impl SipState {
v1: 0,
v2: 0,
v3: 0,
tail: [ 0, 0, 0, 0, 0, 0, 0, 0 ],
tail: 0,
ntail: 0,
};
state.reset();
Expand All @@ -124,15 +136,7 @@ impl SipState {
let mut v2 = self.v2;
let mut v3 = self.v3;

let mut b : u64 = (self.length as u64 & 0xff) << 56;

if self.ntail > 0 { b |= self.tail[0] as u64 << 0; }
if self.ntail > 1 { b |= self.tail[1] as u64 << 8; }
if self.ntail > 2 { b |= self.tail[2] as u64 << 16; }
if self.ntail > 3 { b |= self.tail[3] as u64 << 24; }
if self.ntail > 4 { b |= self.tail[4] as u64 << 32; }
if self.ntail > 5 { b |= self.tail[5] as u64 << 40; }
if self.ntail > 6 { b |= self.tail[6] as u64 << 48; }
let b: u64 = ((self.length as u64 & 0xff) << 56) | self.tail;

v3 ^= b;
compress!(v0, v1, v2, v3);
Expand All @@ -147,8 +151,38 @@ impl SipState {

v0 ^ v1 ^ v2 ^ v3
}

#[inline]
fn write_le(&mut self, n: u64, size: uint) {
self.tail |= n << 8*self.ntail;
self.ntail += size;

if self.ntail >= 8 {
let m = self.tail;

self.v3 ^= m;
compress!(self.v0, self.v1, self.v2, self.v3);
compress!(self.v0, self.v1, self.v2, self.v3);
self.v0 ^= m;

self.ntail -= 8;
if self.ntail == 0 {
self.tail = 0;
} else {
self.tail = n >> 64 - 8*self.ntail;
}
}
}
}

macro_rules! make_write_le(
($this:expr, $n:expr, $size:expr) => ({
$this.write_le($n as u64, $size);
$this.length += $size;
Ok(())
})
)

impl Writer for SipState {
#[inline]
fn write(&mut self, msg: &[u8]) -> IoResult<()> {
Expand All @@ -159,24 +193,13 @@ impl Writer for SipState {

if self.ntail != 0 {
needed = 8 - self.ntail;

if length < needed {
let mut t = 0;
while t < length {
self.tail[self.ntail+t] = msg[t];
t += 1;
}
self.tail |= u8to64_le!(msg, 0, length) << 8*self.ntail;
self.ntail += length;
return Ok(());
}

let mut t = 0;
while t < needed {
self.tail[self.ntail+t] = msg[t];
t += 1;
}

let m = u8to64_le!(self.tail, 0);
let m = self.tail | u8to64_le!(msg, 0, needed) << 8*self.ntail;

self.v3 ^= m;
compress!(self.v0, self.v1, self.v2, self.v3);
Expand All @@ -203,15 +226,62 @@ impl Writer for SipState {
i += 8;
}

let mut t = 0u;
while t < left {
self.tail[t] = msg[i+t];
t += 1
}
self.tail = u8to64_le!(msg, i, left);
self.ntail = left;

Ok(())
}

#[inline]
fn write_u8(&mut self, n: u8) -> IoResult<()> {
make_write_le!(self, n, 1)
}

#[inline]
fn write_le_u16(&mut self, n: u16) -> IoResult<()> {
make_write_le!(self, n, 2)
}

#[inline]
fn write_le_u32(&mut self, n: u32) -> IoResult<()> {
make_write_le!(self, n, 4)
}

#[inline]
fn write_le_u64(&mut self, n: u64) -> IoResult<()> {
make_write_le!(self, n, 8)
}

#[inline]
fn write_le_uint(&mut self, n: uint) -> IoResult<()> {
make_write_le!(self, n, uint::BYTES)
}

#[inline]
fn write_i8(&mut self, n: i8) -> IoResult<()> {
make_write_le!(self, n, 1)
}

#[inline]
fn write_le_i16(&mut self, n: i16) -> IoResult<()> {
make_write_le!(self, n, 2)
}

#[inline]
fn write_le_i32(&mut self, n: i32) -> IoResult<()> {
make_write_le!(self, n, 4)
}

#[inline]
fn write_le_i64(&mut self, n: i64) -> IoResult<()> {
make_write_le!(self, n, 8)
}

#[inline]
fn write_le_int(&mut self, n: int) -> IoResult<()> {
make_write_le!(self, n, int::BYTES)
}

}

impl Clone for SipState {
Expand Down Expand Up @@ -284,6 +354,8 @@ pub fn hash_with_keys<T: Hash<SipState>>(k0: u64, k1: u64, value: &T) -> u64 {
state.result()
}



#[cfg(test)]
mod tests {
extern crate test;
Expand Down Expand Up @@ -517,28 +589,57 @@ mod tests {
}

#[bench]
fn bench_str(b: &mut Bencher) {
fn bench_str_under_8_bytes(b: &mut Bencher) {
let s = "foo";
b.iter(|| {
assert_eq!(hash(&s), 16262950014981195938);
})
}

#[bench]
fn bench_str_of_8_bytes(b: &mut Bencher) {
let s = "foobar78";
b.iter(|| {
assert_eq!(hash(&s), 4898293253460910787);
})
}

#[bench]
fn bench_str_over_8_bytes(b: &mut Bencher) {
let s = "foobarbaz0";
b.iter(|| {
assert_eq!(hash(&s), 10581415515220175264);
})
}

#[bench]
fn bench_long_str(b: &mut Bencher) {
let s = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor \
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud \
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute \
irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla \
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui \
officia deserunt mollit anim id est laborum.";
b.iter(|| {
assert_eq!(hash(&s), 17717065544121360093);
})
}

#[bench]
fn bench_u64(b: &mut Bencher) {
let u = 16262950014981195938u64;
b.iter(|| {
assert_eq!(hash(&u), 5254097107239593357);
})
}

#[deriving(Hash)]
struct Compound {
x: u8,
y: u16,
y: u64,
z: ~str,
}

impl<S: Writer> Hash<S> for Compound {
#[inline]
fn hash(&self, state: &mut S) {
self.x.hash(state);
self.y.hash(state);
self.z.hash(state);
}
}

#[bench]
fn bench_compound_1(b: &mut Bencher) {
let compound = Compound {
Expand All @@ -547,7 +648,7 @@ mod tests {
z: ~"foobarbaz",
};
b.iter(|| {
assert_eq!(hash(&compound), 3581836382593270478);
assert_eq!(hash(&compound), 15783192367317361799);
})
}
}