append-only-vec-0.1.7/.cargo_vcs_info.json0000644000000001360000000000100140420ustar { "git": { "sha1": "1d2940c9ca84bcd2101091ba48f5a4b41e591443" }, "path_in_vcs": "" }append-only-vec-0.1.7/.github/workflows/rust.yml000064400000000000000000000047521046102023000177570ustar 00000000000000on: [push, pull_request] name: Continuous integration jobs: check: name: Check runs-on: ubuntu-latest strategy: matrix: rust: - stable - 1.57 target: - x86_64-unknown-linux-gnu - i686-unknown-linux-gnu - aarch64-unknown-linux-gnu steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} override: true - uses: actions-rs/cargo@v1 with: command: check args: --all-features test: name: Test Suite runs-on: ubuntu-latest strategy: matrix: rust: - stable target: - x86_64-unknown-linux-gnu - i686-unknown-linux-gnu steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} override: true - uses: actions-rs/cargo@v1 with: command: test args: --all-features # docsrs: # name: Generate docs.rs # runs-on: ubuntu-latest # strategy: # matrix: # rust: # - nightly # steps: # - uses: actions/checkout@v2 # - uses: actions-rs/toolchain@v1 # with: # profile: minimal # toolchain: ${{ matrix.rust }} # override: true # - uses: actions-rs/cargo@v1 # with: # command: doc # args: --all-features # env: # RUSTFLAGS: --cfg docsrs # RUSTDOCFLAGS: --cfg docsrs -Dwarnings msrv: name: Minimum supported rust version runs-on: ubuntu-latest strategy: matrix: rust: - 1.56 steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust }} override: true - uses: actions-rs/cargo@v1 with: command: test miri-test: runs-on: ubuntu-latest strategy: matrix: rust: - nightly steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust }} override: true components: miri - name: miri test run: cargo miri test append-only-vec-0.1.7/.gitignore000064400000000000000000000000231046102023000146150ustar 00000000000000/target Cargo.lock append-only-vec-0.1.7/Cargo.toml0000644000000022260000000000100120420ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "append-only-vec" version = "0.1.7" authors = ["David Roundy "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Append-only, concurrent vector" readme = "README.md" keywords = [ "frozen", "data-structure", ] categories = [ "data-structures", "concurrency", ] license = "MIT OR Apache-2.0" repository = "https://github.com/droundy/append-only-vec" [lib] name = "append_only_vec" path = "src/lib.rs" [[bench]] name = "bench" path = "benches/bench.rs" harness = false [dependencies] [dev-dependencies.parking_lot] version = "0.12" [dev-dependencies.scaling] version = "0.1.3" append-only-vec-0.1.7/Cargo.toml.orig000064400000000000000000000007461046102023000155300ustar 00000000000000[package] name = "append-only-vec" version = "0.1.7" authors = ["David Roundy "] edition = "2021" description = "Append-only, concurrent vector" license = "MIT OR Apache-2.0" repository = "https://github.com/droundy/append-only-vec" readme = "README.md" categories = ["data-structures", "concurrency"] keywords = ["frozen", "data-structure"] [dependencies] [dev-dependencies] scaling = "0.1.3" parking_lot = "0.12" [[bench]] name = "bench" harness = false append-only-vec-0.1.7/LICENSE-APACHE000064400000000000000000000227731046102023000145710ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS append-only-vec-0.1.7/LICENSE-MIT000064400000000000000000000017771046102023000143020ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. append-only-vec-0.1.7/README.md000064400000000000000000000015521046102023000141140ustar 00000000000000# Append-only-vec   [![Latest version](https://img.shields.io/crates/v/append-only-vec.svg)](https://crates.io/crates/append-only-vec) [![Documentation](https://docs.rs/append-only-vec/badge.svg)](https://docs.rs/append-only-vec) [![Build Status](https://github.com/droundy/append-only-vec/actions/workflows/rust.yml/badge.svg)](https://github.com/droundy/append-only-vec/actions) **Note: currently there are frequent CI failures above, which are simply due to failure to install miri to run the test. The tests do pass when run locally.** This crate defines a single data simple structure, which is a vector to which you can only append data. It allows you to push new data values even when there are outstanding references to elements of the `AppendOnlyVec`. Reading from a `AppendOnlyVec` is much faster than if it had been protected by a `std::sync::RwLock`.append-only-vec-0.1.7/benches/bench.rs000064400000000000000000000216251046102023000156740ustar 00000000000000use std::{ops::Index, sync::RwLock}; use append_only_vec::AppendOnlyVec; use scaling::{bench, bench_scaling_gen}; struct RwVec { data: RwLock>, } impl RwVec { fn new() -> Self { RwVec { data: RwLock::new(Vec::new()), } } fn push(&self, val: T) { self.data.write().unwrap().push(val) } fn get(&self, index: usize) -> T { self.data.read().unwrap().index(index).clone() } fn len(&self) -> usize { self.data.read().unwrap().len() } } struct ParkVec { data: parking_lot::RwLock>, } impl ParkVec { fn new() -> Self { ParkVec { data: parking_lot::RwLock::new(Vec::new()), } } fn push(&self, val: T) { self.data.write().push(val) } fn get(&self, index: usize) -> T { self.data.read().index(index).clone() } fn len(&self) -> usize { self.data.read().len() } } fn main() { { println!( "AOV: Filling 10 strings: {}", bench(|| { let v = AppendOnlyVec::new(); for i in 0..10 { v.push(format!("{}", i)); } v }) ); println!( "RWV: Filling 10 strings: {}", bench(|| { let v = RwVec::new(); for i in 0..10 { v.push(format!("{}", i)); } v }) ); println!( "plV: Filling 10 strings: {}", bench(|| { let v = ParkVec::new(); for i in 0..10 { v.push(format!("{}", i)); } v }) ); println!( "Vec: Filling 10 strings: {}", bench(|| { let mut v = Vec::new(); for i in 0..10 { v.push(format!("{}", i)); } v }) ); println!(); println!( "AOV: Filling 100 strings: {}", bench(|| { let v = AppendOnlyVec::new(); for i in 0..100 { v.push(format!("{}", i)); } v }) ); println!( "RWV: Filling 100 strings: {}", bench(|| { let v = RwVec::new(); for i in 0..100 { v.push(format!("{}", i)); } v }) ); println!( "plV: Filling 100 strings: {}", bench(|| { let v = ParkVec::new(); for i in 0..100 { v.push(format!("{}", i)); } v }) ); println!( "Vec: Filling 100 strings: {}", bench(|| { let mut v = Vec::new(); for i in 0..100 { v.push(format!("{}", i)); } v }) ); } println!(); { let min_n = 1000; println!( "AOV: sum: {}", bench_scaling_gen( |n: usize| { let vec = AppendOnlyVec::new(); for i in 0..n { vec.push(i); } vec }, |vec| { vec.iter().copied().sum::() }, min_n ) ); println!( "AOV: reversed sum: {}", bench_scaling_gen( |n: usize| { let vec = AppendOnlyVec::new(); for i in 0..n { vec.push(i); } vec }, |vec| { vec.iter().copied().rev().sum::() }, min_n ) ); println!( "Vec: sum: {}", bench_scaling_gen( |n: usize| { let mut vec = Vec::new(); for i in 0..n { vec.push(i); } vec }, |vec| { vec.iter().copied().sum::() }, min_n ) ); println!(); println!( "AOV: loop sum: {}", bench_scaling_gen( |n: usize| { let vec = AppendOnlyVec::new(); for i in 0..n { vec.push(i); } vec }, |vec| { let mut sum = 0; for i in 0..vec.len() { sum += vec[i]; } sum }, min_n ) ); println!( "RWV: loop sum: {}", bench_scaling_gen( |n: usize| { let vec = RwVec::new(); for i in 0..n { vec.push(i); } vec }, |vec| { let mut sum = 0; for i in 0..vec.len() { sum += vec.get(i); } sum }, min_n ) ); println!( "plV: loop sum: {}", bench_scaling_gen( |n: usize| { let vec = ParkVec::new(); for i in 0..n { vec.push(i); } vec }, |vec| { let mut sum = 0; for i in 0..vec.len() { sum += vec.get(i); } sum }, min_n ) ); println!( "Vec: loop sum: {}", bench_scaling_gen( |n: usize| { let mut vec = Vec::new(); for i in 0..n { vec.push(i); } vec }, |vec| { let mut sum = 0; for i in 0..vec.len() { sum += vec[i]; } sum }, min_n ) ); println!(); println!( "AOV: back loop sum: {}", bench_scaling_gen( |n: usize| { let vec = AppendOnlyVec::new(); for i in 0..n { vec.push(i); } vec }, |vec| { let mut sum = 0; let n = vec.len(); for i in 0..n { sum += vec[n - 1 - i]; } sum }, min_n ) ); println!( "RWV: back loop sum: {}", bench_scaling_gen( |n: usize| { let vec = RwVec::new(); for i in 0..n { vec.push(i); } vec }, |vec| { let mut sum = 0; let n = vec.len(); for i in 0..n { sum += vec.get(n - 1 - i); } sum }, min_n ) ); println!( "plV: back loop sum: {}", bench_scaling_gen( |n: usize| { let vec = ParkVec::new(); for i in 0..n { vec.push(i); } vec }, |vec| { let mut sum = 0; let n = vec.len(); for i in 0..n { sum += vec.get(n - 1 - i); } sum }, min_n ) ); println!( "Vec: back loop sum: {}", bench_scaling_gen( |n: usize| { let mut vec = Vec::new(); for i in 0..n { vec.push(i); } vec }, |vec| { let mut sum = 0; let n = vec.len(); for i in 0..n { sum += vec[n - 1 - i]; } sum }, min_n ) ); } println!(); } append-only-vec-0.1.7/src/lib.rs000064400000000000000000000400521046102023000145360ustar 00000000000000//! AppendOnlyVec //! //! This is a pretty simple type, which is a vector that you can push into, but //! cannot modify the elements of. The data structure never moves an element //! once allocated, so you can push to the vec even while holding references to //! elements that have already been pushed. //! //! ### Scaling //! //! 1. Accessing an element is O(1), but slightly more expensive than for a //! standard `Vec`. //! //! 2. Pushing a new element amortizes to O(1), but may require allocation of a //! new chunk. //! //! ### Example //! //! ``` //! use append_only_vec::AppendOnlyVec; //! static V: AppendOnlyVec = AppendOnlyVec::::new(); //! let mut threads = Vec::new(); //! for thread_num in 0..10 { //! threads.push(std::thread::spawn(move || { //! for n in 0..100 { //! let s = format!("thread {} says {}", thread_num, n); //! let which = V.push(s.clone()); //! assert_eq!(&V[which], &s); //! } //! })); //! } //! for t in threads { //! t.join(); //! } //! assert_eq!(V.len(), 1000); //! ``` use std::cell::UnsafeCell; use std::ops::{Index, IndexMut}; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; pub struct AppendOnlyVec { count: AtomicUsize, reserved: AtomicUsize, data: [UnsafeCell<*mut T>; BITS_USED - 1 - 3], } unsafe impl Send for AppendOnlyVec {} unsafe impl Sync for AppendOnlyVec {} const BITS: usize = std::mem::size_of::() * 8; #[cfg(target_arch = "x86_64")] const BITS_USED: usize = 48; #[cfg(all(not(target_arch = "x86_64"), target_pointer_width = "64"))] const BITS_USED: usize = 64; #[cfg(target_pointer_width = "32")] const BITS_USED: usize = 32; // This takes an index into a vec, and determines which data array will hold it // (the first return value), and what the index will be into that data array // (second return value) // // The ith data array holds 1< (u32, usize) { let i = i + 8; let bin = BITS as u32 - 1 - i.leading_zeros(); let bin = bin - 3; let offset = i - bin_size(bin); (bin, offset) } const fn bin_size(array: u32) -> usize { (1 << 3) << array } #[test] fn test_indices() { for i in 0..32 { println!("{:3}: {} {}", i, indices(i).0, indices(i).1); } let mut array = 0; let mut offset = 0; let mut index = 0; while index < 1000 { index += 1; offset += 1; if offset >= bin_size(array) { offset = 0; array += 1; } assert_eq!(indices(index), (array, offset)); } } impl Default for AppendOnlyVec { fn default() -> Self { Self::new() } } impl AppendOnlyVec { /// Return an `Iterator` over the elements of the vec. pub fn iter(&self) -> impl DoubleEndedIterator + ExactSizeIterator { // FIXME this could be written to be a little more efficient probably, // if we made it read each pointer only once. On the other hand, that // could make a reversed iterator less efficient? (0..self.len()).map(|i| unsafe { self.get_unchecked(i) }) } /// Find the length of the array. #[inline] pub fn len(&self) -> usize { self.count.load(Ordering::Acquire) } fn layout(&self, array: u32) -> std::alloc::Layout { std::alloc::Layout::array::(bin_size(array)).unwrap() } /// Internal-only function requests a slot and puts data into it. /// /// However this does not update the size of the vec, which *must* be done /// in order for either the value to be readable *or* for future pushes to /// actually terminate. fn pre_push(&self, val: T) -> usize { let idx = self.reserved.fetch_add(1, Ordering::Relaxed); let (array, offset) = indices(idx); let ptr = if self.len() < 1 + idx - offset { // We are working on a new array, which may not have been allocated... if offset == 0 { // It is our job to allocate the array! The size of the array // is determined in the self.layout method, which needs to be // consistent with the indices function. let layout = self.layout(array); let ptr = unsafe { std::alloc::alloc(layout) } as *mut T; unsafe { *self.data[array as usize].get() = ptr; } ptr } else { // We need to wait for the array to be allocated. while self.len() < 1 + idx - offset { std::hint::spin_loop(); } // The Ordering::Acquire semantics of self.len() ensures that // this pointer read will get the non-null pointer allocated // above. unsafe { *self.data[array as usize].get() } } } else { // The Ordering::Acquire semantics of self.len() ensures that // this pointer read will get the non-null pointer allocated // above. unsafe { *self.data[array as usize].get() } }; // The contents of this offset are guaranteed to be unused (so far) // because we got the idx from our fetch_add above, and ptr is // guaranteed to be valid because of the loop we used above, which used // self.len() which has Ordering::Acquire semantics. unsafe { (ptr.add(offset)).write(val) }; idx } /// Append an element to the array /// /// This is notable in that it doesn't require a `&mut self`, because it /// does appropriate atomic synchronization. /// /// The return value is the index tha was pushed to. pub fn push(&self, val: T) -> usize { let idx = self.pre_push(val); // Now we need to increase the size of the vec, so it can get read. We // use Release upon success, to ensure that the value which we wrote is // visible to any thread that has confirmed that the count is big enough // to read that element. In case of failure, we can be relaxed, since // we don't do anything with the result other than try again. while self .count .compare_exchange(idx, idx + 1, Ordering::Release, Ordering::Relaxed) .is_err() { // This means that someone else *started* pushing before we started, // but hasn't yet finished. We have to wait for them to finish // pushing before we can update the count. Note that using a // spinloop here isn't really ideal, but except when allocating a // new array, the window between reserving space and using it is // pretty small, so contention will hopefully be rare, and having a // context switch during that interval will hopefully be vanishingly // unlikely. std::hint::spin_loop(); } idx } /// Extend the vec with the contents of an iterator. /// /// Note: this is currently no more efficient than calling `push` for each /// element of the iterator. pub fn extend(&self, iter: impl IntoIterator) { for val in iter { self.push(val); } } /// Append an element to the array with exclusive access /// /// This is slightly more efficient than [`AppendOnlyVec::push`] since it /// doesn't need to worry about concurrent access. /// /// The return value is the new size of the array. pub fn push_mut(&mut self, val: T) -> usize { let idx = self.pre_push(val); // We do not need synchronization here because no one else has access to // this data, and if it is passed to another thread that will involve // the appropriate memory barrier. self.count.store(idx, Ordering::Relaxed); idx } const EMPTY: UnsafeCell<*mut T> = UnsafeCell::new(std::ptr::null_mut()); /// Allocate a new empty array pub const fn new() -> Self { AppendOnlyVec { count: AtomicUsize::new(0), reserved: AtomicUsize::new(0), data: [Self::EMPTY; BITS_USED - 1 - 3], } } /// Index the vec without checking the bounds. /// /// To use this correctly, you *must* first ensure that the `idx < /// self.len()`. This not only prevents overwriting the bounds, but also /// creates the memory barriers to ensure that the data is visible to the /// current thread. In single-threaded code, however, it is not needed to /// call `self.len()` explicitly (if e.g. you have counted the number of /// elements pushed). unsafe fn get_unchecked(&self, idx: usize) -> &T { let (array, offset) = indices(idx); // We use a Relaxed load of the pointer, because the length check (which // was supposed to be performed) should ensure that the data we want is // already visible, since self.len() used Ordering::Acquire on // `self.count` which synchronizes with the Ordering::Release write in // `self.push`. let ptr = *self.data[array as usize].get(); &*ptr.add(offset) } /// Convert into a standard `Vec` pub fn into_vec(self) -> Vec { let mut vec = Vec::with_capacity(self.len()); for idx in 0..self.len() { let (array, offset) = indices(idx); // We use a Relaxed load of the pointer, because the loop above (which // ends before `self.len()`) should ensure that the data we want is // already visible, since it Acquired `self.count` which synchronizes // with the write in `self.push`. let ptr = unsafe { *self.data[array as usize].get() }; // Copy the element value. The copy remaining in the array must not // be used again (i.e. make sure we do not drop it) let value = unsafe { ptr.add(offset).read() }; vec.push(value); } // Prevent dropping the copied-out values by marking the count as 0 before // our own drop is run self.count.store(0, Ordering::Relaxed); vec } } impl std::fmt::Debug for AppendOnlyVec where T: std::fmt::Debug, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_list().entries(self.iter()).finish() } } impl Index for AppendOnlyVec { type Output = T; fn index(&self, idx: usize) -> &Self::Output { assert!(idx < self.len()); // this includes the required ordering memory barrier let (array, offset) = indices(idx); // The ptr value below is safe, because the length check above will // ensure that the data we want is already visible, since it used // Ordering::Acquire on `self.count` which synchronizes with the // Ordering::Release write in `self.push`. let ptr = unsafe { *self.data[array as usize].get() }; unsafe { &*ptr.add(offset) } } } impl IndexMut for AppendOnlyVec { fn index_mut(&mut self, idx: usize) -> &mut Self::Output { assert!(idx < self.len()); // this includes the required ordering memory barrier let (array, offset) = indices(idx); // The ptr value below is safe, because the length check above will // ensure that the data we want is already visible, since it used // Ordering::Acquire on `self.count` which synchronizes with the // Ordering::Release write in `self.push`. let ptr = unsafe { *self.data[array as usize].get() }; // `&mut` is safe because there can be no access to data owned by // `self` except via `self`, and we have `&mut` on `self` unsafe { &mut *ptr.add(offset) } } } impl Drop for AppendOnlyVec { fn drop(&mut self) { // First we'll drop all the `T` in a slightly sloppy way. FIXME this // could be optimized to avoid reloading the `ptr`. for idx in 0..self.len() { let (array, offset) = indices(idx); // We use a Relaxed load of the pointer, because the loop above (which // ends before `self.len()`) should ensure that the data we want is // already visible, since it Acquired `self.count` which synchronizes // with the write in `self.push`. let ptr = unsafe { *self.data[array as usize].get() }; unsafe { std::ptr::drop_in_place(ptr.add(offset)); } } // Now we will free all the arrays. for array in 0..self.data.len() as u32 { // This load is relaxed because no other thread can have a reference // to Self because we have a &mut self. let ptr = unsafe { *self.data[array as usize].get() }; if !ptr.is_null() { let layout = self.layout(array); unsafe { std::alloc::dealloc(ptr as *mut u8, layout) }; } else { break; } } } } /// An `Iterator` for the values contained in the `AppendOnlyVec` #[derive(Debug)] pub struct IntoIter(std::vec::IntoIter); impl Iterator for IntoIter { type Item = T; fn next(&mut self) -> Option { self.0.next() } fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } } impl DoubleEndedIterator for IntoIter { fn next_back(&mut self) -> Option { self.0.next_back() } } impl ExactSizeIterator for IntoIter { fn len(&self) -> usize { self.0.len() } } impl IntoIterator for AppendOnlyVec { type Item = T; type IntoIter = IntoIter; fn into_iter(self) -> Self::IntoIter { IntoIter(self.into_vec().into_iter()) } } impl FromIterator for AppendOnlyVec { fn from_iter>(iter: I) -> Self { let out = Self::new(); for x in iter { let idx = out.pre_push(x); // We can be relaxed here because no one else has access to // this data, and if it is passed to another thread that will involve // the appropriate memory barrier. out.count.store(idx + 1, Ordering::Relaxed); } out } } impl From> for AppendOnlyVec { fn from(value: Vec) -> Self { value.into_iter().collect() } } #[test] fn test_pushing_and_indexing() { let v = AppendOnlyVec::::new(); for n in 0..50 { v.push(n); assert_eq!(v.len(), n + 1); for i in 0..(n + 1) { assert_eq!(v[i], i); } } let vec: Vec = v.iter().copied().collect(); let ve2: Vec = (0..50).collect(); assert_eq!(vec, ve2); } #[test] fn test_parallel_pushing() { use std::sync::Arc; let v = Arc::new(AppendOnlyVec::::new()); let mut threads = Vec::new(); const N: u64 = 100; for thread_num in 0..N { let v = v.clone(); threads.push(std::thread::spawn(move || { let which1 = v.push(thread_num); let which2 = v.push(thread_num); assert_eq!(v[which1 as usize], thread_num); assert_eq!(v[which2 as usize], thread_num); })); } for t in threads { t.join().ok(); } for thread_num in 0..N { assert_eq!(2, v.iter().copied().filter(|&x| x == thread_num).count()); } } #[test] fn test_into_vec() { struct SafeToDrop(bool); impl Drop for SafeToDrop { fn drop(&mut self) { assert!(self.0); } } let v = AppendOnlyVec::new(); for _ in 0..50 { v.push(SafeToDrop(false)); } let mut v = v.into_vec(); for i in v.iter_mut() { i.0 = true; } } #[test] fn test_push_then_index_mut() { let mut v = AppendOnlyVec::::new(); for i in 0..1024 { v.push(i); } for i in 0..1024 { v[i] += i; } for i in 0..1024 { assert_eq!(v[i], 2 * i); } } #[test] fn test_from_vec() { for v in [vec![5_i32, 4, 3, 2, 1], Vec::new(), vec![1]] { let aov: AppendOnlyVec = v.clone().into(); assert_eq!(v, aov.into_vec()); } }