From 946e384660810c46d86ee859af5bbce11112ee5d Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Wed, 25 Nov 2015 21:05:11 +0100 Subject: [PATCH] Some benchmarks and low-hanging speed improvements --- Cargo.toml | 1 + src/benches.rs | 86 +++++++++++++++++++++++++++++++++++++++++++++++ src/cloud.rs | 4 +-- src/device.rs | 3 ++ src/ethernet.rs | 26 +++++++------- src/ip.rs | 26 ++++++++++---- src/main.rs | 5 +++ src/types.rs | 45 ++++++++++++++++--------- src/udpmessage.rs | 13 +++---- src/util.rs | 11 +----- 10 files changed, 166 insertions(+), 54 deletions(-) create mode 100644 src/benches.rs diff --git a/Cargo.toml b/Cargo.toml index ccebda8..b70d077 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ gcc = "0.3" [features] default = [] crypto = ["libsodium-sys"] #not default as it requires external library +bench = [] diff --git a/src/benches.rs b/src/benches.rs new file mode 100644 index 0000000..a3d1ad1 --- /dev/null +++ b/src/benches.rs @@ -0,0 +1,86 @@ +use test::Bencher; + +use time::{SteadyTime, Duration}; + +use std::str::FromStr; +use std::net::ToSocketAddrs; + +use super::udpmessage::{Options, Message, encode, decode}; +use super::crypto::Crypto; +use super::ethernet::{Frame, SwitchTable}; +use super::types::{Address, Table, Protocol}; +use super::ip::Packet; + + +#[bench] +fn message_encode(b: &mut Bencher) { + let mut options = Options::default(); + let mut crypto = Crypto::None; + let payload = [0; 1400]; + let msg = Message::Data(&payload); + let mut buf = [0; 1500]; + b.iter(|| { + encode(&mut options, &msg, &mut buf[..], &mut crypto); + }); +} + +#[bench] +fn message_decode(b: &mut Bencher) { + let mut options = Options::default(); + let mut crypto = Crypto::None; + let payload = [0; 1400]; + let msg = Message::Data(&payload); + let mut buf = [0; 1500]; + let size = encode(&mut options, &msg, &mut buf[..], &mut crypto); + b.iter(|| { + decode(&mut buf[..size], &mut crypto).unwrap(); + }); +} + +#[bench] +fn switch_learn(b: &mut Bencher) { + let mut table = SwitchTable::new(Duration::seconds(10)); + let addr = Address::from_str("12:34:56:78:90:ab").unwrap(); + let peer = "1.2.3.4:5678".to_socket_addrs().unwrap().next().unwrap(); + b.iter(|| { + table.learn(addr.clone(), None, peer); + }) +} + +#[bench] +fn switch_lookup(b: &mut Bencher) { + let mut table = SwitchTable::new(Duration::seconds(10)); + let addr = Address::from_str("12:34:56:78:90:ab").unwrap(); + let peer = "1.2.3.4:5678".to_socket_addrs().unwrap().next().unwrap(); + table.learn(addr.clone(), None, peer); + b.iter(|| { + table.lookup(&addr); + }) + +} + +#[bench] +fn ethernet_parse(b: &mut Bencher) { + let data = [0; 1500]; + b.iter(|| { + Frame::parse(&data).unwrap() + }) +} + +#[bench] +fn ipv4_parse(b: &mut Bencher) { + let mut data = [0; 1500]; + data[0] = 4*16; + b.iter(|| { + Packet::parse(&data).unwrap() + }) +} + +#[bench] +fn ipv6_parse(b: &mut Bencher) { + let mut data = [0; 1500]; + data[0] = 6*16; + b.iter(|| { + Packet::parse(&data).unwrap() + }) +} diff --git a/src/cloud.rs b/src/cloud.rs index 6cead6d..95764d4 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -225,7 +225,7 @@ impl GenericCloud

