Skip to content

Commit 3f84916

Browse files
committed
Seed HashMaps thread-locally, straight from the OS.
This reduces how much rand is required in std, and makes creating hash-maps noticably faster. The first invocation of HashMap::new() in a thread goes from ~130_000 ns to ~1500 ns (i.e. 86x faster) and later invocations go from 10-20 ns to a more consistent 1.8-2.0ns. The mean for many invocations in a loop, on a single thread, goes from ~77ns to ~1.9ns, and the standard deviation drops from 2800ns to 33ns (the *massive* variance of the old scheme comes from the occasional reseeding of the thread rng). These new numbers are only slightly higher than creating the `RandomState` outside the loop and calling `with_state` in it (i.e. only measuring the non-random parts of hashmap initialisation): 1.3 and 18 ns respectively. This new scheme has the slight downside of being consistent on a single thread, so users may unintentionally rely on the order being fixed (e.g. two hashmaps with the same contents). Closes #27243.
1 parent 7cae6b5 commit 3f84916

File tree

3 files changed

+293
-6
lines changed

3 files changed

+293
-6
lines changed

src/libstd/collections/hash/map.rs

+17-6
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ use borrow::Borrow;
1616
use cmp::max;
1717
use fmt::{self, Debug};
1818
use hash::{Hash, SipHasher, BuildHasher};
19+
use internal_rand;
1920
use iter::{self, Map, FromIterator};
2021
use mem::{self, replace};
2122
use ops::{Deref, Index};
22-
use rand::{self, Rng};
2323

