1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
#![doc( html_root_url = "https://docs.rs/contrie/0.1.0/contrie/", test(attr(deny(warnings))) )] // Note: we can't use forbid(unsafe_code). We do allow unsafe code in the raw submodule (but not // outside of it). #![deny(missing_docs, warnings, unsafe_code)] //! A concurrent trie. //! //! The crate provides implementation of a hash map and a hash set. Unlike the versions from the //! standard library, these allow concurrent accesses and *modifications* from multiple threads at //! once, without locking. Actually, even the inner implementation is [lock-free] and the lookups //! are [wait-free] (though even the lookup methods may invoke collecting garbage in //! [`crossbeam-epoch`] which while unlikely might contain non-wait-free functions). //! //! # Downsides //! //! The concurrent access does not come for free, if you can, you should prefer using the usual //! single-threaded alternatives or consider combining them with locks. In particular, there are //! several downsides. //! //! * Under the hood, the [crossbeam-epoch] is used to manage memory. This has the effect that //! removed elements are not deleted at precisely known moment. While the [crossbeam-epoch] is //! usually reasonably fast in collecting garbage, this might be unsuitable for object with //! observable side effects in their destructors (like, containing open files that need to be //! flushed and closed). //! * As even after removing an element this element might be still being accessed by another //! thread, there's no way to get an owned access to the original element once it is inserted. //! Depending on the flavour, the data structure either clones the data or returns [`Arc`]s to //! them. //! * They are slower in single-threaded usage and use more memory than their standard library //! counter-parts. While the mileage may differ, 2-3 times slowdown was measured in trivial //! benchmarks. //! * The order of modifications as seen by different threads may be different (if one thread //! inserts elements `a` and `b`, another thread may see `b` already inserted while `a` still not //! being present). In other words, the existence of values of different keys is considered //! independent on each other. This limitation might be lifted in the future by letting the user //! configure the desired sequentially consistent behaviour at the cost of further slowdown. //! * Iteration doesn't take a snapshot at a given time. In other words, if the data structure is //! modified during the iteration (even if by the same thread that iterates), the changes may or //! may not be reflected in the list of iterated elements. //! * Iteration pins an epoch for the whole time it iterates, possibly delaying releasing some //! memory. Therefore, it is advised not to hold onto iterators for extended periods of time. //! //! # The gist of the data structure //! //! Internally, the data structure is an array-mapped trie. At each level there's an array of 16 //! pointers. They get indexed by 4 bits of the hash of the key. The branches are kept as short as //! possible to still distinguish between different hashes (once a subtree contains only one value, //! it contains only the data leaf without further intermediate levels). These pointers can be //! updated atomically with compare-and-swap operations. //! //! The idea was inspired by this [article] and [wikipedia entry], but with severe simplifications. //! The simplifications were done to lower the number of traversed pointers during operations and //! to gain more confidence in correctness of the implementation, however at the cost of the //! snapshots for iteration and higher memory consumption. Pruning of the trie of unneeded branches //! on removals was preserved. //! //! For further technical details, arguments of correctness and similar, see the source code and //! comments in there, especially the [`raw`] module. //! //! # Examples //! //! ```rust //! use contrie::ConMap; //! use crossbeam_utils::thread; //! //! let map = ConMap::new(); //! //! thread::scope(|s| { //! s.spawn(|_| { //! map.insert(1, 2); //! }); //! s.spawn(|_| { //! map.insert(2, 3); //! }); //! }).unwrap(); //! //! assert_eq!(3, *map.get(&2).unwrap().value()); //! ``` //! //! [wait-free]: https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom //! [lock-free]: https://en.wikipedia.org/wiki/Non-blocking_algorithm#Lock-freedom //! [crossbeam-epoch]: https://docs.rs/crossbeam-epoch //! [`Arc`]: std::sync::Arc //! [article]: https://www.researchgate.net/publication/221643801_Concurrent_Tries_with_Efficient_Non-Blocking_Snapshots //! [wikipedia entry]: https://en.wikipedia.org/wiki/Ctrie mod existing_or_new; pub mod map; pub mod raw; // Some integration-like tests live here, instead of crate/tests. This is because this allows cargo // to compile them in parallel with the crate and also run them more in parallel. And I like to get // all the test failures at once. // // Interface is tested through doctests anyway. #[cfg(test)] mod tests; pub use self::existing_or_new::ExistingOrNew; pub use self::map::ConMap;