{ return Err(Error::WrongNetwork(options.network_id)); } } - debug!("Recieved {:?} from {}", msg, peer); + debug!("Received {:?} from {}", msg, peer); match msg { Message::Data(payload) => { let (src, _dst) = try!(P::parse(payload)); @@ -237,7 +237,7 @@ impl GenericCloud

{ return Err(Error::TunTapDevError("Failed to write to device")); } } - self.peers.add(&peer); + // not adding peer to increase performance if self.learning { //learn single address self.table.learn(src, None, peer); diff --git a/src/device.rs b/src/device.rs index 17fbc64..fec810f 100644 --- a/src/device.rs +++ b/src/device.rs @@ -38,10 +38,12 @@ impl Device { &self.ifname } + #[inline] pub fn read(&mut self, mut buffer: &mut [u8]) -> Result { self.fd.read(&mut buffer).map_err(|_| Error::TunTapDevError("Read error")) } + #[inline] pub fn write(&mut self, data: &[u8]) -> Result<(), Error> { match self.fd.write_all(&data) { Ok(_) => Ok(()), @@ -51,6 +53,7 @@ impl Device { } impl AsRawFd for Device { + #[inline(always)] fn as_raw_fd(&self) -> RawFd { self.fd.as_raw_fd() } diff --git a/src/ethernet.rs b/src/ethernet.rs index 73aeb65..014216d 100644 --- a/src/ethernet.rs +++ b/src/ethernet.rs @@ -25,27 +25,23 @@ impl Protocol for Frame { if data.len() < pos + 2 { return Err(Error::ParseError("Vlan frame is too short")); } - let mut src = Vec::with_capacity(8); - let mut dst = Vec::with_capacity(8); + let mut src = [0; 16]; + let mut dst = [0; 16]; unsafe { - src.set_len(8); ptr::copy_nonoverlapping(data[pos..].as_ptr(), src.as_mut_ptr(), 2); ptr::copy_nonoverlapping(src_data.as_ptr(), src[2..].as_mut_ptr(), 6); - dst.set_len(8); ptr::copy_nonoverlapping(data[pos..].as_ptr(), dst.as_mut_ptr(), 2); ptr::copy_nonoverlapping(dst_data.as_ptr(), dst[2..].as_mut_ptr(), 6); } - Ok((Address(src), Address(dst))) + Ok((Address(src, 8), Address(dst, 8))) } else { - let mut src = Vec::with_capacity(6); - let mut dst = Vec::with_capacity(6); + let mut src = [0; 16]; + let mut dst = [0; 16]; unsafe { ptr::copy_nonoverlapping(src_data.as_ptr(), src.as_mut_ptr(), 6); - src.set_len(6); ptr::copy_nonoverlapping(dst_data.as_ptr(), dst.as_mut_ptr(), 6); - dst.set_len(6); } - Ok((Address(src), Address(dst))) + Ok((Address(src, 6), Address(dst, 6))) } } } @@ -84,6 +80,7 @@ impl Table for SwitchTable { self.cache = None; } + #[inline] fn learn(&mut self, key: Address, _prefix_len: Option, addr: SocketAddr) { let value = SwitchTableValue{address: addr, timeout: SteadyTime::now()+self.timeout}; if self.table.insert(key.clone(), value).is_none() { @@ -91,6 +88,7 @@ impl Table for SwitchTable { } } + #[inline] fn lookup(&mut self, key: &Address) -> Option { match self.table.get(key) { Some(value) => Some(value.address), @@ -108,14 +106,14 @@ impl Table for SwitchTable { fn without_vlan() { let data = [6,5,4,3,2,1,1,2,3,4,5,6,1,2,3,4,5,6,7,8]; let (src, dst) = Frame::parse(&data).unwrap(); - assert_eq!(src, Address(vec![1,2,3,4,5,6])); - assert_eq!(dst, Address(vec![6,5,4,3,2,1])); + assert_eq!(src, Address([1,2,3,4,5,6,0,0,0,0,0,0,0,0,0,0], 6)); + assert_eq!(dst, Address([6,5,4,3,2,1,0,0,0,0,0,0,0,0,0,0], 6)); } #[test] fn with_vlan() { let data = [6,5,4,3,2,1,1,2,3,4,5,6,0x81,0,4,210,1,2,3,4,5,6,7,8]; let (src, dst) = Frame::parse(&data).unwrap(); - assert_eq!(src, Address(vec![4,210,1,2,3,4,5,6])); - assert_eq!(dst, Address(vec![4,210,6,5,4,3,2,1])); + assert_eq!(src, Address([4,210,1,2,3,4,5,6,0,0,0,0,0,0,0,0], 8)); + assert_eq!(dst, Address([4,210,6,5,4,3,2,1,0,0,0,0,0,0,0,0], 8)); } diff --git a/src/ip.rs b/src/ip.rs index af0a66c..097823a 100644 --- a/src/ip.rs +++ b/src/ip.rs @@ -1,9 +1,10 @@ use std::net::SocketAddr; use std::collections::{hash_map, HashMap}; use std::io::Read; +use std::ptr; use super::types::{Protocol, Error, Table, Address}; -use super::util::to_vec; + #[allow(dead_code)] pub struct Packet; @@ -14,18 +15,28 @@ impl Protocol for Packet { return Err(Error::ParseError("Empty header")); } let version = data[0] >> 4; + let mut src = [0; 16]; + let mut dst = [0; 16]; match version { 4 => { if data.len() < 20 { return Err(Error::ParseError("Truncated header")); } - Ok((Address(to_vec(&data[12..16])), Address(to_vec(&data[16..20])))) + unsafe { + ptr::copy_nonoverlapping(data[12..].as_ptr(), src.as_mut_ptr(), 4); + ptr::copy_nonoverlapping(data[16..].as_ptr(), dst.as_mut_ptr(), 4); + } + Ok((Address(src, 4), Address(dst, 4))) }, 6 => { if data.len() < 40 { return Err(Error::ParseError("Truncated header")); } - Ok((Address(to_vec(&data[8..24])), Address(to_vec(&data[24..40])))) + unsafe { + ptr::copy_nonoverlapping(data[8..].as_ptr(), src.as_mut_ptr(), 16); + ptr::copy_nonoverlapping(data[24..].as_ptr(), dst.as_mut_ptr(), 16); + } + Ok((Address(src, 16), Address(dst, 16))) }, _ => Err(Error::ParseError("Invalid version")) } @@ -35,11 +46,11 @@ impl Protocol for Packet { struct RoutingEntry { address: SocketAddr, - bytes: Vec, + bytes: [u8; 16], prefix_len: u8 } -pub struct RoutingTable(HashMap, Vec>); +pub struct RoutingTable(HashMap<[u8; 16], Vec>); impl RoutingTable { pub fn new() -> Self { @@ -55,7 +66,10 @@ impl Table for RoutingTable { }; info!("New routing entry: {:?}/{} => {}", addr, prefix_len, address); let group_len = (prefix_len as usize / 16) * 2; - let group_bytes: Vec = addr.0[..group_len].iter().map(|b| *b).collect(); + assert!(group_len <= 16); + let mut group_bytes = [0; 16]; + unsafe { ptr::copy_nonoverlapping(addr.0.as_ptr(), group_bytes.as_mut_ptr(), group_len) }; + let routing_entry = RoutingEntry{address: address, bytes: addr.0, prefix_len: prefix_len}; match self.0.entry(group_bytes) { hash_map::Entry::Occupied(mut entry) => entry.get_mut().push(routing_entry), diff --git a/src/main.rs b/src/main.rs index 7e22a1d..a8a9f23 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +#![cfg_attr(feature = "bench", feature(test))] #[macro_use] extern crate log; extern crate time; extern crate docopt; @@ -6,6 +7,7 @@ extern crate epoll; extern crate signal; extern crate nix; #[cfg(feature = "crypto")] extern crate libsodium_sys; +#[cfg(feature = "bench")] extern crate test; #[macro_use] mod util; mod types; @@ -15,6 +17,7 @@ mod ethernet; mod ip; mod cloud; mod device; +#[cfg(feature = "bench")] mod benches; use time::Duration; use docopt::Docopt; @@ -35,10 +38,12 @@ use crypto::Crypto; struct SimpleLogger; impl log::Log for SimpleLogger { + #[inline(always)] fn enabled(&self, _metadata: &log::LogMetadata) -> bool { true } + #[inline(always)] fn log(&self, record: &log::LogRecord) { if self.enabled(record.metadata()) { println!("{} - {}", record.level(), record.args()); diff --git a/src/types.rs b/src/types.rs index f70fd33..58cc3ed 100644 --- a/src/types.rs +++ b/src/types.rs @@ -7,12 +7,27 @@ use super::util::{as_bytes, as_obj}; pub type NetworkId = u64; -#[derive(PartialEq, PartialOrd, Eq, Ord, Hash, Clone)] -pub struct Address(pub Vec); +#[derive(PartialOrd, Eq, Ord, Clone, Hash)] +pub struct Address(pub [u8; 16], pub u8); + +impl PartialEq for Address { + fn eq(&self, rhs: &Self) -> bool { + if self.1 != rhs.1 { + return false; + } + for i in 0..self.1 as usize { + if self.0[i] != rhs.0[i] { + return false; + } + } + true + } +} + impl fmt::Debug for Address { fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match self.0.len() { + match self.1 { 4 => write!(formatter, "{}.{}.{}.{}", self.0[0], self.0[1], self.0[2], self.0[3]), 6 => write!(formatter, "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]), @@ -36,12 +51,11 @@ impl FromStr for Address { fn from_str(text: &str) -> Result { if let Ok(addr) = Ipv4Addr::from_str(text) { let ip = addr.octets(); - let mut res = Vec::with_capacity(4); + let mut res = [0; 16]; unsafe { - res.set_len(4); ptr::copy_nonoverlapping(ip.as_ptr(), res.as_mut_ptr(), ip.len()); } - return Ok(Address(res)); + return Ok(Address(res, 4)); } if let Ok(addr) = Ipv6Addr::from_str(text) { let mut segments = addr.segments(); @@ -49,20 +63,19 @@ impl FromStr for Address { segments[i] = segments[i].to_be(); } let bytes = unsafe { as_bytes(&segments) }; - let mut res = Vec::with_capacity(16); + let mut res = [0; 16]; unsafe { - res.set_len(16); ptr::copy_nonoverlapping(bytes.as_ptr(), res.as_mut_ptr(), bytes.len()); } - return Ok(Address(res)); + return Ok(Address(res, 16)); } let parts: Vec<&str> = text.split(':').collect(); if parts.len() == 6 { - let mut bytes = Vec::with_capacity(6); + let mut bytes = [0; 16]; for i in 0..6 { - bytes.push(try!(u8::from_str_radix(&parts[i], 16).map_err(|_| Error::ParseError("Failed to parse mac")))); + bytes[i] = try!(u8::from_str_radix(&parts[i], 16).map_err(|_| Error::ParseError("Failed to parse mac"))); } - return Ok(Address(bytes)); + return Ok(Address(bytes, 6)); } return Err(Error::ParseError("Failed to parse address")) } @@ -154,8 +167,8 @@ impl fmt::Display for Error { #[test] fn address_fmt() { - assert_eq!(format!("{:?}", Address(vec![120,45,22,5])), "120.45.22.5"); - assert_eq!(format!("{:?}", Address(vec![120,45,22,5,1,2])), "78:2d:16:05:01:02"); - assert_eq!(format!("{:?}", Address(vec![3,56,120,45,22,5,1,2])), "vlan824/78:2d:16:05:01:02"); - assert_eq!(format!("{:?}", Address(vec![0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15])), "0001:0203:0405:0607:0809:0a0b:0c0d:0e0f"); + assert_eq!(format!("{:?}", Address([120,45,22,5,0,0,0,0,0,0,0,0,0,0,0,0], 4)), "120.45.22.5"); + assert_eq!(format!("{:?}", Address([120,45,22,5,1,2,0,0,0,0,0,0,0,0,0,0], 6)), "78:2d:16:05:01:02"); + assert_eq!(format!("{:?}", Address([3,56,120,45,22,5,1,2,0,0,0,0,0,0,0,0], 8)), "vlan824/78:2d:16:05:01:02"); + assert_eq!(format!("{:?}", Address([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15], 16)), "0001:0203:0405:0607:0809:0a0b:0c0d:0e0f"); } diff --git a/src/udpmessage.rs b/src/udpmessage.rs index 8710a20..c209c9d 100644 --- a/src/udpmessage.rs +++ b/src/udpmessage.rs @@ -3,7 +3,7 @@ use std::net::{SocketAddr, SocketAddrV4, Ipv4Addr, SocketAddrV6, Ipv6Addr}; use std::u16; use super::types::{Error, NetworkId, Range, Address}; -use super::util::{as_obj, as_bytes, to_vec}; +use super::util::{as_obj, as_bytes}; use super::crypto::Crypto; const MAGIC: [u8; 3] = [0x76, 0x70, 0x6e]; @@ -166,7 +166,9 @@ pub fn decode<'a>(data: &'a mut [u8], crypto: &mut Crypto) -> Result<(Options, M if end < pos + len + 1 { return Err(Error::ParseError("Init data too short")); } - let base = Address(to_vec(&data[pos..pos+len])); + let mut addr = [0; 16]; + unsafe { ptr::copy_nonoverlapping(data[pos..].as_ptr(), addr.as_mut_ptr(), len) }; + let base = Address(addr, len as u8); let prefix_len = data[pos+len]; pos += len + 1; addrs.push(Range{base: base, prefix_len: prefix_len}); @@ -260,8 +262,7 @@ pub fn encode(options: &Options, msg: &Message, buf: &mut [u8], crypto: &mut Cry pos += 1; for range in ranges { let base = &range.base; - let len = base.0.len(); - assert!(len <= 255); + let len = base.1 as usize; assert!(buf.len() >= pos + 1 + len + 1); buf[pos] = len as u8; pos += 1; @@ -350,8 +351,8 @@ fn encode_option_network_id() { fn encode_message_init() { let mut options = Options::default(); let mut crypto = Crypto::None; - let addrs = vec![Range{base: Address(vec![0,1,2,3]), prefix_len: 24}, - Range{base: Address(vec![0,1,2,3,4,5]), prefix_len: 16}]; + let addrs = vec![Range{base: Address([0,1,2,3,0,0,0,0,0,0,0,0,0,0,0,0], 4), prefix_len: 24}, + Range{base: Address([0,1,2,3,4,5,0,0,0,0,0,0,0,0,0,0], 6), prefix_len: 16}]; let msg = Message::Init(addrs); let mut buf = [0; 1024]; let size = encode(&mut options, &msg, &mut buf[..], &mut crypto); diff --git a/src/util.rs b/src/util.rs index 817681f..3ff87a8 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,4 @@ -use std::{mem, slice, ptr}; +use std::{mem, slice}; #[inline(always)] pub unsafe fn as_bytes(obj: &T) -> &[u8] { @@ -11,15 +11,6 @@ pub unsafe fn as_obj(data: &[u8]) -> &T { mem::transmute(data.as_ptr()) } -pub fn to_vec(data: &[u8]) -> Vec { - let mut v = Vec::with_capacity(data.len()); - unsafe { - ptr::copy_nonoverlapping(data.as_ptr(), v.as_mut_ptr(), data.len()); - v.set_len(data.len()); - } - v -} - macro_rules! fail { ($format:expr) => ( { use std::process;