ElBlo
rdseed behavior under load
The rdrand
and
rdseed
instructions can be used
to generate random numbers. These instructions can fail, and they indicate
their success by setting the Carry Flag in the EFLAGS
register.
The Intel Documentation1 says that rdseed
could fail if we ask for values faster than they can
be generated.
This simple program helps you test that:
use std::arch::asm;
use std::env;
use std::thread;
use std::time::{Duration, Instant};
fn rdrand_count(its: usize) -> (u64, Duration) {
let zero: u64 = 0;
let mut count: u64 = 0;
let now = Instant::now();
for _ in 0..its {
unsafe {
asm!(
"rdrand {tmp}",
"adc {count}, {zero}",
zero = in(reg) zero,
count = inout(reg) count,
tmp = out(reg) _
);
}
}
(count, now.elapsed())
}
fn rdseed_count(its: usize) -> (u64, Duration) {
let zero: u64 = 0;
let mut count: u64 = 0;
let now = Instant::now();
for _ in 0..its {
unsafe {
asm!(
"rdseed {tmp}",
"adc {count}, {zero}",
zero = in(reg) zero,
count = inout(reg) count,
tmp = out(reg) _
);
}
}
(count, now.elapsed())
}
fn parse_flags(args: &Vec<String>) -> (fn(usize) -> (u64, Duration), usize, usize) {
println!("{:?}", args);
// args[1] should have the function name.
let func = if args.len() > 1 {
match &args[1][..] {
"rdseed" => rdseed_count,
"rdrand" => rdrand_count,
_ => {
panic!(
"invalid function {}. usage: {} rdrand/rdseed iterations threads",
args[1], args[0]
);
}
}
} else {
rdseed_count
};
let tries = if args.len() > 2 {
args[2].parse().unwrap()
} else {
4000
};
let num_threads = if args.len() > 3 {
args[3].parse().unwrap()
} else {
2
};
return (func, tries, num_threads);
}
fn main() {
let args: Vec<String> = env::args().collect();
let (func, tries, num_threads) = parse_flags(&args);
let mut threads = vec![];
for _ in 0..num_threads {
threads.push(thread::spawn(move || loop {
func(tries);
}));
}
for _ in 0..100 {
let (successes, elapsed) = func(tries);
println!("{}/{}\t{:?}", successes, tries, elapsed);
}
}
The idea is that you launch background threads that run the rdrand
/rdseed
instructions in a loop, and then the main thread does the same and measures how
long it took and how many of them were successful.
I’ve tested this on an AMD Ryzen Threadripper PRO 3995WX
, and I couldn’t get
either rdrand
or rdseed
to fail. Instead, the instructions get way slower
as we add more load to the system.
I’ve also tested it on an Intel Xeon Gold 6154
, and the behavior there is
different: I got a 0.0175 success rate on average using 16/72 threads; after
that, the success rate is more or less the same, but the performance starts
decreasing.