├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── benches.rs └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: required 3 | rust: 4 | - stable 5 | before_script: 6 | - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH 7 | script: 8 | - | 9 | travis-cargo build && 10 | travis-cargo test && 11 | travis-cargo bench && 12 | travis-cargo doc 13 | after_success: 14 | - travis-cargo --only stable doc-upload 15 | env: 16 | global: 17 | secure: LKWGoK8q1t4q4m4a9LNy9bX1V7ZmvwL3oMOhwDRU8NKNf/3+kdl7aiMOZed00GPryxmockSCAx9zPP1FNsHuh1cvWodXeaBniPwkwRvMZB4LtmZzuLB9uus1R7JQXTBUnBdyBSftPs9G3xLjYCZJhgPqQTX6qye9LxUzoTyCZyRcYyqz9ZXNgzXLCnkzeDbwsmWxQwBk1vplxtY9oe7B5dXyPHKrOADEOMY+/tvAqJhks7Hwo0s+ex789ZkCF/j2J7U3oYjRbkZO0DZeFyLkCggIVqOvmYqofAGWTfGYFEo3PZLViPYGzmCX8Yqw7pLD822VFQVuP2WJjCx7Udi1bJcK1D65Z/pxZaRfPZBY6XLw4fra002VKSBjyxf6xeJDta8/qdSgsj0hhguxjohi9L8lpyKaWOE62GkjwEzO/Z1KRmSDy+LkJUXDLtLzl0lzcpDoaLN61dqItglEK6aPpVFEH3F1hRqzpG3bAmuFNmsTxj5jq8QT6RD82tQohQMGWvYxXXonGCxam4PofIt8eZJcMR5aQvIJD9xjhPnY0SzusuT1zQGYp5VewXEWb6xGrDngoznTc7+yR1CzHSaF75V7xUfUYgrcg0YboEUUC/0sYNape1JTAa4GV8h/49CUXMi623+Y0PF0iHBqWfE4/FDkVu4ve0p8ZjEjKKYTljg= 18 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "recycler" 3 | version = "0.1.4" 4 | authors = ["Frank McSherry "] 5 | 6 | description = "A small Rust library for recycling types containing owned memory" 7 | 8 | # These URLs point to more information about the repository 9 | documentation = "https://github.com/frankmcsherry/recycler" 10 | homepage = "https://github.com/frankmcsherry/recycler" 11 | repository = "https://github.com/frankmcsherry/recycler" 12 | 13 | # This points to a file in the repository (relative to this Cargo.toml). The 14 | # contents of this file are stored and indexed in the registry. 15 | readme = "README.md" 16 | 17 | # This is a small list of keywords used to categorize and search for this 18 | # package. 19 | keywords = ["recycler", "pool", "pooling"] 20 | 21 | # This is a string description of the license for this package. Currently 22 | # crates.io will validate the license provided against a whitelist of known 23 | # license identifiers from http://spdx.org/licenses/. Multiple licenses can 24 | # be separated with a `/` 25 | license = "MIT" 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Frank McSherry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # recycler 2 | A small Rust library for recycling types with owned memory 3 | 4 | Recycler provides the `Recycler` trait and several implementations. Each `Recycler` object is capable of "recycling" items of its associated type `Item`, and using recycled items to "recreate" owned copies of referenced items. 5 | 6 | ```rust 7 | pub trait Recycler : Default { 8 | type Item; 9 | fn recycle(&mut self, item: Self::Item); 10 | fn recreate(&mut self, other: &Self::Item) -> Self::Item; 11 | } 12 | ``` 13 | The default `TrashRecycler` just drops arguments to `recycle` and clones arguments to `recreate`. However, smarter recyclers for types with owned memory can deconstruct the item and stash any of its owned memory, and then use the stashed memory to recreate items. 14 | 15 | For example, `VecRecycler` does just this for vectors of recycleable types: 16 | 17 | ```rust 18 | // A recycler for vectors and their contents 19 | pub struct VecRecycler { 20 | pub recycler: R, 21 | stash: Vec>, 22 | } 23 | 24 | impl Recycler for VecRecycler { 25 | type Item = Vec; 26 | // recycles vec contents and then stashes the vec 27 | fn recycle(&mut self, mut vec: Vec) { 28 | while let Some(x) = vec.pop() { 29 | self.recycler.recycle(x) 30 | } 31 | self.stash.push(vec); 32 | } 33 | // pops a stashed vector and then recreates each element 34 | fn recreate(&mut self, other: &Vec) -> Vec { 35 | let mut vec = self.stash.pop().unwrap_or(Vec::new()); 36 | for elem in other.iter() { 37 | vec.push(self.recycler.recreate(elem)); 38 | } 39 | vec 40 | } 41 | } 42 | ``` 43 | 44 | While recycling might sound great just because of civic duty, the real purpose is that these recyclers are able to return the owned memory to you, using a pattern not unlike standard allocation. Where you might write something like 45 | 46 | ```rust 47 | #[bench] 48 | fn allocate_vec_vec_str(bencher: &mut Bencher) { 49 | bencher.iter(|| { 50 | let mut v1 = Vec::with_capacity(10); 51 | for _ in 0..10 { 52 | let mut v2 = Vec::with_capacity(10); 53 | for _ in 0..10 { 54 | v2.push(("test!").to_owned()); 55 | } 56 | v1.push(v2); 57 | } 58 | v1 59 | }); 60 | } 61 | ``` 62 | 63 | you can now instead write something pretty similar (no, not the same): 64 | 65 | ```rust 66 | #[bench] 67 | fn recycler_vec_vec_str(bencher: &mut Bencher) { 68 | let mut r1 = make_recycler::>>(); 69 | bencher.iter(|| { 70 | let v = { // scope the borrow of r1 71 | let (mut v1, r2) = r1.new(); 72 | for _ in 0..10 { 73 | let (mut v2, r3) = r2.new(); 74 | for _ in 0..10 { 75 | v2.push(r3.new_from("test!")); 76 | } 77 | v1.push(v2); 78 | } 79 | v1 80 | }; 81 | r1.recycle(v); 82 | }); 83 | } 84 | ``` 85 | 86 | The reason you do this is because if you run those benchmarks up there, you see numbers like: 87 | 88 | test allocate_vec_vec_str ... bench: 3,494 ns/iter (+/- 1,128) 89 | test recycler_vec_vec_str ... bench: 1,709 ns/iter (+/- 643) 90 | 91 | If you do less formatting stuff and just put some `u64` data in the vectors, you see similar distinction: 92 | 93 | test allocate_vec_vec_u64 ... bench: 267 ns/iter (+/- 49) 94 | test recycler_vec_vec_u64 ... bench: 145 ns/iter (+/- 26) 95 | 96 | The main down side is that you may get vectors that may have more memory than you need, and memory may also live for quite a while in the recycler. I almost added a `clear` method, but if you want to do that just make a new recycler and clobber the old one. 97 | 98 | Note: a previous version of these numbers looked worse for the `allocate` variants because they used `Vec::new()` rather than `Vec::with_capacity(10)`, which correctly sizes the allocation and avoids copies. 99 | 100 | ## recreate 101 | 102 | If for some reason you find you are often given references to objects and need a quick clone (for example, using `decode` or `verify` in [Abomonation](https://github.com/frankmcsherry/abomonation)), the `recreate` method is meant to be painless. The above benchmark becomes: 103 | 104 | ```rust 105 | #[bench] 106 | fn recreate_vec_vec_str(bencher: &mut Bencher) { 107 | let mut recycler = make_recycler::>>(); 108 | let data = vec![vec!["test!".to_owned(); 10]; 10]; 109 | bencher.iter(|| { 110 | let record = recycler.recreate(&data); 111 | recycler.recycle(record); 112 | }); 113 | } 114 | ``` 115 | 116 | If you compare using `recreate` with just using `clone`, you see numbers like: 117 | 118 | test clone_vec_vec_str ... bench: 2,906 ns/iter (+/- 774) 119 | test recreate_vec_vec_str ... bench: 1,773 ns/iter (+/- 625) 120 | 121 | test clone_vec_vec_u64 ... bench: 344 ns/iter (+/- 134) 122 | test recreate_vec_vec_u64 ... bench: 157 ns/iter (+/- 42) 123 | 124 | ## thanks! 125 | 126 | If anyone has any hot tips or recommendations, especially about a macro or syntax extension that would let structs and such automatically derive recyclers, I'd be all ears. Any other friendly comments or contributions are also welcome. 127 | -------------------------------------------------------------------------------- /benches/benches.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate recycler; 4 | extern crate test; 5 | 6 | use test::Bencher; 7 | use recycler::*; 8 | 9 | #[bench] 10 | fn recycler_vec_vec_str(bencher: &mut Bencher) { 11 | let mut r1 = make_recycler::>>(); 12 | bencher.iter(|| { 13 | let v = { // scope the borrow of r1 14 | let (mut v1, r2) = r1.new(); 15 | for _ in 0..10 { 16 | let (mut v2, r3) = r2.new(); 17 | for _ in 0..10 { 18 | v2.push(r3.new_from("test!")); 19 | } 20 | v1.push(v2); 21 | } 22 | v1 23 | }; 24 | r1.recycle(v); 25 | }); 26 | } 27 | 28 | #[bench] 29 | fn allocate_vec_vec_str(bencher: &mut Bencher) { 30 | bencher.iter(|| { 31 | let mut v1 = Vec::with_capacity(10); 32 | for _ in 0..10 { 33 | let mut v2 = Vec::with_capacity(10); 34 | for _ in 0..10 { 35 | v2.push(("test!").to_owned()); 36 | } 37 | v1.push(v2); 38 | } 39 | v1 40 | }); 41 | } 42 | 43 | #[bench] 44 | fn recreate_vec_vec_str(bencher: &mut Bencher) { 45 | let mut recycler = make_recycler::>>(); 46 | let data = vec![vec!["test!".to_owned(); 10]; 10]; 47 | bencher.iter(|| { 48 | let record = recycler.recreate(&data); 49 | recycler.recycle(record); 50 | }); 51 | } 52 | 53 | #[bench] 54 | fn clone_vec_vec_str(bencher: &mut Bencher) { 55 | let data = vec![vec!["test!".to_owned(); 10]; 10]; 56 | bencher.iter(|| { 57 | data.clone() 58 | }); 59 | } 60 | 61 | #[bench] 62 | fn recycler_vec_vec_u64(bencher: &mut Bencher) { 63 | let mut r1 = make_recycler::>>(); 64 | bencher.iter(move || { 65 | let v = { // scope the borrow of r1 66 | let (mut v1, r2) = r1.new(); 67 | for _ in 0..10 { 68 | let (mut v2, _r3) = r2.new(); 69 | for _ in 0..10 { 70 | v2.push(0u64); 71 | } 72 | v1.push(v2); 73 | } 74 | v1 75 | }; 76 | r1.recycle(v); 77 | }); 78 | } 79 | 80 | #[bench] 81 | fn allocate_vec_vec_u64(bencher: &mut Bencher) { 82 | bencher.iter(|| { 83 | let mut v1 = Vec::with_capacity(10); 84 | for _ in 0..10 { 85 | let mut v2 = Vec::with_capacity(10); 86 | for _ in 0..10 { 87 | v2.push(0u64); 88 | } 89 | v1.push(v2); 90 | } 91 | v1 92 | }); 93 | } 94 | 95 | #[bench] 96 | fn recreate_vec_vec_u64(bencher: &mut Bencher) { 97 | let mut recycler = make_recycler::>>(); 98 | let data = vec![vec![0u64; 10]; 10]; 99 | bencher.iter(|| { 100 | let record = recycler.recreate(&data); 101 | recycler.recycle(record); 102 | }); 103 | } 104 | 105 | #[bench] 106 | fn clone_vec_vec_u64(bencher: &mut Bencher) { 107 | let data = vec![vec![0u64; 10]; 10]; 108 | bencher.iter(|| { 109 | data.clone() 110 | }); 111 | } 112 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // #![feature(std_misc)] 2 | 3 | use std::default::Default; 4 | use std::marker::PhantomData; 5 | use std::ops::{Deref, DerefMut}; 6 | 7 | // use std::collections::HashMap; 8 | // use std::hash::Hash; 9 | 10 | /// A value that has some default type that can recycle it. 11 | pub trait Recyclable : Sized { 12 | type DefaultRecycler: Recycler; 13 | } 14 | 15 | pub fn make_recycler() -> T::DefaultRecycler { 16 | Default::default() 17 | } 18 | 19 | // These really want default associated types. (rust-lang/rust#19476) 20 | impl Recyclable for u8 { type DefaultRecycler = TrashRecycler; } 21 | impl Recyclable for i8 { type DefaultRecycler = TrashRecycler; } 22 | impl Recyclable for u16 { type DefaultRecycler = TrashRecycler; } 23 | impl Recyclable for i16 { type DefaultRecycler = TrashRecycler; } 24 | impl Recyclable for u32 { type DefaultRecycler = TrashRecycler; } 25 | impl Recyclable for i32 { type DefaultRecycler = TrashRecycler; } 26 | impl Recyclable for u64 { type DefaultRecycler = TrashRecycler; } 27 | impl Recyclable for i64 { type DefaultRecycler = TrashRecycler; } 28 | impl Recyclable for usize { type DefaultRecycler = TrashRecycler; } 29 | impl Recyclable for isize { type DefaultRecycler = TrashRecycler; } 30 | impl Recyclable for bool { type DefaultRecycler = TrashRecycler; } 31 | impl Recyclable for () { type DefaultRecycler = TrashRecycler; } 32 | 33 | impl Recyclable for String { 34 | type DefaultRecycler = StringRecycler; 35 | } 36 | 37 | impl Recyclable for Vec { 38 | type DefaultRecycler = VecRecycler; 39 | } 40 | 41 | impl Recyclable for Option { 42 | type DefaultRecycler = OptionRecycler; 43 | } 44 | 45 | impl Recyclable for (A, B) { 46 | type DefaultRecycler = (A::DefaultRecycler, B::DefaultRecycler); 47 | } 48 | impl Recyclable for (A, B, C) { 49 | type DefaultRecycler = (A::DefaultRecycler, B::DefaultRecycler, C::DefaultRecycler); 50 | } 51 | 52 | // impl Recyclable for HashMap { 53 | // type DefaultRecycler = HashMapRecycler; 54 | // } 55 | 56 | // demonstrating how tuples might be recycled 57 | impl Recycler for (R1, R2) { 58 | type Item = (R1::Item, R2::Item); 59 | #[inline] fn recycle(&mut self, (part1, part2): (R1::Item, R2::Item)) { 60 | self.0.recycle(part1); 61 | self.1.recycle(part2); 62 | } 63 | #[inline] fn recreate(&mut self, &(ref other1, ref other2): &(R1::Item, R2::Item)) -> (R1::Item, R2::Item) { 64 | (self.0.recreate(other1), self.1.recreate(other2)) 65 | } 66 | } 67 | 68 | // demonstrating how tuples might be recycled 69 | impl Recycler for (R1, R2, R3) { 70 | type Item = (R1::Item, R2::Item, R3::Item); 71 | #[inline] fn recycle(&mut self, (part1, part2, part3): (R1::Item, R2::Item, R3::Item)) { 72 | self.0.recycle(part1); 73 | self.1.recycle(part2); 74 | self.2.recycle(part3); 75 | } 76 | #[inline] fn recreate(&mut self, &(ref other1, ref other2, ref other3): &(R1::Item, R2::Item, R3::Item)) -> (R1::Item, R2::Item, R3::Item) { 77 | (self.0.recreate(other1), self.1.recreate(other2), self.2.recreate(other3)) 78 | } 79 | } 80 | 81 | // allows recycling of items 82 | pub trait Recycler : Default { 83 | type Item; 84 | fn recycle(&mut self, item: Self::Item); 85 | fn recreate(&mut self, other: &Self::Item) -> Self::Item; 86 | } 87 | 88 | /// A "recycler" that doesn't recycle anything, instead just dropping anything 89 | /// it is given. This is particularly useful for primitive types such as `i32` 90 | /// that do not have `Drop` implementations. 91 | pub struct TrashRecycler { 92 | marker: PhantomData 93 | } 94 | 95 | impl Default for TrashRecycler { 96 | fn default() -> Self { 97 | TrashRecycler { 98 | marker: PhantomData 99 | } 100 | } 101 | } 102 | 103 | impl Recycler for TrashRecycler { 104 | type Item = Item; 105 | #[inline] fn recycle(&mut self, _item: Self::Item) { } 106 | #[inline] fn recreate(&mut self, other: &Self::Item) -> Self::Item { other.clone() } 107 | } 108 | 109 | #[derive(Default)] 110 | pub struct StringRecycler { 111 | stash: Vec 112 | } 113 | 114 | impl Recycler for StringRecycler { 115 | type Item = String; 116 | #[inline] fn recycle(&mut self, mut string: String) { 117 | string.clear(); 118 | self.stash.push(string); 119 | } 120 | #[inline] fn recreate(&mut self, other: &String) -> String { 121 | self.new_from(other) 122 | } 123 | } 124 | 125 | impl StringRecycler { 126 | #[inline] pub fn new(&mut self) -> String { 127 | self.stash.pop().unwrap_or(String::new()) 128 | } 129 | #[inline] pub fn new_from(&mut self, s: &str) -> String { 130 | let mut string = self.new(); 131 | string.push_str(s); 132 | string 133 | } 134 | } 135 | 136 | // A recycler for vectors and their contents 137 | pub struct VecRecycler { 138 | pub recycler: R, 139 | stash: Vec>, 140 | } 141 | 142 | // recycles vec contents, then stashes the vec 143 | impl Recycler for VecRecycler { 144 | type Item = Vec; 145 | #[inline] fn recycle(&mut self, mut vec: Vec) { 146 | while let Some(x) = vec.pop() { 147 | self.recycler.recycle(x) 148 | } 149 | self.stash.push(vec); 150 | } 151 | #[inline] fn recreate(&mut self, other: &Vec) -> Vec { 152 | let mut vec = self.stash.pop().unwrap_or(Vec::new()); 153 | for elem in other.iter() { 154 | vec.push(self.recycler.recreate(elem)); 155 | } 156 | vec 157 | } 158 | } 159 | 160 | impl VecRecycler { 161 | #[inline] pub fn new(&mut self) -> (Vec, &mut R) { 162 | (self.stash.pop().unwrap_or(Vec::new()), &mut self.recycler) 163 | } 164 | } 165 | 166 | impl Default for VecRecycler { 167 | fn default() -> Self { 168 | VecRecycler { 169 | recycler: Default::default(), 170 | stash: Vec::new(), 171 | } 172 | } 173 | } 174 | 175 | // option recycler 176 | #[derive(Default)] 177 | pub struct OptionRecycler { 178 | pub recycler: R, 179 | } 180 | 181 | impl Recycler for OptionRecycler { 182 | type Item = Option; 183 | #[inline] fn recycle(&mut self, option: Option) { 184 | if let Some(thing) = option { 185 | self.recycler.recycle(thing); 186 | } 187 | } 188 | #[inline] fn recreate(&mut self, other: &Option) -> Option { 189 | if let &Some(ref thing) = other { 190 | Some(self.recycler.recreate(thing)) 191 | } 192 | else { None } 193 | } 194 | } 195 | 196 | // derefs to contained recycler 197 | impl Deref for OptionRecycler { 198 | type Target = R; 199 | #[inline] fn deref(&self) -> &Self::Target { &self.recycler } 200 | } 201 | 202 | // derefs to contained recycler, permits .new() 203 | impl DerefMut for OptionRecycler { 204 | #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.recycler } 205 | } 206 | 207 | // // commented out due to beta-instability of .drain() 208 | // // recycles keys and values, then stashes the hashmap 209 | // pub struct HashMapRecycler { 210 | // pub key_recycler: KR, 211 | // pub val_recycler: VR, 212 | // pub stash: Vec>, 213 | // } 214 | // 215 | // impl Recycler for HashMapRecycler where KR::Item: Eq+Hash { 216 | // type Item = HashMap; 217 | // fn recycle(&mut self, mut map: HashMap) { 218 | // for (key, val) in map.drain() { 219 | // self.key_recycler.recycle(key); 220 | // self.val_recycler.recycle(val); 221 | // } 222 | // self.stash.push(map); 223 | // } 224 | // } 225 | // 226 | // impl HashMapRecycler where KR::Item: Eq+Hash { 227 | // pub fn new(&mut self) -> (HashMap, (&mut KR, &mut VR)) { 228 | // (self.stash.pop().unwrap_or(HashMap::new()), (&mut self.key_recycler, &mut self.val_recycler)) 229 | // } 230 | // } 231 | // 232 | // impl Default for HashMapRecycler { 233 | // fn default() -> Self { 234 | // HashMapRecycler { 235 | // key_recycler: Default::default(), 236 | // val_recycler: Default::default(), 237 | // stash: Vec::new(), 238 | // } 239 | // } 240 | // } 241 | --------------------------------------------------------------------------------