Open
Description
I've discovered an issue with IoError
, and really returning any enums that are larger than 1 word, are running an order of magnitude slower than returning an error enum that's 1 word size. Here's my test case:
extern crate test;
use std::mem;
use std::vec;
use std::io;
const BUFFER_SIZE: uint = 128;
//////////////////////////////////////////////////////////////////////////////
trait Error {
fn is_eof(&self) -> bool;
}
impl Error for io::IoError {
fn is_eof(&self) -> bool {
self.kind == io::EndOfFile
}
}
#[deriving(Show, PartialEq, Eq)]
enum MyError {
EndOfFile,
Error,
_Error1,
}
impl Error for MyError {
fn is_eof(&self) -> bool {
*self == MyError::EndOfFile
}
}
#[deriving(Show, PartialEq, Eq)]
enum MyError2 {
EndOfFile,
Error,
_Error1(uint),
}
impl Error for MyError2 {
fn is_eof(&self) -> bool {
*self == MyError2::EndOfFile
}
}
impl Error for () {
fn is_eof(&self) -> bool {
true
}
}
impl Error for Box<MyError> {
fn is_eof(&self) -> bool {
**self == MyError::EndOfFile
}
}
//////////////////////////////////////////////////////////////////////////////
fn generate_bytes() -> Vec<u8> {
let mut bytes = Vec::new();
for i in range(0i, 1024) {
bytes.push(i as u8);
}
bytes
}
//////////////////////////////////////////////////////////////////////////////
struct Foo11<'a, E> {
iter: vec::MoveItems<u8>,
f: |&mut Vec<u8>|: 'a -> Result<(), E>,
}
impl<'a, E: Error> Foo11<'a, E> {
fn new<'a>(f: |&mut Vec<u8>|: 'a -> Result<(), E>) -> Foo11<'a, E> {
let buf = Vec::with_capacity(BUFFER_SIZE);
Foo11 {
iter: buf.into_iter(),
f: f,
}
}
fn fill_buf(&mut self) -> Result<bool, E> {
let mut iter = Vec::new().into_iter();
mem::swap(&mut iter, &mut self.iter);
let mut buf = iter.into_inner();
buf.clear();
try!((self.f)(&mut buf));
if buf.is_empty() {
Ok(false)
} else {
self.iter = buf.into_iter();
Ok(true)
}
}
}
impl<'a, E: Error> Iterator<Result<u8, E>> for Foo11<'a, E> {
fn next(&mut self) -> Option<Result<u8, E>> {
loop {
match self.iter.next() {
Some(value) => { return Some(Ok(value)); }
None => {
match self.fill_buf() {
Ok(false) => { return None; }
Ok(true) => { }
Err(err) => { return Some(Err(err)); }
}
}
}
}
}
}
#[bench]
fn bench_foo11_ioerror(b: &mut test::Bencher) {
let bytes = generate_bytes();
b.bytes = bytes.len() as u64;
b.iter(|| {
let mut rdr = bytes.as_slice();
let iter = Foo11::new(|buf| -> Result<(), io::IoError> {
match rdr.push(BUFFER_SIZE, buf) {
Ok(_) => Ok(()),
Err(io::IoError { kind: io::EndOfFile, .. }) => Ok(()),
Err(err) => Err(err),
}
});
for (idx, item) in iter.enumerate() {
assert_eq!(idx as u8, item.unwrap());
}
})
}
#[bench]
fn bench_foo11_enum_one_word(b: &mut test::Bencher) {
let bytes = generate_bytes();
b.bytes = bytes.len() as u64;
b.iter(|| {
let mut rdr = bytes.as_slice();
let iter = Foo11::new(|buf| -> Result<(), MyError> {
match rdr.push(BUFFER_SIZE, buf) {
Ok(_) => Ok(()),
Err(io::IoError { kind: io::EndOfFile, .. }) => Ok(()),
Err(_) => Err(MyError::Error),
}
});
for (idx, item) in iter.enumerate() {
assert_eq!(idx as u8, item.unwrap());
}
})
}
#[bench]
fn bench_foo11_null(b: &mut test::Bencher) {
let bytes = generate_bytes();
b.bytes = bytes.len() as u64;
b.iter(|| {
let mut rdr = bytes.as_slice();
let iter = Foo11::new(|buf| -> Result<(), ()> {
match rdr.push(BUFFER_SIZE, buf) {
Ok(_) => Ok(()),
Err(io::IoError { kind: io::EndOfFile, .. }) => Ok(()),
Err(_) => Ok(()), //{ panic!() }
}
});
for (idx, item) in iter.enumerate() {
assert_eq!(idx as u8, item.unwrap());
}
})
}
#[bench]
fn bench_foo11_enum_2_words(b: &mut test::Bencher) {
let bytes = generate_bytes();
b.bytes = bytes.len() as u64;
b.iter(|| {
let mut rdr = bytes.as_slice();
let iter = Foo11::new(|buf| -> Result<(), MyError2> {
match rdr.push(BUFFER_SIZE, buf) {
Ok(_) => Ok(()),
Err(io::IoError { kind: io::EndOfFile, .. }) => Ok(()),
Err(_) => Err(MyError2::Error),
}
});
for (idx, item) in iter.enumerate() {
assert_eq!(idx as u8, item.unwrap());
}
})
}
#[bench]
fn bench_foo11_boxed(b: &mut test::Bencher) {
let bytes = generate_bytes();
b.bytes = bytes.len() as u64;
b.iter(|| {
let mut rdr = bytes.as_slice();
let iter = Foo11::new(|buf| -> Result<(), Box<MyError>> {
match rdr.push(BUFFER_SIZE, buf) {
Ok(_) => Ok(()),
Err(io::IoError { kind: io::EndOfFile, .. }) => Ok(()),
Err(_) => Err(box MyError::Error),
}
});
for (idx, item) in iter.enumerate() {
assert_eq!(idx as u8, item.unwrap());
}
})
}
Here are the results:
test bench_foo11_boxed ... bench: 13754 ns/iter (+/- 4222) = 74 MB/s
test bench_foo11_enum_2_words ... bench: 15027 ns/iter (+/- 4318) = 68 MB/s
test bench_foo11_enum_one_word ... bench: 1550 ns/iter (+/- 417) = 660 MB/s
test bench_foo11_ioerror ... bench: 25003 ns/iter (+/- 8007) = 40 MB/s
test bench_foo11_null ... bench: 817 ns/iter (+/- 206) = 1253 MB/s
On a related note, @alexcrichton just found a similar case with:
extern crate test;
use std::iter::repeat;
const N: uint = 100000;
#[deriving(Clone)]
struct Foo;
#[deriving(Clone)]
struct Bar {
name: &'static str,
desc: Option<String>,
other: Option<String>,
}
#[bench]
fn b1(b: &mut test::Bencher) {
b.iter(|| {
let r: Result<u8, Foo> = Ok(1u8);
repeat(r).take(N).map(|x| test::black_box(x)).count()
});
}
#[bench]
fn b2(b: &mut test::Bencher) {
b.iter(|| {
let r: Result<u8, Bar> = Ok(1u8);
repeat(r).take(N).map(|x| test::black_box(x)).count()
});
}
The assembly for b1
is about 3 instructions, but the assembly for b2
has a ton of mov
instructions.