2424
use super::table::{
2525
self,
@@ -1648,9 +1648,10 @@ impl<'a, K, V, S> Extend<(&'a K, &'a V)> for HashMap<K, V, S>
16481648

16491649
/// `RandomState` is the default state for `HashMap` types.
16501650
///
1651-
/// A particular instance `RandomState` will create the same instances of
1652-
/// `Hasher`, but the hashers created by two different `RandomState`
1653-
/// instances are unlikely to produce the same result for the same values.
1651+
/// A particular instance `RandomState` will create the same instances
1652+
/// of `Hasher`, but there is no guarantee that hashers created by two
1653+
/// different `RandomState` instances will produce the same result for
1654+
/// the same values.
16541655
#[derive(Clone)]
16551656
#[stable(feature = "hashmap_build_hasher", since = "1.7.0")]
16561657
pub struct RandomState {
@@ -1664,8 +1665,18 @@ impl RandomState {
16641665
#[allow(deprecated)] // rand
16651666
#[stable(feature = "hashmap_build_hasher", since = "1.7.0")]
16661667
pub fn new() -> RandomState {
1667-
let mut r = rand::thread_rng();
1668-
RandomState { k0: r.gen(), k1: r.gen() }
1668+
thread_local!(static KEYS: (u64, u64) = {
1669+
// get some random bytes from the OS
1670+
let mut bytes = [0_u8; 16];
1671+
internal_rand::fill_bytes(&mut bytes);
1672+
1673+
let keys: (u64, u64) = unsafe { mem::transmute(bytes) };
1674+
keys
1675+
});
1676+
1677+
KEYS.with(|&(k0, k1)| {
1678+
RandomState { k0: k0, k1: k1 }
1679+
})
16691680
}
16701681
}
16711682

src/libstd/internal_rand.rs

+275
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! Interface to the operating system provided random number
12+
//! generators:
13+
//!
14+
//! - Unix-like systems (Linux, Android, Mac OSX): read directly from
15+
//! `/dev/urandom`, or from `getrandom(2)` system call if available.
16+
//! - Windows: calls `CryptGenRandom`, using the default cryptographic
17+
//! service provider with the `PROV_RSA_FULL` type.
18+
//! - iOS: calls SecRandomCopyBytes as /dev/(u)random is sandboxed.
19+
//! - OpenBSD: uses the `getentropy(2)` system call.
20+
21+
22+
pub use self::imp::fill_bytes;
23+
24+
#[cfg(all(unix, not(target_os = "ios"), not(target_os = "openbsd")))]
25+
mod imp {
26+
use fs::File;
27+
use io::{self, Read};
28+
use libc;
29+
use sys::os::errno;
30+
31+
#[cfg(all(target_os = "linux",
32+
any(target_arch = "x86_64",
33+
target_arch = "x86",
34+
target_arch = "arm",
35+
target_arch = "aarch64",
36+
target_arch = "powerpc",
37+
target_arch = "powerpc64",
38+
target_arch = "powerpc64le")))]
39+
fn getrandom(buf: &mut [u8]) -> libc::c_long {
40+
#[cfg(target_arch = "x86_64")]
41+
const NR_GETRANDOM: libc::c_long = 318;
42+
#[cfg(target_arch = "x86")]
43+
const NR_GETRANDOM: libc::c_long = 355;
44+
#[cfg(target_arch = "arm")]
45+
const NR_GETRANDOM: libc::c_long = 384;
46+
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64",
47+
target_arch = "powerpc64le"))]
48+
const NR_GETRANDOM: libc::c_long = 359;
49+
#[cfg(target_arch = "aarch64")]
50+
const NR_GETRANDOM: libc::c_long = 278;
51+
52+
unsafe {
53+
libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), 0)
54+
}
55+
}
56+
57+
#[cfg(not(all(target_os = "linux",
58+
any(target_arch = "x86_64",
59+
target_arch = "x86",
60+
target_arch = "arm",
61+
target_arch = "aarch64",
62+
target_arch = "powerpc",
63+
target_arch = "powerpc64",
64+
target_arch = "powerpc64le"))))]
65+
fn getrandom(_buf: &mut [u8]) -> libc::c_long { -1 }
66+
67+
fn getrandom_fill_bytes(v: &mut [u8]) {
68+
let mut read = 0;
69+
while read < v.len() {
70+
let result = getrandom(&mut v[read..]);
71+
if result == -1 {
72+
let err = errno() as libc::c_int;
73+
if err == libc::EINTR {
74+
continue;
75+
} else {
76+
panic!("unexpected getrandom error: {}", err);
77+
}
78+
} else {
79+
read += result as usize;
80+
}
81+
}
82+
}
83+
84+
#[cfg(all(target_os = "linux",
85+
any(target_arch = "x86_64",
86+
target_arch = "x86",
87+
target_arch = "arm",
88+
target_arch = "aarch64",
89+
target_arch = "powerpc",
90+
target_arch = "powerpc64",
91+
target_arch = "powerpc64le")))]
92+
fn is_getrandom_available() -> bool {
93+
use sync::atomic::{AtomicBool, Ordering};
94+
use sync::Once;
95+
96+
static CHECKER: Once = Once::new();
97+
static AVAILABLE: AtomicBool = AtomicBool::new(false);
98+
99+
CHECKER.call_once(|| {
100+
let mut buf: [u8; 0] = [];
101+
let result = getrandom(&mut buf);
102+
let available = if result == -1 {
103+
let err = io::Error::last_os_error().raw_os_error();
104+
err != Some(libc::ENOSYS)
105+
} else {
106+
true
107+
};
108+
AVAILABLE.store(available, Ordering::Relaxed);
109+
});
110+
111+
AVAILABLE.load(Ordering::Relaxed)
112+
}
113+
114+
#[cfg(not(all(target_os = "linux",
115+
any(target_arch = "x86_64",
116+
target_arch = "x86",
117+
target_arch = "arm",
118+
target_arch = "aarch64",
119+
target_arch = "powerpc",
120+
target_arch = "powerpc64",
121+
target_arch = "powerpc64le"))))]
122+
fn is_getrandom_available() -> bool { false }
123+
124+
pub fn fill_bytes(b: &mut [u8]) {
125+
if is_getrandom_available() {
126+
getrandom_fill_bytes(b)
127+
} else {
128+
let mut reader = File::open("/dev/urandom").expect("failed to open /dev/urandom");
129+
reader.read_exact(b).expect("failed to read bytes from /dev/urandom");
130+
}
131+
}
132+
}
133+
134+
#[cfg(target_os = "openbsd")]
135+
mod imp {
136+
use libc;
137+
use sys::os::errno;
138+
139+
pub fn fill_bytes(v: &mut [u8]) {
140+
// getentropy(2) permits a maximum buffer size of 256 bytes
141+
for s in v.chunks_mut(256) {
142+
let ret = unsafe {
143+
libc::getentropy(s.as_mut_ptr() as *mut libc::c_void, s.len())
144+
};
145+
if ret == -1 {
146+
panic!("unexpected getentropy error: {}", errno());
147+
}
148+
}
149+
}
150+
}
151+
152+
#[cfg(target_os = "ios")]
153+
mod imp {
154+
use io;
155+
use ptr;
156+
use libc::{c_int, size_t};
157+
158+
enum SecRandom {}
159+
160+
#[allow(non_upper_case_globals)]
161+
const kSecRandomDefault: *const SecRandom = ptr::null();
162+
163+
#[link(name = "Security", kind = "framework")]
164+
extern "C" {
165+
fn SecRandomCopyBytes(rnd: *const SecRandom,
166+
count: size_t, bytes: *mut u8) -> c_int;
167+
}
168+
169+
pub fn fill_bytes(v: &mut [u8]) {
170+
let ret = unsafe {
171+
SecRandomCopyBytes(kSecRandomDefault, v.len() as size_t,
172+
v.as_mut_ptr())
173+
};
174+
if ret == -1 {
175+
panic!("couldn't generate random bytes: {}",
176+
io::Error::last_os_error());
177+
}
178+
}
179+
}
180+
181+
#[cfg(windows)]
182+
mod imp {
183+
use io;
184+
use sys::c;
185+
186+
// SBRM struct to ensure the cryptography context gets cleaned up
187+
// properly
188+
struct OsRng {
189+
hcryptprov: c::HCRYPTPROV
190+
}
191+
192+
impl OsRng {
193+
/// Create a new `OsRng`.
194+
pub fn new() -> io::Result<OsRng> {
195+
let mut hcp = 0;
196+
let ret = unsafe {
197+
c::CryptAcquireContextA(&mut hcp, 0 as c::LPCSTR, 0 as c::LPCSTR,
198+
c::PROV_RSA_FULL,
199+
c::CRYPT_VERIFYCONTEXT | c::CRYPT_SILENT)
200+
};
201+
202+
if ret == 0 {
203+
Err(io::Error::last_os_error())
204+
} else {
205+
Ok(OsRng { hcryptprov: hcp })
206+
}
207+
}
208+
}
209+
impl Drop for OsRng {
210+
fn drop(&mut self) {
211+
let ret = unsafe {
212+
c::CryptReleaseContext(self.hcryptprov, 0)
213+
};
214+
if ret == 0 {
215+
panic!("couldn't release context: {}",
216+
io::Error::last_os_error());
217+
}
218+
}
219+
}
220+
221+
pub fn fill_bytes(v: &mut [u8]) {
222+
let os = OsRng::new().expect("failed to acquire crypt context for randomness");
223+
let ret = unsafe {
224+
c::CryptGenRandom(self.hcryptprov, v.len() as c::DWORD,
225+
v.as_mut_ptr())
226+
};
227+
if ret == 0 {
228+
panic!("couldn't generate random bytes: {}",
229+
io::Error::last_os_error());
230+
}
231+
}
232+
233+
}
234+
235+
#[cfg(test)]
236+
mod tests {
237+
use sync::mpsc::channel;
238+
use super::fill_bytes;
239+
use thread;
240+
241+
#[test]
242+
fn test_fill_bytes() {
243+
let mut v = [0; 1000];
244+
fill_bytes(&mut v);
245+
}
246+
247+
#[test]
248+
fn test_fill_bytes_tasks() {
249+
250+
let mut txs = vec!();
251+
for _ in 0..20 {
252+
let (tx, rx) = channel();
253+
txs.push(tx);
254+
255+
thread::spawn(move|| {
256+
// wait until all the threads are ready to go.
257+
rx.recv().unwrap();
258+
259+
let mut v = [0; 1000];
260+
261+
for _ in 0..100 {
262+
fill_bytes(&mut v);
263+
// deschedule to attempt to interleave things as much
264+
// as possible (XXX: is this a good test?)
265+
thread::yield_now();
266+
}
267+
});
268+
}
269+
270+
// start all the threads
271+
for tx in &txs {
272+
tx.send(()).unwrap();
273+
}
274+
}
275+
}

src/libstd/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ mod memchr;
436436
pub mod rt;
437437
mod panicking;
438438
mod rand;
439+
mod internal_rand;
439440

440441
// Some external utilities of the standard library rely on randomness (aka
441442
// rustc_back::TempDir and tests) and need a way to get at the OS rng we've got

0 commit comments

Comments
 (0)