├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── README.md ├── _clang_format ├── container ├── .gitignore └── container.cpp ├── detail ├── atomic_shared_ptr.hpp └── atomic_shared_ptr_traits.hpp ├── jss ├── README.md ├── atomic_shared_ptr └── atomic_shared_ptr_traits.hpp ├── loop ├── .gitignore ├── Makefile └── loop.cpp ├── measurements ├── CMakeLists.txt ├── display.py ├── measure.cpp └── measure.py ├── rcu_ptr.hpp └── tests ├── CMakeLists.txt ├── ExecuteInLoop.hpp ├── countdown.hpp ├── driver.cpp ├── orders.hpp ├── rcu_ptr_under_test.hpp ├── rcu_race.cpp ├── rcu_unit.cpp ├── scoped_thread.hpp ├── std_asp_concurrent.cpp └── std_asp_core.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build* 2 | cmk.json* 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "googletest"] 2 | path = googletest 3 | url = https://github.com/google/googletest.git 4 | [submodule "tbb"] 5 | path = tbb 6 | url = git@github.com:01org/tbb.git 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.2) 2 | 3 | include(CTest) 4 | 5 | # dispatch linking ThreadSanitizer lib based 6 | # on target platform 7 | # 8 | function (set_libtsan_link_flag) 9 | message ("-- Compiler ID: ${CMAKE_CXX_COMPILER_ID}") 10 | message ("-- Compiler Version: ${CMAKE_CXX_COMPILER_VERSION}") 11 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.3.0") 12 | set (_CRF_LIBTSAN_LINK_FLAG "-static-libtsan") 13 | else(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.3.0") 14 | set (_CRF_LIBTSAN_LINK_FALG "") 15 | endif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.3.0") 16 | endfunction () 17 | 18 | 19 | # setup sanitized build 20 | # 21 | if (_CRF_SANITIZE_BUILD) 22 | add_compile_options (-fno-omit-frame-pointer) 23 | 24 | if (${_CRF_SANITIZE_BUILD} STREQUAL "msan") 25 | add_compile_options (-fsanitize=memory) 26 | message ("-- Making MemorySanitize-d Build") 27 | 28 | elseif (${_CRF_SANITIZE_BUILD} STREQUAL "asan") 29 | add_compile_options (-fsanitize=address) 30 | message ("-- Making AddressSanitize-d Build") 31 | 32 | else () 33 | set_libtsan_link_flag () 34 | add_compile_options (-fsanitize=thread) 35 | add_compile_options (${_CRF_LIBTSAN_LINK_FLAG}) 36 | message ("-- Making ThreadSanitize-d Build") 37 | 38 | endif () 39 | endif () 40 | 41 | 42 | add_compile_options (-std=c++14) 43 | add_compile_options (-Wall) 44 | add_compile_options (-Wextra) 45 | add_compile_options (-Wdeprecated) 46 | add_compile_options (-pedantic) 47 | 48 | 49 | if (CMAKE_BUILD_TYPE MATCHES Release) 50 | add_compile_options (-O3) 51 | 52 | else () 53 | add_compile_options (-O0) 54 | add_compile_options (-ggdb3) 55 | 56 | endif () 57 | 58 | if(UNIX AND NOT APPLE) 59 | set(LINUX TRUE) 60 | endif() 61 | set(ATOMICLIB "") 62 | # Currently OSX cannot link with latomic 63 | if(LINUX) 64 | set(ATOMICLIB atomic) 65 | endif() 66 | 67 | include_directories ("${PROJECT_SOURCE_DIR}") 68 | add_subdirectory (googletest) 69 | # Supress all warnings from gtest/gmock 70 | target_compile_options(gmock PRIVATE -w) 71 | target_compile_options(gmock_main PRIVATE -w) 72 | target_compile_options(gtest PRIVATE -w) 73 | target_compile_options(gtest_main PRIVATE -w) 74 | 75 | add_compile_options(-Werror) 76 | add_subdirectory (tests) 77 | add_subdirectory (measurements) 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rcu_ptr 2 | 3 | ## Introduction 4 | Read-copy-update pointer (`rcu_ptr`) is a special smart pointer which can be used to exchange data between threads. 5 | Read-copy-update (RCU) is a general synchronization mechanism, which is similar to readers-writers lock. 6 | It allows extremely low overhead for reads. However, RCU updates can be expensive, as they must leave the old versions of the data structure in place to accommodate pre-existing readers \[[1][1], [2][2]\]. 7 | RCU is useful when you have several readers and few writers. 8 | Depending on the size of the data you want to update, writes can be really slow, since they need to copy. 9 | Therefore, it's worth to do measurments and analyze the characteristics of the inputs and environment of your system. 10 | 11 | `rcu_ptr` implements the read-copy-update mechanism by wrapping a `std::shared_ptr` (in a way, it has some similarity to `std::weak_ptr`). 12 | 13 | ### atomic_shared_ptr 14 | `rcu_ptr` relies on the free [atomic_...](http://en.cppreference.com/w/cpp/memory/shared_ptr/atomic) function overloads for `std::shared_ptr`. Would be nice to use an [atomic_shared_ptr](http://en.cppreference.com/w/cpp/experimental/atomic_shared_ptr), but currently that is still in experimental phase. 15 | We use atomic shared_ptr operations which are implemented in terms of a spin-lock (most probably that's how it is implemented in the currently available standard libraries). 16 | Having a lock-free atomic_shared_ptr would be really benefitial. However, implementing a lock-free atomic_shared_ptr in a portable way can have extreme difficulties \[[3][3]\]. Thought it might be easier on architectures, where we have double word CAS operations. 17 | 18 | ## Why do we need RCU and `rcu_ptr`? 19 | Imagine we have a collection and several readers and some writer threads on it. 20 | It is a common way to make the collection thread safe by holding a lock until the iteration is finished (on the reader thread). 21 | Example: 22 | ```c++ 23 | class X { 24 | std::vector v; 25 | mutable std::mutex m; 26 | public: 27 | int sum() const { // read operation 28 | std::lock_guard lock{m}; 29 | return std::accumulate(v.begin(), v.end(), 0); 30 | } 31 | void add(int i) { // write operation 32 | std::lock_guard lock{m}; 33 | v.push_back(i); 34 | } 35 | }; 36 | ``` 37 | This does not scale well, and prone to some errors (e.g. you can have a deadlock only if you use a lock). 38 | The first idea to make it better is to have a shared_ptr and hold the lock only until that is copied by the reader or updated by the writer: 39 | ```c++ 40 | class X { 41 | std::shared_ptr> v; 42 | mutable std::mutex m; 43 | public: 44 | X() : v(std::make_shared>()) {} 45 | int sum() const { // read operation 46 | std::shared_ptr> local_copy; 47 | { 48 | std::lock_guard lock{m}; 49 | local_copy = v; 50 | } 51 | return std::accumulate(local_copy->begin(), local_copy->end(), 0); 52 | } 53 | void add(int i) { // write operation 54 | std::shared_ptr> local_copy; 55 | { 56 | std::lock_guard lock{m}; 57 | local_copy = v; 58 | } 59 | local_copy->push_back(i); 60 | { 61 | std::lock_guard lock{m}; 62 | v = local_copy; 63 | } 64 | } 65 | }; 66 | ``` 67 | Now we have a race on the pointee itself during the write. 68 | So we need to have a deep copy: 69 | ```c++ 70 | void add(int i) { // write operation 71 | std::shared_ptr> local_copy; 72 | { 73 | std::lock_guard lock{m}; 74 | local_copy = v; 75 | } 76 | auto local_deep_copy = std::make_shared>(*local_copy); 77 | local_deep_copy->push_back(i); 78 | { 79 | std::lock_guard lock{m}; 80 | v = local_deep_copy; 81 | } 82 | } 83 | ``` 84 | The copy construction of the underlying data (vector) is thread safe, since the copy ctor param is a const ref `const std::vector&`. 85 | Now, if there are two concurrent write operations than we might miss one update. 86 | We'd need to check whether the other writer had done an update after the actual writer has loaded the local copy. 87 | If it did then we should load the data again and try to do the update again. 88 | This leads to the general idea of using an `atomic_compare_exchange` in a while loop. 89 | So we could use an `atomic_shared_ptr` from C++17, but until then we have to settle for the free funtcion overloads for shared_ptr: 90 | ```c++ 91 | class X { 92 | std::shared_ptr> v; 93 | public: 94 | X() : v(std::make_shared>()) {} 95 | int sum() const { // read operation 96 | auto local_copy = std::atomic_load(&v); 97 | return std::accumulate(local_copy->begin(), local_copy->end(), 0); 98 | } 99 | void add(int i) { // write operation 100 | auto local_copy = std::atomic_load(&v); 101 | auto exchange_result = false; 102 | while (!exchange_result) { 103 | // we need a deep copy 104 | auto local_deep_copy = std::make_shared>( 105 | *local_copy); 106 | local_deep_copy->push_back(i); 107 | exchange_result = 108 | std::atomic_compare_exchange_strong(&v, &local_copy, 109 | local_deep_copy); 110 | } 111 | } 112 | }; 113 | ``` 114 | 115 | Also (regarding the write operation), since we are already in a while loop we can use `atomic_compare_exchange_weak`. 116 | That can result in a performance gain on some platforms. 117 | See http://stackoverflow.com/questions/25199838/understanding-stdatomiccompare-exchange-weak-in-c11 118 | Note, we could move construct the 3rd parameter of `atomic_compare_exchange_strong`, therefore we could spare a reference count increment and decrement: 119 | ```c++ 120 | exchange_result = 121 | std::atomic_compare_exchange_strong(&v, &local_copy, 122 | std::move(local_deep_copy)); 123 | ``` 124 | 125 | In the current form of class `X`, nothing stops an other programmer (e.g. a naive maintainer of the code years later) to add a new reader operation, like this: 126 | ```c++ 127 | int another_sum() const { 128 | return std::accumulate(v->begin(), v->end(), 0); 129 | } 130 | ``` 131 | This is definetly a race condition and a problem. 132 | And this is the exact reason why `rcu_ptr` was created. 133 | The goal is to provide a general higher level abstraction above `atomic_shared_ptr`: 134 | ```c++ 135 | class X { 136 | rcu_ptr> v; 137 | 138 | public: 139 | X() { v.reset(std::make_shared>()); } 140 | int sum() const { // read operation 141 | std::shared_ptr> local_copy = v.read(); 142 | return std::accumulate(local_copy->begin(), local_copy->end(), 0); 143 | } 144 | void add(int i) { // write operation 145 | v.copy_update([i](std::vector* copy) { 146 | copy->push_back(i); 147 | }); 148 | } 149 | }; 150 | ``` 151 | The `read` method of `rcu_ptr` returns a `shared_ptr` by value, therefore it is thread safe. 152 | The existence of the shared_ptr in the scope enforces that the read object will live at least until this read operation finishes. 153 | By using the shared_ptr this way, we are free from ABA problems, see [Anthony Williams - Why do we need atomic_shared_ptr?](https://www.justsoftwaresolutions.co.uk/threading/why-do-we-need-atomic_shared_ptr.html). 154 | The `copy_update` method receives a lambda. This lambda is called whenever an update needs to be done, i.e. it will be called continuously until the update is successful. 155 | The lambda receives a `T*` for the copy of the actual data. 156 | We can modify the copy of the actual data inside the lambda. 157 | The `reset` method receives a `const shared_ptr&` with which we can overwrite the actual contained shared_ptr. 158 | 159 | ## Design Rationale 160 | ### Ordering 161 | In case of `reset` and `read` we use `memory_order_relaxed`. 162 | 163 | For `copy_update` we can use consume-release semantics. 164 | There is a nice long data dependency chain here: 165 | ```c++ 166 | std::shared_ptr sp_l = 167 | std::atomic_load_explicit(&sp, std::memory_order_consume); 168 | auto exchange_result = false; 169 | while (!exchange_result) { 170 | 171 | // deep copy 172 | auto r = std::make_shared(*sp_l); 173 | 174 | // update 175 | std::forward(fun)(r.get()); 176 | 177 | exchange_result = std::atomic_compare_exchange_strong_explicit( 178 | &sp, &sp_l, r, std::memory_order_release, 179 | std::memory_order_release); 180 | ``` 181 | If we'd use relaxed ordering and if the `fun` is inlined and `fun` itself is not an ordering operation or it does not contain any fences then the load or the compare_exchange might be reordered in between the middle of `fun`. 182 | Though, there is a data dependency chain: `sp`->`sp_l`->`r`->`compare_exchange(...,r)`. 183 | So if all the architectures would be preserving data dependency ordering, than we'd be fine with relaxed. 184 | But, some architectures don't preserve data dependency ordering (e.g. DEC Alpha), therefore we need to explicitly state that we rely on that the cpu will not reorder data dependent operations. 185 | This is what we express with the consume-release semantics. 186 | This is perfecty aligned with the Linux kernel RCU implmentation, they use consume-relase too. 187 | 188 | Of course, all the mentioned ordering constraints has sense only if we use a lock-free atomic_shared_ptr. 189 | If we use a non-lock free one, then that enforces an acquire-release semantics because it must use internally a spinlock for all of its member functions. 190 | 191 | ### Public interface 192 | `copy_update`
193 | The signature of the lambda we pass to `copy_update` could have different forms. 194 | * `T(const T&)` We receive a reference to the actual value held. 195 | The user of `rcu_ptr` has to do the copy, and return with that. 196 | * Pros: 197 | * The user must do the copy, but they will surely know there is a copy happening. 198 | * Cons: 199 | * Longer repetative work in user code (always do the copy) 200 | 201 | * `shared_ptr(shared_ptr)` We receive a shared_ptr to const to the actual value held. 202 | The user had to do the copy with make_shared. 203 | * Pros: 204 | * It is consistent with the rest of the member functions. 205 | I.e. reset takes a shared_ptr too. 206 | * Cons: 207 | * We copy the shared pointer 208 | 209 | * `shared_ptr(const shared_ptr&)` We receive a shared_ptr to the actual value held. 210 | The user had to do the copy with make_shared. 211 | * Pros: 212 | * It is consistent with the rest of the member functions. 213 | I.e. reset takes a shared_ptr too. 214 | * We do not copy the shared_ptr 215 | * Cons: 216 | * We give a non-const pointer the actual data, opens possibility to a data-race 217 | 218 | * `void(T&)` We receive a reference to the copied value. 219 | * Pros: 220 | * Simple 221 | * Less things to write in user code 222 | * Cons: 223 | * It might not be ovious that we do a copy in the background. 224 | * The signature is not consistent with the other member functions, which are all related to a shared_ptr (e.g. read returns with shared_ptr). 225 | 226 | * `void(T*)` We receive a pointer to the copied value. 227 | * Pros: 228 | * Simple 229 | * Less things to write in user code 230 | * The signature is quite consistent with the other member functions, we pass a pointer. 231 | * Can handle the case when the contained shared_ptr is a nullptr. 232 | * Cons: 233 | * It might not be ovious that we do a copy in the background. 234 | 235 | At the moment, the last one is the chosen one. 236 | 237 | ## Usage 238 | 239 | `rcu_ptr` depends on the features of the `C++11` standard. 240 | `rcu_ptr` has two default template parameters which makes it possible to use a different `atomic_shared_ptr` other than the default setting. 241 | By default we use a wrapper class which uses the [free function overloads for `std::shared_ptr`] (http://en.cppreference.com/w/cpp/memory/shared_ptr/atomic). 242 | Note, these overloads are not implemented in GCC/libstdc++ if the version is less than 5.0 (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57250). 243 | We can use the lock-free `atomic_shared_ptr` from Anthony Williams like this: 244 | ``` 245 | #include 246 | #include 247 | #include 248 | 249 | using asp_traits = jss::atomic_shared_ptr_traits; 250 | 251 | template 252 | using RcuPtr = rcu_ptr; 253 | 254 | void bar(asp_traits::shared_ptr sp); 255 | void foo() { 256 | auto i = asp_traits::make_shared(42); 257 | bar(i); 258 | } 259 | void f() { 260 | RcuPtr p; 261 | auto const new_ = asp_traits::make_shared(42); 262 | p.reset(new_); 263 | } 264 | ``` 265 | `asp_traits` provides the actual type of the `shared_ptr` (and `make_shared`) which is connected to the underlying `atomic_shared_ptr`. 266 | For extensive usage examples please check in `test/rcu_race.cpp`. 267 | 268 | 269 | ### Building 270 | 271 | The library is header only: `rcu_ptr.hpp`, thus it requires no build. 272 | The directory `detail` must be in the include path as well. 273 | If you want to use the lock-free implementation of `atomic_shared_ptr` (from Anthony Williams) as the underlying `shared_ptr` then `jss` must be in the path too and `rcu_ptr` shall be instantiated with types from the jss namespace. 274 | 275 | Tests and measurements are included to verify the concept and expected behaviour of the `rcu_ptr`. 276 | We use [CMake] (https://cmake.org/) and the tests may be built with [ThreadSanitizer] (https://code.google.com/archive/p/data-race-test/wikis/ThreadSanitizer.wiki) introduced in GCC 4.8. 277 | The tests require polimorphic lambdas from C++14. 278 | To build the measurements as well, we need to install URCU in the system (http://liburcu.org/). 279 | ```bash 280 | git clone git@github.com:martong/rcu_ptr.git 281 | cd rcu_ptr 282 | git submodule init 283 | git submodule update 284 | mkdir build 285 | cd build 286 | cmake -G Ninja .. 287 | ninja 288 | ctest 289 | ``` 290 | 291 | We tested `rcu_ptr` on Linux with gcc 5 and 7 and our primary target is Linux/gcc7. 292 | On macOs we could not use the `jss::atomic_shared_ptr`, so the jss targets are not supported on macOs. 293 | 294 | [1]: https://lwn.net/Articles/262464/ 295 | [2]: https://en.wikipedia.org/wiki/Read-copy-update 296 | [3]: https://github.com/brycelelbach/cppnow_presentations_2016/blob/master/01_wednesday/implementing_a_lock_free_atomic_shared_ptr.pdf 297 | 298 | ### Acknowledgement 299 | 300 | Many thanks to `bucienator` for having valuable discussions about the implementation, and the public interface. 301 | Thanks for Máté Cserna, for his really helpful comments. 302 | Thanks for Jeff Preshing for his wonderful [blog](http://preshing.com/archives/), and Anthony Williams for his [book](http://www.amazon.com/C-Concurrency-Action-Practical-Multithreading/dp/1933988770). 303 | -------------------------------------------------------------------------------- /_clang_format: -------------------------------------------------------------------------------- 1 | --- 2 | # BasedOnStyle: Google 3 | AccessModifierOffset: -4 4 | ConstructorInitializerIndentWidth: 4 5 | AlignEscapedNewlinesLeft: true 6 | AlignTrailingComments: true 7 | AllowAllParametersOfDeclarationOnNextLine: true 8 | AllowShortIfStatementsOnASingleLine: true 9 | AllowShortLoopsOnASingleLine: true 10 | AlwaysBreakTemplateDeclarations: true 11 | AlwaysBreakBeforeMultilineStrings: true 12 | BreakBeforeBinaryOperators: false 13 | BreakBeforeTernaryOperators: true 14 | BreakConstructorInitializersBeforeComma: false 15 | BinPackParameters: true 16 | ColumnLimit: 80 17 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 18 | DerivePointerBinding: true 19 | ExperimentalAutoDetectBinPacking: false 20 | IndentCaseLabels: true 21 | MaxEmptyLinesToKeep: 1 22 | NamespaceIndentation: None 23 | ObjCSpaceBeforeProtocolList: false 24 | PenaltyBreakBeforeFirstCallParameter: 1 25 | PenaltyBreakComment: 60 26 | PenaltyBreakString: 1000 27 | PenaltyBreakFirstLessLess: 120 28 | PenaltyExcessCharacter: 1000000 29 | PenaltyReturnTypeOnItsOwnLine: 200 30 | PointerBindsToType: true 31 | SpacesBeforeTrailingComments: 1 32 | Cpp11BracedListStyle: true 33 | Standard: Auto 34 | IndentWidth: 4 35 | TabWidth: 4 36 | UseTab: Never 37 | BreakBeforeBraces: Attach 38 | IndentFunctionDeclarationAfterType: true 39 | SpacesInParentheses: false 40 | SpacesInAngles: false 41 | SpaceInEmptyParentheses: false 42 | SpacesInCStyleCastParentheses: false 43 | SpaceAfterControlStatementKeyword: true 44 | SpaceBeforeAssignmentOperators: true 45 | ContinuationIndentWidth: 4 46 | ... 47 | 48 | -------------------------------------------------------------------------------- /container/.gitignore: -------------------------------------------------------------------------------- 1 | container 2 | -------------------------------------------------------------------------------- /container/container.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "../rcu_ptr.hpp" 6 | #include "../race_test/ExecuteInLoop.hpp" 7 | #include 8 | #include 9 | 10 | /** 11 | * This file contains helper snipets for README.md 12 | */ 13 | 14 | //class X { 15 | //std::vector v; 16 | //mutable std::mutex m; 17 | //public: 18 | //int sum() const { // read operation 19 | //std::lock_guard lock{m}; 20 | //return std::accumulate(v.begin(), v.end(), 0); 21 | //} 22 | //void add(int i) { // write operation 23 | //std::lock_guard lock{m}; 24 | //v.push_back(i); 25 | //} 26 | //}; 27 | 28 | // RACE on the pointee: 29 | //class X { 30 | //std::shared_ptr> v; 31 | //mutable std::mutex m; 32 | //public: 33 | //X() : v(std::make_shared>()) {} 34 | //int sum() const { // read operation 35 | //std::shared_ptr> local_copy; 36 | //{ 37 | //std::lock_guard lock{m}; 38 | //local_copy = v; 39 | //} 40 | //return std::accumulate(local_copy->begin(), local_copy->end(), 0); 41 | //} 42 | //void add(int i) { // write operation 43 | //std::shared_ptr> local_copy; 44 | //{ 45 | //std::lock_guard lock{m}; 46 | //local_copy = v; 47 | //} 48 | //local_copy->push_back(i); 49 | //{ 50 | //std::lock_guard lock{m}; 51 | //v = local_copy; 52 | //} 53 | //} 54 | //}; 55 | 56 | // if there are two concurrent write operations than we might miss one update, 57 | // assert will fail 58 | //class X { 59 | //std::shared_ptr> v; 60 | //mutable std::mutex m; 61 | //public: 62 | //X() : v(std::make_shared>()) {} 63 | //int sum() const { // read operation 64 | //std::shared_ptr> local_copy; 65 | //{ 66 | //std::lock_guard lock{m}; 67 | //local_copy = v; 68 | //} 69 | //return std::accumulate(local_copy->begin(), local_copy->end(), 0); 70 | //} 71 | //void add(int i) { // write operation 72 | //std::shared_ptr> local_copy; 73 | //{ 74 | //std::lock_guard lock{m}; 75 | //local_copy = v; 76 | //} 77 | //auto local_deep_copy = std::make_shared>(*local_copy); 78 | //local_deep_copy->push_back(i); 79 | //{ 80 | //std::lock_guard lock{m}; 81 | //v = local_deep_copy; 82 | //} 83 | //} 84 | //}; 85 | 86 | //class X { 87 | //std::shared_ptr> v; 88 | //public: 89 | //X() : v(std::make_shared>()) {} 90 | //int sum() const { // read operation 91 | //auto local_copy = std::atomic_load(&v); 92 | //return std::accumulate(local_copy->begin(), local_copy->end(), 0); 93 | //} 94 | //void add(int i) { // write operation 95 | //auto local_copy = std::atomic_load(&v); 96 | //auto exchange_result = false; 97 | //while (!exchange_result) { 98 | //// we need a deep copy 99 | //auto local_deep_copy = std::make_shared>( 100 | //*local_copy); 101 | //local_deep_copy->push_back(i); 102 | //exchange_result = 103 | //std::atomic_compare_exchange_strong(&v, &local_copy, 104 | //local_deep_copy); 105 | //} 106 | //} 107 | //}; 108 | 109 | -------------------------------------------------------------------------------- /detail/atomic_shared_ptr.hpp: -------------------------------------------------------------------------------- 1 | // atomic_shared_ptr.hpp 2 | // 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | namespace detail { namespace __std { 9 | 10 | template< typename T > 11 | class atomic_shared_ptr { 12 | public: 13 | constexpr atomic_shared_ptr() noexcept = default; 14 | constexpr atomic_shared_ptr(std::shared_ptr desired) noexcept 15 | : sptr{desired} 16 | {} 17 | 18 | atomic_shared_ptr(const atomic_shared_ptr&) = delete; 19 | 20 | void operator= (std::shared_ptr desired) noexcept 21 | { store(desired); } 22 | 23 | void operator= (const atomic_shared_ptr&) = delete; 24 | 25 | bool is_lock_free() const noexcept 26 | { return std::atomic_is_lock_free(&sptr); } 27 | 28 | void store(std::shared_ptr desired, std::memory_order order = std::memory_order_seq_cst) noexcept 29 | { std::atomic_store_explicit(&sptr, desired, order); } 30 | 31 | std::shared_ptr load(std::memory_order order = std::memory_order_seq_cst) const noexcept 32 | { return std::atomic_load_explicit(&sptr, order); } 33 | 34 | operator std::shared_ptr() const noexcept 35 | { return load(); } 36 | 37 | std::shared_ptr exchange(std::shared_ptr desired, std::memory_order order = std::memory_order_seq_cst ) noexcept 38 | { return std::atomic_exchange_explicit(&sptr, desired, order); } 39 | 40 | bool compare_exchange_weak(std::shared_ptr& expected, const std::shared_ptr& desired, 41 | std::memory_order success, std::memory_order failure) noexcept 42 | { return std::atomic_compare_exchange_weak_explicit(&sptr, &expected, desired, success, failure); } 43 | 44 | bool compare_exchange_weak(std::shared_ptr& expected, std::shared_ptr&& desired, 45 | std::memory_order success, std::memory_order failure) noexcept 46 | { return std::atomic_compare_exchange_weak_explicit(&sptr, &expected, desired, success, failure); } 47 | 48 | bool compare_exchange_weak(std::shared_ptr& expected, const std::shared_ptr& desired, 49 | std::memory_order order = std::memory_order_seq_cst) noexcept 50 | { return compare_exchange_weak(expected, desired, order, order); } 51 | 52 | bool compare_exchange_weak(std::shared_ptr& expected, std::shared_ptr&& desired, 53 | std::memory_order order = std::memory_order_seq_cst) noexcept 54 | { return compare_exchange_weak(expected, std::move(desired), order, order); } 55 | 56 | 57 | bool compare_exchange_strong(std::shared_ptr& expected, const std::shared_ptr& desired, 58 | std::memory_order success, std::memory_order failure) noexcept 59 | { return std::atomic_compare_exchange_strong_explicit(&sptr, &expected, desired, success, failure); } 60 | 61 | bool compare_exchange_strong(std::shared_ptr& expected, std::shared_ptr&& desired, 62 | std::memory_order success, std::memory_order failure) noexcept 63 | { return std::atomic_compare_exchange_strong_explicit(&sptr, &expected, desired, success, failure); } 64 | 65 | bool compare_exchange_strong(std::shared_ptr& expected, const std::shared_ptr& desired, 66 | std::memory_order order = std::memory_order_seq_cst) noexcept 67 | { return compare_exchange_strong(expected, desired, order, order); } 68 | 69 | bool compare_exchange_strong(std::shared_ptr& expected, std::shared_ptr&& desired, 70 | std::memory_order order = std::memory_order_seq_cst) noexcept 71 | { return compare_exchange_strong(expected, std::move(desired), order, order); } 72 | 73 | private: 74 | std::shared_ptr sptr; 75 | 76 | }; 77 | 78 | 79 | } // namespace __std 80 | } // namespace detail 81 | 82 | -------------------------------------------------------------------------------- /detail/atomic_shared_ptr_traits.hpp: -------------------------------------------------------------------------------- 1 | // atomic_shared_ptr_traits.hpp 2 | // 3 | #pragma once 4 | #include 5 | 6 | namespace detail { 7 | 8 | template< template class AtomicSharedPtr > 9 | struct atomic_shared_ptr_traits 10 | { 11 | template< typename T > 12 | using atomic_shared_ptr = AtomicSharedPtr; 13 | 14 | template< typename T > 15 | using shared_ptr = std::shared_ptr; 16 | 17 | template< typename T, typename... Args > 18 | static auto make_shared(Args &&...args) 19 | { return std::make_shared(std::forward(args)...); } 20 | }; 21 | 22 | } // namespace detail 23 | 24 | -------------------------------------------------------------------------------- /jss/README.md: -------------------------------------------------------------------------------- 1 | # README # 2 | 3 | This is an implementation of a lock-free atomic_shared_ptr class template as described in N4162 (http://isocpp.org/files/papers/N4162.pdf). 4 | 5 | It is provided as a single header file released under the BSD license. 6 | -------------------------------------------------------------------------------- /jss/atomic_shared_ptr: -------------------------------------------------------------------------------- 1 | // //-*-C++-*- 2 | // Implementation of atomic_shared_ptr as per N4162 3 | // (http://isocpp.org/files/papers/N4162.pdf) 4 | // 5 | // Copyright (c) 2014, Just Software Solutions Ltd 6 | // All rights reserved. 7 | // 8 | // Redistribution and use in source and binary forms, with or 9 | // without modification, are permitted provided that the 10 | // following conditions are met: 11 | // 12 | // 1. Redistributions of source code must retain the above 13 | // copyright notice, this list of conditions and the following 14 | // disclaimer. 15 | // 16 | // 2. Redistributions in binary form must reproduce the above 17 | // copyright notice, this list of conditions and the following 18 | // disclaimer in the documentation and/or other materials 19 | // provided with the distribution. 20 | // 21 | // 3. Neither the name of the copyright holder nor the names of 22 | // its contributors may be used to endorse or promote products 23 | // derived from this software without specific prior written 24 | // permission. 25 | // 26 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 27 | // CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 28 | // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 29 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 30 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 31 | // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 32 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 33 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 34 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 35 | // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 36 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 37 | // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 38 | // EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 39 | 40 | #ifndef _JSS_ATOMIC_SHARED_PTR 41 | #define _JSS_ATOMIC_SHARED_PTR 42 | #include 43 | #include 44 | 45 | namespace jss{ 46 | template class shared_ptr; 47 | template class weak_ptr; 48 | 49 | struct shared_ptr_data_block_base{}; 50 | 51 | template 52 | struct shared_ptr_deleter_base{ 53 | D d; 54 | 55 | shared_ptr_deleter_base(D& d_): 56 | d(d_) 57 | {} 58 | 59 | template 60 | void do_delete(P p) 61 | { 62 | d(p); 63 | } 64 | }; 65 | 66 | template<> 67 | struct shared_ptr_deleter_base{ 68 | template 69 | void do_delete(T* p) 70 | { 71 | delete p; 72 | } 73 | }; 74 | 75 | struct shared_ptr_header_block_base{ 76 | struct counter{ 77 | unsigned external_counters; 78 | int count; 79 | 80 | counter() noexcept: 81 | external_counters(0), 82 | count(1) 83 | {} 84 | }; 85 | 86 | static unsigned const cast_pointer_count=3; 87 | struct ptr_extension_block{ 88 | std::atomic cast_pointers[cast_pointer_count]; 89 | std::atomic cp_extension; 90 | 91 | ptr_extension_block(): 92 | cp_extension(0) 93 | { 94 | for(unsigned i=0;iget_ptr_index(p)+cast_pointer_count; 123 | } 124 | 125 | void* get_pointer(unsigned index) 126 | { 127 | return (indexget_pointer(index-cast_pointer_count); 130 | } 131 | 132 | ~ptr_extension_block() 133 | { 134 | delete cp_extension.load(); 135 | } 136 | 137 | }; 138 | 139 | std::atomic count; 140 | std::atomic weak_count; 141 | ptr_extension_block cp_extension; 142 | 143 | unsigned use_count() 144 | { 145 | counter c=count.load(std::memory_order_relaxed); 146 | return c.count+(c.external_counters?1:0); 147 | } 148 | 149 | unsigned get_ptr_index(void* p) 150 | { 151 | return cp_extension.get_ptr_index(p); 152 | } 153 | 154 | virtual ~shared_ptr_header_block_base() 155 | {} 156 | 157 | template 158 | T* get_ptr(unsigned index) 159 | { 160 | return static_cast(cp_extension.get_pointer(index)); 161 | } 162 | 163 | shared_ptr_header_block_base(): 164 | count(counter()),weak_count(1) 165 | {} 166 | 167 | virtual void do_delete()=0; 168 | 169 | void delete_object() 170 | { 171 | do_delete(); 172 | dec_weak_count(); 173 | } 174 | 175 | void dec_weak_count() 176 | { 177 | if(weak_count.fetch_add(-1)==1){ 178 | delete this; 179 | } 180 | } 181 | 182 | void inc_weak_count() 183 | { 184 | ++weak_count; 185 | } 186 | 187 | void dec_count() 188 | { 189 | counter old=count.load(std::memory_order_relaxed); 190 | for(;;){ 191 | counter new_count=old; 192 | --new_count.count; 193 | if(count.compare_exchange_weak(old,new_count)) 194 | break; 195 | } 196 | if((old.count==1) && !old.external_counters){ 197 | delete_object(); 198 | } 199 | } 200 | 201 | bool shared_from_weak() 202 | { 203 | counter old=count.load(std::memory_order_relaxed); 204 | while(old.count||old.external_counters){ 205 | counter new_count=old; 206 | ++new_count.count; 207 | if(count.compare_exchange_weak(old,new_count)) 208 | return true; 209 | } 210 | return false; 211 | } 212 | 213 | void inc_count() 214 | { 215 | counter old=count.load(std::memory_order_relaxed); 216 | for(;;){ 217 | counter new_count=old; 218 | ++new_count.count; 219 | if(count.compare_exchange_weak(old,new_count)) 220 | break; 221 | } 222 | } 223 | 224 | void add_external_counters(unsigned external_count) 225 | { 226 | counter old=count.load(std::memory_order_relaxed); 227 | for(;;){ 228 | counter new_count=old; 229 | new_count.external_counters+=external_count; 230 | if(count.compare_exchange_weak(old,new_count)) 231 | break; 232 | } 233 | } 234 | 235 | void remove_external_counter() 236 | { 237 | counter old=count.load(std::memory_order_relaxed); 238 | for(;;){ 239 | counter new_count=old; 240 | --new_count.external_counters; 241 | if(count.compare_exchange_weak(old,new_count)) 242 | break; 243 | } 244 | if(!old.count && (old.external_counters==1)){ 245 | delete_object(); 246 | } 247 | } 248 | 249 | }; 250 | 251 | template 252 | struct shared_ptr_header_block: 253 | shared_ptr_header_block_base{}; 254 | 255 | template 256 | struct shared_ptr_header_separate: 257 | public shared_ptr_header_block

, 258 | private shared_ptr_deleter_base{ 259 | P const ptr; 260 | 261 | void* get_base_ptr() 262 | { 263 | return ptr; 264 | } 265 | 266 | shared_ptr_header_separate(P p): 267 | ptr(p) 268 | {} 269 | 270 | template 271 | shared_ptr_header_separate(P p,D2& d): 272 | shared_ptr_deleter_base(d),ptr(p) 273 | {} 274 | 275 | void do_delete() 276 | { 277 | shared_ptr_deleter_base::do_delete(ptr); 278 | } 279 | }; 280 | 281 | template 282 | struct shared_ptr_header_combined: 283 | public shared_ptr_header_block{ 284 | typedef typename std::aligned_storage::type storage_type; 285 | storage_type storage; 286 | 287 | T* value(){ 288 | return static_cast(get_base_ptr()); 289 | } 290 | 291 | void* get_base_ptr() 292 | { 293 | return &storage; 294 | } 295 | 296 | template 297 | shared_ptr_header_combined(Args&& ... args) 298 | { 299 | new(get_base_ptr()) T(static_cast(args)...); 300 | } 301 | 302 | void do_delete() 303 | { 304 | value()->~T(); 305 | } 306 | }; 307 | 308 | template 309 | shared_ptr make_shared(Args&& ... args); 310 | 311 | template class shared_ptr { 312 | private: 313 | T* ptr; 314 | shared_ptr_header_block_base* header; 315 | 316 | template 317 | friend class atomic_shared_ptr; 318 | template 319 | friend class shared_ptr; 320 | template 321 | friend class weak_ptr; 322 | 323 | template 324 | friend shared_ptr make_shared(Args&& ... args); 325 | 326 | shared_ptr(shared_ptr_header_block_base* header_,unsigned index): 327 | ptr(header_?header_->get_ptr(index):nullptr),header(header_) 328 | { 329 | if(header){ 330 | header->inc_count(); 331 | } 332 | } 333 | 334 | shared_ptr(shared_ptr_header_block_base* header_,T* ptr_): 335 | ptr(ptr_),header(header_) 336 | { 337 | if(header && !header->shared_from_weak()){ 338 | ptr=nullptr; 339 | header=nullptr; 340 | } 341 | } 342 | 343 | shared_ptr(shared_ptr_header_combined* header_): 344 | ptr(header_->value()),header(header_) 345 | {} 346 | 347 | void clear() 348 | { 349 | header=nullptr; 350 | ptr=nullptr; 351 | } 352 | 353 | public: 354 | typedef T element_type; 355 | // 20.8.2.2.1, constructors: 356 | constexpr shared_ptr() noexcept: 357 | ptr(nullptr),header(nullptr) 358 | {} 359 | 360 | template explicit shared_ptr(Y* p) 361 | try: 362 | ptr(p), 363 | header(new shared_ptr_header_separate(p)) 364 | {} 365 | catch(...){ 366 | delete p; 367 | } 368 | 369 | 370 | template shared_ptr(Y* p, D d) 371 | try: 372 | ptr(p),header(new shared_ptr_header_separate(p,d)) 373 | {} 374 | catch(...){ 375 | d(p); 376 | } 377 | 378 | template shared_ptr(std::nullptr_t p, D d) 379 | try: 380 | ptr(p), 381 | header(new shared_ptr_header_separate(p,d)) 382 | {} 383 | catch(...){ 384 | d(p); 385 | } 386 | 387 | template shared_ptr(Y* p, D d, A a); 388 | template shared_ptr(std::nullptr_t p, D d, A a); 389 | 390 | template shared_ptr(const shared_ptr& r, T* p) noexcept: 391 | ptr(p),header(r.header) 392 | { 393 | if(header) 394 | header->inc_count(); 395 | } 396 | 397 | shared_ptr(const shared_ptr& r) noexcept: 398 | ptr(r.ptr),header(r.header) 399 | { 400 | if(header) 401 | header->inc_count(); 402 | } 403 | 404 | template shared_ptr(const shared_ptr& r) noexcept: 405 | ptr(r.ptr),header(r.header) 406 | { 407 | if(header) 408 | header->inc_count(); 409 | } 410 | 411 | shared_ptr(shared_ptr&& r) noexcept: 412 | ptr(r.ptr),header(r.header) 413 | { 414 | r.clear(); 415 | } 416 | 417 | template shared_ptr(shared_ptr&& r) noexcept: 418 | ptr(r.ptr),header(r.header) 419 | { 420 | r.clear(); 421 | } 422 | 423 | template explicit shared_ptr(const weak_ptr& r); 424 | 425 | template shared_ptr(std::unique_ptr&& r): 426 | ptr(r.get()), 427 | header( 428 | r.get()? 429 | new shared_ptr_header_separate(r.get(),r.get_deleter()):nullptr) 430 | { 431 | r.release(); 432 | } 433 | constexpr shared_ptr(std::nullptr_t) : shared_ptr() { } 434 | // 20.8.2.2.2, destructor: 435 | ~shared_ptr() 436 | { 437 | if(header){ 438 | header->dec_count(); 439 | } 440 | } 441 | 442 | // 20.8.2.2.3, assignment: 443 | shared_ptr& operator=(const shared_ptr& r) noexcept 444 | { 445 | if(&r!=this){ 446 | shared_ptr temp(r); 447 | swap(temp); 448 | } 449 | return *this; 450 | } 451 | template shared_ptr& operator=(const shared_ptr& r) noexcept 452 | { 453 | shared_ptr temp(r); 454 | swap(temp); 455 | return *this; 456 | } 457 | 458 | shared_ptr& operator=(shared_ptr&& r) noexcept 459 | { 460 | swap(r); 461 | r.reset(); 462 | return *this; 463 | } 464 | 465 | template shared_ptr& operator=(shared_ptr&& r) noexcept 466 | { 467 | shared_ptr temp(static_cast&&>(r)); 468 | swap(temp); 469 | return *this; 470 | } 471 | 472 | template shared_ptr& operator=(std::unique_ptr&& r) 473 | { 474 | shared_ptr temp(static_cast&&>(r)); 475 | swap(temp); 476 | return *this; 477 | } 478 | // 20.8.2.2.4, modifiers: 479 | void swap(shared_ptr& r) noexcept 480 | { 481 | std::swap(ptr,r.ptr); 482 | std::swap(header,r.header); 483 | } 484 | void reset() noexcept 485 | { 486 | if(header){ 487 | header->dec_count(); 488 | } 489 | clear(); 490 | } 491 | 492 | template void reset(Y* p) 493 | { 494 | shared_ptr temp(p); 495 | swap(temp); 496 | } 497 | 498 | template void reset(Y* p, D d) 499 | { 500 | shared_ptr temp(p,d); 501 | swap(temp); 502 | } 503 | 504 | template void reset(Y* p, D d, A a); 505 | // 20.8.2.2.5, observers: 506 | T* get() const noexcept 507 | { 508 | return ptr; 509 | } 510 | 511 | T& operator*() const noexcept 512 | { 513 | return *ptr; 514 | } 515 | 516 | T* operator->() const noexcept 517 | { 518 | return ptr; 519 | } 520 | 521 | long use_count() const noexcept 522 | { 523 | return header?header->use_count():0; 524 | } 525 | 526 | bool unique() const noexcept 527 | { 528 | return use_count()==1; 529 | } 530 | 531 | explicit operator bool() const noexcept 532 | { 533 | return ptr; 534 | } 535 | template bool owner_before(shared_ptr const& b) const; 536 | template bool owner_before(weak_ptr const& b) const; 537 | 538 | friend inline bool operator==(shared_ptr const& lhs,shared_ptr const& rhs) 539 | { 540 | return lhs.ptr==rhs.ptr; 541 | } 542 | 543 | friend inline bool operator!=(shared_ptr const& lhs,shared_ptr const& rhs) 544 | { 545 | return !(lhs==rhs); 546 | } 547 | 548 | }; 549 | 550 | template 551 | shared_ptr make_shared(Args&& ... args){ 552 | return shared_ptr( 553 | new shared_ptr_header_combined( 554 | static_cast(args)...)); 555 | } 556 | 557 | template class weak_ptr { 558 | T* ptr; 559 | shared_ptr_header_block_base* header; 560 | 561 | void clear() 562 | { 563 | header=nullptr; 564 | ptr=nullptr; 565 | } 566 | 567 | public: 568 | typedef T element_type; 569 | // 20.8.2.3.1, constructors 570 | constexpr weak_ptr() noexcept: 571 | header(nullptr) 572 | {} 573 | 574 | template weak_ptr(shared_ptr const& r) noexcept: 575 | ptr(r.ptr),header(r.header) 576 | { 577 | if(header) 578 | header->inc_weak_count(); 579 | } 580 | 581 | weak_ptr(weak_ptr const& r) noexcept: 582 | ptr(r.ptr),header(r.header) 583 | { 584 | if(header) 585 | header->inc_weak_count(); 586 | } 587 | template weak_ptr(weak_ptr const& r) noexcept: 588 | ptr(r.ptr),header(r.header) 589 | { 590 | if(header) 591 | header->inc_weak_count(); 592 | } 593 | weak_ptr(weak_ptr&& r) noexcept: 594 | ptr(r.ptr),header(r.header) 595 | { 596 | r.clear(); 597 | } 598 | template weak_ptr(weak_ptr&& r) noexcept: 599 | ptr(r.ptr),header(r.header) 600 | { 601 | r.clear(); 602 | } 603 | // 20.8.2.3.2, destructor 604 | ~weak_ptr() 605 | { 606 | if(header) 607 | header->dec_weak_count(); 608 | } 609 | 610 | // 20.8.2.3.3, assignment 611 | weak_ptr& operator=(weak_ptr const& r) noexcept 612 | { 613 | if(&r!=this){ 614 | weak_ptr temp(r); 615 | swap(temp); 616 | } 617 | return *this; 618 | } 619 | 620 | template weak_ptr& operator=(weak_ptr const& r) noexcept 621 | { 622 | weak_ptr temp(r); 623 | swap(temp); 624 | return *this; 625 | } 626 | 627 | template weak_ptr& operator=(shared_ptr const& r) noexcept 628 | { 629 | weak_ptr temp(r); 630 | swap(temp); 631 | return *this; 632 | } 633 | 634 | weak_ptr& operator=(weak_ptr&& r) noexcept 635 | { 636 | swap(r); 637 | r.reset(); 638 | return *this; 639 | } 640 | 641 | template weak_ptr& operator=(weak_ptr&& r) noexcept 642 | { 643 | weak_ptr temp(static_cast&&>(r)); 644 | swap(temp); 645 | return *this; 646 | } 647 | 648 | // 20.8.2.3.4, modifiers 649 | void swap(weak_ptr& r) noexcept 650 | { 651 | std::swap(r.header,header); 652 | std::swap(r.ptr,ptr); 653 | } 654 | 655 | void reset() noexcept 656 | { 657 | if(header) 658 | header->dec_weak_count(); 659 | clear(); 660 | } 661 | 662 | // 20.8.2.3.5, observers 663 | long use_count() const noexcept 664 | { 665 | return header?header->use_count():0; 666 | } 667 | 668 | bool expired() const noexcept 669 | { 670 | return !use_count(); 671 | } 672 | 673 | shared_ptr lock() const noexcept 674 | { 675 | return shared_ptr(header,ptr); 676 | } 677 | 678 | template bool owner_before(shared_ptr const& b) const; 679 | template bool owner_before(weak_ptr const& b) const; 680 | }; 681 | 682 | #ifdef _MSC_VER 683 | #define JSS_ASP_ALIGN_TO(alignment) __declspec(align(alignment)) 684 | #ifdef _WIN64 685 | #define JSS_ASP_BITFIELD_SIZE 32 686 | #else 687 | #define JSS_ASP_BITFIELD_SIZE 16 688 | #endif 689 | #else 690 | #define JSS_ASP_ALIGN_TO(alignment) __attribute__((aligned(alignment))) 691 | #ifdef __LP64__ 692 | #define JSS_ASP_BITFIELD_SIZE 32 693 | #else 694 | #define JSS_ASP_BITFIELD_SIZE 16 695 | #endif 696 | #endif 697 | 698 | template 699 | class atomic_shared_ptr 700 | { 701 | template 702 | friend class atomic_shared_ptr; 703 | 704 | struct counted_ptr{ 705 | unsigned access_count:JSS_ASP_BITFIELD_SIZE; 706 | unsigned index:JSS_ASP_BITFIELD_SIZE; 707 | shared_ptr_header_block_base* ptr; 708 | 709 | counted_ptr() noexcept: 710 | access_count(0),index(0),ptr(nullptr) 711 | {} 712 | 713 | counted_ptr(shared_ptr_header_block_base* ptr_,unsigned index_): 714 | access_count(0),index(index_),ptr(ptr_) 715 | {} 716 | }; 717 | 718 | mutable JSS_ASP_ALIGN_TO(sizeof(counted_ptr)) std::atomic p; 719 | 720 | struct local_access{ 721 | std::atomic& p; 722 | counted_ptr val; 723 | 724 | void acquire(std::memory_order order){ 725 | if(!val.ptr) 726 | return; 727 | for(;;){ 728 | counted_ptr newval=val; 729 | ++newval.access_count; 730 | if(p.compare_exchange_weak(val,newval,order)) 731 | break; 732 | } 733 | ++val.access_count; 734 | } 735 | 736 | local_access( 737 | std::atomic& p_, 738 | std::memory_order order=std::memory_order_relaxed): 739 | p(p_),val(p.load(order)) 740 | { 741 | acquire(order); 742 | } 743 | 744 | ~local_access() 745 | { 746 | release(); 747 | } 748 | 749 | void release(){ 750 | if(!val.ptr) 751 | return; 752 | counted_ptr target=val; 753 | do{ 754 | counted_ptr newval=target; 755 | --newval.access_count; 756 | if(p.compare_exchange_weak(target,newval)) 757 | break; 758 | }while(target.ptr==val.ptr); 759 | if(target.ptr!=val.ptr){ 760 | val.ptr->remove_external_counter(); 761 | } 762 | } 763 | 764 | void refresh(counted_ptr newval,std::memory_order order){ 765 | if(newval.ptr==val.ptr) 766 | return; 767 | release(); 768 | val=newval; 769 | acquire(order); 770 | } 771 | 772 | shared_ptr_header_block_base* get_ptr() 773 | { 774 | return val.ptr; 775 | } 776 | 777 | shared_ptr get_shared_ptr() 778 | { 779 | return shared_ptr(val.ptr,val.index); 780 | } 781 | 782 | }; 783 | 784 | 785 | public: 786 | 787 | bool is_lock_free() const noexcept 788 | { 789 | return p.is_lock_free(); 790 | } 791 | 792 | void store( 793 | shared_ptr newptr, 794 | std::memory_order order= std::memory_order_seq_cst) /*noexcept*/ 795 | { 796 | unsigned index=0; 797 | if(newptr.header){ 798 | index=newptr.header->get_ptr_index(newptr.ptr); 799 | } 800 | counted_ptr old=p.exchange(counted_ptr(newptr.header,index),order); 801 | if(old.ptr){ 802 | old.ptr->add_external_counters(old.access_count); 803 | old.ptr->dec_count(); 804 | } 805 | newptr.clear(); 806 | } 807 | 808 | shared_ptr load( 809 | std::memory_order order= std::memory_order_seq_cst) const noexcept 810 | { 811 | local_access guard(p,order); 812 | return guard.get_shared_ptr(); 813 | } 814 | 815 | operator shared_ptr() const noexcept { 816 | return load(); 817 | } 818 | 819 | shared_ptr exchange( 820 | shared_ptr newptr, 821 | std::memory_order order= std::memory_order_seq_cst) /*noexcept*/ 822 | { 823 | counted_ptr newval( 824 | newptr.header, 825 | newptr.header?newptr.header->get_ptr_index(newptr.ptr):0); 826 | counted_ptr old=p.exchange(newval,order); 827 | shared_ptr res(old.ptr,old.index); 828 | if(old.ptr){ 829 | old.ptr->add_external_counters(old.access_count); 830 | old.ptr->dec_count(); 831 | } 832 | newptr.clear(); 833 | return res; 834 | } 835 | 836 | bool compare_exchange_weak( 837 | shared_ptr & expected, shared_ptr newptr, 838 | std::memory_order success_order=std::memory_order_seq_cst, 839 | std::memory_order failure_order=std::memory_order_seq_cst) /*noexcept*/ 840 | { 841 | local_access guard(p); 842 | if(guard.get_ptr()!=expected.header){ 843 | expected=guard.get_shared_ptr(); 844 | return false; 845 | } 846 | counted_ptr oldval(guard.val); 847 | counted_ptr newval( 848 | newptr.header, 849 | newptr.header?newptr.header->get_ptr_index(newptr.ptr):0); 850 | if(p.compare_exchange_weak(oldval,newval,success_order,failure_order)){ 851 | if(oldval.ptr){ 852 | oldval.ptr->add_external_counters(oldval.access_count); 853 | oldval.ptr->dec_count(); 854 | } 855 | newptr.clear(); 856 | return true; 857 | } 858 | else{ 859 | guard.refresh(oldval,failure_order); 860 | expected=guard.get_shared_ptr(); 861 | return false; 862 | } 863 | } 864 | 865 | bool compare_exchange_strong( 866 | shared_ptr &expected,shared_ptr newptr, 867 | std::memory_order success_order=std::memory_order_seq_cst, 868 | std::memory_order failure_order=std::memory_order_seq_cst) noexcept 869 | { 870 | shared_ptr local_expected=expected; 871 | do{ 872 | if(compare_exchange_weak(expected,newptr,success_order,failure_order)) 873 | return true; 874 | } 875 | while(expected==local_expected); 876 | return false; 877 | } 878 | 879 | atomic_shared_ptr() noexcept = default; 880 | atomic_shared_ptr( shared_ptr val) /*noexcept*/: 881 | p(counted_ptr(val.header,val.header?val.header->get_ptr_index(val.ptr):0)) 882 | { 883 | val.header=nullptr; 884 | val.ptr=nullptr; 885 | } 886 | 887 | ~atomic_shared_ptr() 888 | { 889 | counted_ptr old=p.load(std::memory_order_relaxed); 890 | if(old.ptr) 891 | old.ptr->dec_count(); 892 | } 893 | 894 | atomic_shared_ptr(const atomic_shared_ptr&) = delete; 895 | atomic_shared_ptr& operator=(const atomic_shared_ptr&) = delete; 896 | shared_ptr operator=(shared_ptr newval) noexcept 897 | { 898 | store(static_cast&&>(newval)); 899 | return newval; 900 | } 901 | 902 | }; 903 | 904 | } 905 | 906 | #endif 907 | -------------------------------------------------------------------------------- /jss/atomic_shared_ptr_traits.hpp: -------------------------------------------------------------------------------- 1 | // atomic_shared_ptr_traits.hpp 2 | // 3 | #pragma once 4 | #include "atomic_shared_ptr" 5 | 6 | namespace jss { 7 | 8 | template< template class AtomicSharedPtr > 9 | struct atomic_shared_ptr_traits 10 | { 11 | template< typename T > 12 | using atomic_shared_ptr = AtomicSharedPtr; 13 | 14 | template< typename T > 15 | using shared_ptr = jss::shared_ptr; 16 | 17 | template< typename T, typename... Args > 18 | static auto make_shared(Args &&...args) 19 | { return jss::make_shared(std::forward(args)...); } 20 | }; 21 | 22 | } // namespace jss 23 | 24 | -------------------------------------------------------------------------------- /loop/.gitignore: -------------------------------------------------------------------------------- 1 | loop.s 2 | -------------------------------------------------------------------------------- /loop/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | $(CXX) -std=c++14 -O3 -S loop.cpp 3 | -------------------------------------------------------------------------------- /loop/loop.cpp: -------------------------------------------------------------------------------- 1 | void foo() { 2 | static const constexpr int N = 100; 3 | for (int i = 0; i < N; ++i) { 4 | __asm__(""); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /measurements/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TBB_ROOT ${PROJECT_SOURCE_DIR}/tbb) 2 | include(${TBB_ROOT}/cmake/TBBBuild.cmake) 3 | tbb_build(TBB_ROOT ${TBB_ROOT} CONFIG_DIR TBB_DIR) 4 | find_package(TBB REQUIRED tbb) 5 | message("tbb imported libs: " ${TBB_IMPORTED_TARGETS}) 6 | include_directories(${TBB_ROOT}/include) 7 | 8 | add_executable (measure_rcuptr measure.cpp) 9 | target_link_libraries (measure_rcuptr pthread ${ATOMICLIB}) 10 | 11 | add_executable (measure_rcuptr_jss measure.cpp) 12 | target_link_libraries (measure_rcuptr_jss pthread ${ATOMICLIB}) 13 | target_compile_options(measure_rcuptr_jss PRIVATE -DTEST_WITH_JSS_ASP) 14 | 15 | add_executable (measure_std_mutex measure.cpp) 16 | target_link_libraries (measure_std_mutex pthread ${ATOMICLIB}) 17 | target_compile_options(measure_std_mutex PRIVATE -DX_STD_MUTEX) 18 | 19 | add_executable (measure_tbb_qrw_mutex measure.cpp) 20 | target_link_libraries (measure_tbb_qrw_mutex pthread ${ATOMICLIB} ${TBB_IMPORTED_TARGETS}) 21 | target_compile_options(measure_tbb_qrw_mutex PRIVATE -DX_TBB_QRW_MUTEX) 22 | 23 | add_executable (measure_tbb_srw_mutex measure.cpp) 24 | target_link_libraries (measure_tbb_srw_mutex pthread ${ATOMICLIB} ${TBB_IMPORTED_TARGETS}) 25 | target_compile_options(measure_tbb_srw_mutex PRIVATE -DX_TBB_SRW_MUTEX) 26 | 27 | add_executable (measure_urcu measure.cpp) 28 | target_link_libraries (measure_urcu urcu pthread) 29 | target_compile_options(measure_urcu PRIVATE -DX_URCU) 30 | 31 | add_executable (measure_urcu_mb measure.cpp) 32 | target_link_libraries (measure_urcu_mb urcu-mb pthread) 33 | target_compile_options(measure_urcu_mb PRIVATE -DRCU_MB -DX_URCU) 34 | 35 | add_executable (measure_urcu_bp measure.cpp) 36 | target_link_libraries (measure_urcu_bp urcu-bp pthread) 37 | target_compile_options(measure_urcu_bp PRIVATE -DX_URCU -DX_URCU_BP) 38 | -------------------------------------------------------------------------------- /measurements/display.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import argparse 4 | import os 5 | import re 6 | import locale 7 | from decimal import Decimal 8 | 9 | from matplotlib import rc 10 | import matplotlib 11 | import matplotlib.pyplot as plt 12 | 13 | dot_line_formats = { 14 | 'std_mutex': ('bs', '-b'), 15 | 'tbb_srw_mutex': ('rd', '-r'), 16 | 'tbb_qrw_mutex': ('ro', '-r'), 17 | 'rcuptr': ('gv', '-g'), 18 | 'rcuptr_jss': ('g^', '-g'), 19 | 'urcu': ('c*', '-c'), 20 | 'urcu_mb': ('c+', '-c'), 21 | 'urcu_bp': ('cx', '-c'), 22 | } 23 | 24 | 25 | # Represents one measurement configuration. 26 | class MeasureKey: 27 | 28 | def __init__( 29 | self, 30 | test_bin, 31 | vec_size, 32 | num_all_readers, 33 | num_readers, 34 | num_writers): 35 | self.test_bin = test_bin 36 | self.num_all_readers = num_all_readers 37 | self.vec_size = vec_size 38 | self.num_readers = num_readers 39 | self.num_writers = num_writers 40 | 41 | def __str__(self): 42 | return (self.test_bin + 43 | " " + 44 | str(self.vec_size) + 45 | " " + 46 | str(self.num_all_readers) + 47 | " " + 48 | str(self.num_readers) + 49 | " " + 50 | str(self.num_writers)) 51 | 52 | def __hash__(self): 53 | return self.__str__().__hash__() 54 | 55 | def __eq__(self, other): 56 | return self.__str__().__eq__(other.__str__()) 57 | 58 | 59 | # We have multiple measurement values for the same configuration (i.e. 60 | # MeasureKey). 61 | class MeasureIterations: 62 | 63 | def __init__(self): 64 | self.reader_sum = [] 65 | self.writer_sum = [] 66 | 67 | def __str__(self): 68 | return str(self.reader_sum) 69 | 70 | 71 | class ChartLine: 72 | 73 | def __init__(self): 74 | self.values = dict() 75 | self.x = [] 76 | self.y = [] 77 | 78 | def __str__(self): 79 | return "x: " + str(self.x) + "\n" + "y: " + str(self.y) 80 | 81 | 82 | # param l: list 83 | def getAverage(l): 84 | l.remove(max(l)) 85 | l.remove(min(l)) 86 | return sum(l) / len(l) 87 | 88 | 89 | # Get a more verbose better reading label to a value 90 | def getYlabel(value): 91 | m = {'reader_sum': u"Number of Read Operations * $10^6$ / second", 92 | 'writer_sum': 'Number of Write Operations * $10^5$ / second'} 93 | return m[value] 94 | 95 | 96 | def display( 97 | measures, 98 | vec_size, 99 | num_writers, 100 | num_all_readers, 101 | args): 102 | chartData = dict() 103 | value = args.value 104 | for measureKey, measureIterations in measures.iteritems(): 105 | print(measureKey) 106 | print(measureIterations) 107 | print("=======================") 108 | if args.skip_urcu and 'urcu' in measureKey.test_bin: 109 | continue 110 | if args.skip_mtx and 'mutex' in measureKey.test_bin: 111 | continue 112 | if (measureKey.vec_size == vec_size and measureKey.num_writers == 113 | num_writers and measureKey.num_all_readers == num_all_readers): 114 | if measureKey.test_bin not in chartData: 115 | chartData[measureKey.test_bin] = ChartLine() 116 | chartData[ 117 | measureKey.test_bin].values[ 118 | int(measureKey.num_readers)] = getAverage( 119 | getattr(measureIterations, value)) 120 | 121 | for key, chartline in chartData.iteritems(): 122 | lists = sorted(chartline.values.items()) 123 | chartline.x, chartline.y = zip(*lists) 124 | 125 | title = " vec_size: " + str(vec_size) + " num_writers: " + str( 126 | num_writers) + " num_all_readers: " + str(num_all_readers) 127 | title = title.replace('_', ' ') 128 | #plt.title(title) 129 | #plt.ylabel(value.replace('_', ' ')) 130 | # Y axis label is cut off, so we must adjust it 131 | plt.gcf().subplots_adjust(left=0.15) 132 | plt.ylabel(getYlabel(value)) 133 | plt.xlabel("Number of Reader Threads") 134 | for key, chartline in chartData.iteritems(): 135 | plot(chartline.x, chartline.y, key[len("measure_"):], args) 136 | 137 | if args.save: 138 | filename = "_".join( 139 | ["res", str(value), str(vec_size), 140 | str(num_all_readers), 141 | str(num_writers)]) 142 | if args.latex: 143 | plt.savefig(filename + ".eps", format='eps', dpi=1000) 144 | else: 145 | plt.savefig(filename + ".png") 146 | plt.clf() 147 | else: 148 | plt.show() 149 | 150 | 151 | def plot(xs, ys, name, args): 152 | ax = plt.subplot(111) 153 | 154 | fig_loc = 'lower right' 155 | if args.fig_loc is not None: 156 | fig_loc = args.fig_loc 157 | 158 | # logarithmic scale 159 | if args.log: 160 | ax.semilogy(xs, ys, dot_line_formats[name][0], 161 | label=name.replace('_', ' ')) 162 | ax.semilogy(xs, ys, dot_line_formats[name][1]) 163 | ax.legend(loc=fig_loc, fontsize='small', shadow=True, ncol=2) 164 | 165 | # linear scale 166 | else: 167 | ax.plot(xs, ys, dot_line_formats[name][0], label=name.replace('_', ' ')) 168 | ax.plot(xs, ys, dot_line_formats[name][1]) 169 | ax.legend(loc=fig_loc, shadow=True, fontsize='small') 170 | 171 | ax.get_yaxis().set_major_formatter( 172 | #matplotlib.ticker.FuncFormatter(lambda x, p: '%.1e' % Decimal(x))) 173 | matplotlib.ticker.FuncFormatter(lambda x, p: x / 100000)) 174 | 175 | 176 | def main(): 177 | parser = argparse.ArgumentParser() 178 | parser.add_argument('--result_dir', help='path of result dir', 179 | required=True) 180 | parser.add_argument('--value', required=True, choices=['reader_sum', 'writer_sum']) 181 | parser.add_argument('--skip_urcu', action='store_true') 182 | parser.add_argument('--skip_mtx', action='store_true') 183 | parser.add_argument('--latex', action='store_true') 184 | parser.add_argument('--save', action='store_true') 185 | parser.add_argument('--log', action='store_true') 186 | parser.add_argument('--fig_loc', default=None) 187 | args = parser.parse_args() 188 | 189 | if args.latex: 190 | rc('text', usetex=True) 191 | rc('font', **{'family': 'serif', 'serif': ['Computer Modern Roman']}) 192 | matplotlib.rcParams.update({'font.size': 15}) 193 | 194 | patterns = [ 195 | (re.compile("reader sum: ([\d|\.]+)"), 'reader_sum'), 196 | (re.compile("writer sum: ([\d|\.]+)"), 'writer_sum'), 197 | ] 198 | 199 | # Aggregate the values in each files into a dict, where the key is 200 | # MeasureKey, and the value is MeasureIterations. 201 | # We append the found values in each measureIteration to the specific list. 202 | measures = dict() 203 | for file in os.listdir(args.result_dir): 204 | basename = os.path.splitext(file)[0] 205 | elements = basename.split('__') 206 | key = MeasureKey(elements[0], elements[1], elements[2], 207 | elements[3], elements[4]) 208 | # skip measures when number of readers are below a certain limit 209 | #if int(key.num_readers) < 5: 210 | #continue 211 | if key not in measures: 212 | measures[key] = MeasureIterations() 213 | measureIt = measures[key] 214 | for _, line in enumerate(open(os.path.join(args.result_dir, file))): 215 | for pattern, attr in patterns: 216 | for match in re.finditer(pattern, line): 217 | value = match.groups()[0] 218 | locale.setlocale(locale.LC_NUMERIC, '') 219 | values = getattr(measureIt, attr) 220 | values.append(int(locale.atof(value))) 221 | setattr(measureIt, attr, values) 222 | 223 | # slow readers too 224 | """ 225 | display(measures, '8196', '1', '1', args) 226 | display(measures, '131072', '1', '1', args) 227 | display(measures, '1048576', '1', '1', args) 228 | """ 229 | 230 | # no slow readers 231 | display(measures, '8196', '1', '0', args) 232 | #display(measures, '131072', '1', '0', args) 233 | #display(measures, '1048576', '1', '0', args) 234 | 235 | 236 | if __name__ == "__main__": 237 | main() 238 | -------------------------------------------------------------------------------- /measurements/measure.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #ifdef X_URCU 16 | #ifdef X_URCU_BP 17 | #include 18 | #else 19 | #include 20 | #endif 21 | #else 22 | //urcu-bp.h renders rcu_init, rcu_register_thread, rcu_unregister_thread to 23 | //noop. 24 | #include 25 | #endif 26 | 27 | class XRcuPtr { 28 | rcu_ptr_under_test> v; 29 | const int default_value = 1; 30 | 31 | public: 32 | XRcuPtr(size_t vec_size) 33 | : v(asp_traits::make_shared>(vec_size, 34 | default_value)) {} 35 | 36 | int read_one(unsigned index) const { 37 | asp_traits::shared_ptr> local_copy = v.read(); 38 | assert(index < local_copy->size()); 39 | return (*local_copy)[index]; 40 | } 41 | int read_all() const { // sum 42 | asp_traits::shared_ptr> local_copy = v.read(); 43 | return std::accumulate(local_copy->begin(), local_copy->end(), 0); 44 | } 45 | 46 | void update_all(int value) { 47 | v.copy_update([=](std::vector* copy) { 48 | for (auto& e : *copy) { 49 | e = value; 50 | } 51 | }); 52 | } 53 | }; 54 | 55 | class XStdMutex { 56 | std::vector v; 57 | const int default_value = 1; 58 | mutable std::mutex m; 59 | 60 | public: 61 | XStdMutex(size_t vec_size) : v(vec_size, default_value) {} 62 | 63 | int read_one(unsigned index) const { 64 | std::lock_guard lock{m}; 65 | assert(index < v.size()); 66 | return v[index]; 67 | } 68 | int read_all() const { // sum 69 | std::lock_guard lock{m}; 70 | return std::accumulate(v.begin(), v.end(), 0); 71 | } 72 | 73 | void update_all(int value) { 74 | std::lock_guard lock{m}; 75 | for (auto& e : v) { 76 | e = value; 77 | } 78 | } 79 | }; 80 | 81 | class XTbbQueuingRwMutex { 82 | std::vector v; 83 | const int default_value = 1; 84 | mutable tbb::queuing_rw_mutex m; 85 | 86 | public: 87 | XTbbQueuingRwMutex(size_t vec_size) : v(vec_size, default_value) {} 88 | 89 | int read_one(unsigned index) const { 90 | tbb::queuing_rw_mutex::scoped_lock lock{m, false}; // read lock 91 | assert(index < v.size()); 92 | return v[index]; 93 | } 94 | int read_all() const { // sum 95 | tbb::queuing_rw_mutex::scoped_lock lock{m, false}; // read lock 96 | return std::accumulate(v.begin(), v.end(), 0); 97 | } 98 | 99 | void update_all(int value) { 100 | tbb::queuing_rw_mutex::scoped_lock lock{m}; // write lock 101 | for (auto& e : v) { 102 | e = value; 103 | } 104 | } 105 | }; 106 | 107 | class XTbbSpinRwMutex { 108 | std::vector v; 109 | const int default_value = 1; 110 | mutable tbb::spin_rw_mutex m; 111 | 112 | public: 113 | XTbbSpinRwMutex(size_t vec_size) : v(vec_size, default_value) {} 114 | 115 | int read_one(unsigned index) const { 116 | tbb::spin_rw_mutex::scoped_lock lock{m, false}; // read lock 117 | assert(index < v.size()); 118 | return v[index]; 119 | } 120 | int read_all() const { // sum 121 | tbb::spin_rw_mutex::scoped_lock lock{m, false}; // read lock 122 | return std::accumulate(v.begin(), v.end(), 0); 123 | } 124 | 125 | void update_all(int value) { 126 | tbb::spin_rw_mutex::scoped_lock lock{m}; // write lock 127 | for (auto& e : v) { 128 | e = value; 129 | } 130 | } 131 | }; 132 | 133 | class XURCU { 134 | std::vector* v; 135 | const int default_value = 1; 136 | mutable std::mutex m; // to support concurrent writers 137 | 138 | public: 139 | XURCU(size_t vec_size) : v(new std::vector(vec_size, default_value)) {} 140 | 141 | int read_one(unsigned index) const { 142 | rcu_read_lock(); 143 | const std::vector* local_copy = rcu_dereference(v); 144 | assert(index < local_copy->size()); 145 | int result = (*local_copy)[index]; 146 | rcu_read_unlock(); 147 | return result; 148 | } 149 | int read_all() const { // sum 150 | rcu_read_lock(); 151 | const std::vector* local_copy = rcu_dereference(v); 152 | int result = std::accumulate(local_copy->begin(), local_copy->end(), 0); 153 | rcu_read_unlock(); 154 | return result; 155 | } 156 | 157 | void update_all(int value) { 158 | std::lock_guard lock{m}; // support concurrent writers 159 | 160 | rcu_read_lock(); 161 | std::vector* local_copy = rcu_dereference(v); 162 | std::vector* local_deep_copy = new std::vector(*local_copy); 163 | rcu_read_unlock(); 164 | 165 | assert(index < copy->size()); 166 | for (auto& e : *local_deep_copy) { 167 | e = ++value; 168 | } 169 | synchronize_rcu(); 170 | delete local_copy; 171 | rcu_assign_pointer(v, local_deep_copy); 172 | } 173 | 174 | ~XURCU() { 175 | synchronize_rcu(); 176 | delete v; 177 | } 178 | }; 179 | 180 | class RoundRobin { 181 | long long i = 0; 182 | unsigned size; 183 | 184 | public: 185 | RoundRobin(unsigned size) : size(size) {} 186 | unsigned next() { return i++ % size; } 187 | }; 188 | 189 | template 190 | struct Driver { 191 | X x; 192 | std::atomic stop = ATOMIC_FLAG_INIT; 193 | unsigned vec_size; 194 | std::mutex finish_mtx; 195 | std::vector reader_cycles; 196 | std::vector writer_cycles; 197 | 198 | Driver(unsigned vec_size) 199 | : x(vec_size), vec_size(vec_size) {} 200 | 201 | void timer_fun() { 202 | using namespace std::chrono_literals; 203 | auto start = std::chrono::high_resolution_clock::now(); 204 | std::this_thread::sleep_for(1s); 205 | auto end = std::chrono::high_resolution_clock::now(); 206 | std::chrono::duration elapsed = end - start; 207 | stop.store(true, std::memory_order_relaxed); 208 | std::cout << "Waited " << elapsed.count() << " ms\n"; 209 | } 210 | 211 | void reader_fun() { 212 | long long cycles = 0; 213 | while (!stop.load(std::memory_order_relaxed)) { 214 | x.read_all(); 215 | ++cycles; 216 | } 217 | { 218 | std::lock_guard lock(finish_mtx); 219 | reader_cycles.push_back(cycles); 220 | } 221 | } 222 | 223 | void one_reader_fun() { 224 | long long cycles = 0; 225 | RoundRobin rr{vec_size}; 226 | while (!stop.load(std::memory_order_relaxed)) { 227 | x.read_one(rr.next()); 228 | ++cycles; 229 | } 230 | { 231 | std::lock_guard lock(finish_mtx); 232 | reader_cycles.push_back(cycles); 233 | } 234 | } 235 | 236 | void writer_fun() { 237 | long long cycles = 0; 238 | while (!stop.load(std::memory_order_relaxed)) { 239 | x.update_all(0); 240 | ++cycles; 241 | } 242 | { 243 | std::lock_guard lock(finish_mtx); 244 | writer_cycles.push_back(cycles); 245 | } 246 | } 247 | 248 | void print_stats() { 249 | long long reader_sum = 0, writer_sum = 0; 250 | for (auto cycles : reader_cycles) { 251 | reader_sum += cycles; 252 | std::cout << "reader thread: " << cycles << "\n"; 253 | } 254 | for (auto cycles : writer_cycles) { 255 | writer_sum += cycles; 256 | std::cout << "writer thread: " << cycles << "\n"; 257 | } 258 | std::cout << "reader sum: " << reader_sum << "\n"; 259 | std::cout << "writer sum: " << writer_sum << "\n"; 260 | std::cout << "reader av: " << (reader_cycles.size() > 0 261 | ? reader_sum / reader_cycles.size() 262 | : 0) 263 | << "\n"; 264 | std::cout << "writer av: " << (writer_cycles.size() > 0 265 | ? writer_sum / writer_cycles.size() 266 | : 0) 267 | << "\n"; 268 | } 269 | }; 270 | 271 | int main(int argc, char** argv) { 272 | if (argc != 5) { 273 | std::cerr << "Wrong program args!\n"; 274 | exit(-1); 275 | } 276 | unsigned vec_size = atoi(argv[1]); 277 | assert(vec_size >= 1); 278 | unsigned num_all_readers = atoi(argv[2]); 279 | unsigned num_one_readers = atoi(argv[3]); 280 | unsigned num_writers = atoi(argv[4]); 281 | 282 | #ifdef X_STD_MUTEX 283 | Driver driver{vec_size}; 284 | #elif defined X_TBB_QRW_MUTEX 285 | Driver driver{vec_size}; 286 | #elif defined X_TBB_SRW_MUTEX 287 | Driver driver{vec_size}; 288 | #elif defined X_URCU 289 | Driver driver{vec_size}; 290 | #else 291 | Driver driver{vec_size}; 292 | #endif 293 | 294 | std::thread timer_thread([&driver]() { driver.timer_fun(); }); 295 | std::vector reader_threads; 296 | std::vector writer_threads; 297 | 298 | rcu_init(); 299 | for (unsigned i = 0; i < num_all_readers; ++i) { 300 | reader_threads.push_back(std::thread([&driver]() { 301 | rcu_register_thread(); 302 | driver.reader_fun(); 303 | rcu_unregister_thread(); 304 | })); 305 | } 306 | for (unsigned i = 0; i < num_one_readers; ++i) { 307 | reader_threads.push_back(std::thread([&driver]() { 308 | rcu_register_thread(); 309 | driver.one_reader_fun(); 310 | rcu_unregister_thread(); 311 | })); 312 | } 313 | for (unsigned i = 0; i < num_writers; ++i) { 314 | writer_threads.push_back(std::thread([&driver]() { 315 | rcu_register_thread(); 316 | driver.writer_fun(); 317 | rcu_unregister_thread(); 318 | })); 319 | } 320 | 321 | timer_thread.join(); 322 | for (auto& t : reader_threads) { 323 | t.join(); 324 | } 325 | for (auto& t : writer_threads) { 326 | t.join(); 327 | } 328 | driver.print_stats(); 329 | 330 | return 0; 331 | } 332 | -------------------------------------------------------------------------------- /measurements/measure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import subprocess 3 | import argparse 4 | import shutil 5 | import os 6 | import multiprocessing 7 | 8 | 9 | def call_command(cmd, cwd=None, env=None): 10 | """ 11 | Execute a process in a test case. If the run is successful do not bloat 12 | the test output, but in case of any failure dump stdout and stderr. 13 | Returns the utf decoded (stdout, stderr) pair of strings. 14 | """ 15 | def show(out, err): 16 | print("\nTEST execute stdout:\n") 17 | print(out.decode("utf-8")) 18 | print("\nTEST execute stderr:\n") 19 | print(err.decode("utf-8")) 20 | try: 21 | proc = subprocess.Popen(cmd, 22 | stdout=subprocess.PIPE, 23 | stderr=subprocess.PIPE, 24 | cwd=cwd, env=env) 25 | out, err = proc.communicate() 26 | if proc.returncode != 0: 27 | show(out, err) 28 | print('Unsuccessful run: "' + ' '.join(cmd) + '"') 29 | raise Exception("Unsuccessful run of command.") 30 | return out.decode("utf-8"), err.decode("utf-8") 31 | except OSError: 32 | show(out, err) 33 | print('Failed to run: "' + ' '.join(cmd) + '"') 34 | raise 35 | 36 | 37 | def one_measure( 38 | args, 39 | test_bin, 40 | num_all_readers, 41 | vec_size, 42 | num_writers, 43 | num_readers, 44 | iteration): 45 | binary = os.path.join(args.bin_dir, test_bin) 46 | file_name = '__'.join([test_bin, vec_size, num_all_readers, num_readers, 47 | num_writers]) 48 | file_name = file_name + "." + str(iteration) 49 | print(file_name) 50 | out, err = call_command( 51 | ['perf', 'stat', '-d', binary, vec_size, num_all_readers, num_readers, 52 | num_writers]) 53 | with open(os.path.join(args.result_dir, file_name), 'w') as f: 54 | f.write(out) 55 | f.write(err) 56 | 57 | 58 | def main(): 59 | parser = argparse.ArgumentParser() 60 | parser.add_argument('--bin_dir', help='path to the test binary', 61 | required=True) 62 | parser.add_argument('--result_dir', help='path of result dir', 63 | required=True) 64 | args = parser.parse_args() 65 | 66 | if os.path.exists(args.result_dir): 67 | shutil.rmtree(args.result_dir) 68 | os.mkdir(args.result_dir) 69 | 70 | test_bins = [ 71 | "measure_std_mutex", 72 | "measure_rcuptr", 73 | "measure_rcuptr_jss", 74 | "measure_tbb_qrw_mutex", 75 | "measure_tbb_srw_mutex", 76 | "measure_urcu_bp", 77 | ] 78 | 79 | vec_sizes = ['8196', '131072', '1048576'] 80 | all_readers = ['0', '1'] 81 | writers = ['1'] 82 | measure_iterations = 5 83 | 84 | print("cpu count: " + str(multiprocessing.cpu_count())) 85 | for iteration in range(0, measure_iterations): 86 | for test_bin in test_bins: 87 | for num_all_readers in all_readers: 88 | for vec_size in vec_sizes: 89 | for num_writers in writers: 90 | max_readers = ( 91 | multiprocessing.cpu_count() - 92 | int(num_writers) - 93 | int(num_all_readers)) 94 | for num_readers in range(1, max_readers + 1): 95 | one_measure( 96 | args, 97 | test_bin, 98 | num_all_readers, 99 | vec_size, 100 | num_writers, 101 | str(num_readers), 102 | iteration 103 | ) 104 | 105 | 106 | if __name__ == "__main__": 107 | main() 108 | -------------------------------------------------------------------------------- /rcu_ptr.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | template class AtomicSharedPtr = 9 | detail::__std::atomic_shared_ptr, 10 | typename ASPTraits = 11 | detail::atomic_shared_ptr_traits > 12 | class rcu_ptr { 13 | 14 | template 15 | using atomic_shared_ptr = 16 | typename ASPTraits::template atomic_shared_ptr<_T>; 17 | 18 | atomic_shared_ptr asp; 19 | 20 | public: 21 | template 22 | using shared_ptr = typename ASPTraits::template shared_ptr<_T>; 23 | using element_type = typename shared_ptr::element_type; 24 | 25 | // TODO add 26 | // template 27 | // rcu_ptr(const std::shared_ptr& r) {} 28 | 29 | rcu_ptr() = default; 30 | 31 | rcu_ptr(const shared_ptr& desired) : asp(desired) {} 32 | 33 | rcu_ptr(shared_ptr&& desired) : asp(std::move(desired)) {} 34 | 35 | rcu_ptr(const rcu_ptr&) = delete; 36 | rcu_ptr& operator=(const rcu_ptr&) = delete; 37 | rcu_ptr(rcu_ptr&&) = delete; 38 | rcu_ptr& operator=(rcu_ptr&&) = delete; 39 | 40 | ~rcu_ptr() = default; 41 | 42 | void operator=(const shared_ptr& desired) { reset(desired); } 43 | 44 | shared_ptr read() const { 45 | return asp.load(std::memory_order_consume); 46 | } 47 | 48 | // Overwrites the content of the wrapped shared_ptr. 49 | // We can use it to reset the wrapped data to a new value independent from 50 | // the old value. ( e.g. vector.clear() ) 51 | void reset(const shared_ptr& r) { 52 | asp.store(r, std::memory_order_release); 53 | } 54 | 55 | void reset(shared_ptr&& r) { 56 | asp.store(std::move(r), std::memory_order_release); 57 | } 58 | 59 | // Updates the content of the wrapped shared_ptr. 60 | // We can use it to update the wrapped data to a new value which is 61 | // dependent from the old value ( e.g. vector.push_back() ). 62 | // 63 | // @param fun is a lambda which is called whenever an update 64 | // needs to be done, i.e. it will be called continuously until the update is 65 | // successful. 66 | // 67 | // A call expression with this function is invalid, 68 | // if T is a non-copyable type. 69 | template 70 | void copy_update(R&& fun) { 71 | shared_ptr sp_l = asp.load(std::memory_order_consume); 72 | shared_ptr r; 73 | do { 74 | if (sp_l) { 75 | // deep copy 76 | r = ASPTraits::template make_shared(*sp_l); 77 | } 78 | 79 | // update 80 | std::forward(fun)(r.get()); 81 | } while (!asp.compare_exchange_strong(sp_l, std::move(r), 82 | std::memory_order_release, 83 | std::memory_order_consume)); 84 | } 85 | }; 86 | 87 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable (asp_wrapper_test std_asp_core.cpp std_asp_concurrent.cpp) 2 | target_include_directories(asp_wrapper_test SYSTEM 3 | PUBLIC "${gtest_SOURCE_DIR}/include" 4 | PUBLIC "${gmock_SOURCE_DIR}/include") 5 | target_link_libraries (asp_wrapper_test gtest_main pthread) 6 | add_test(NAME asp_wrapper_test COMMAND asp_wrapper_test) 7 | 8 | add_executable (rcu_ptr_test rcu_unit.cpp rcu_race.cpp) 9 | target_include_directories(rcu_ptr_test SYSTEM 10 | PUBLIC "${gtest_SOURCE_DIR}/include" 11 | PUBLIC "${gmock_SOURCE_DIR}/include") 12 | target_link_libraries (rcu_ptr_test gtest_main pthread) 13 | add_test(NAME rcu_ptr_test COMMAND rcu_ptr_test) 14 | 15 | add_executable (jss_rcu_ptr_test rcu_unit.cpp rcu_race.cpp) 16 | target_include_directories(jss_rcu_ptr_test SYSTEM 17 | PUBLIC "${gtest_SOURCE_DIR}/include" 18 | PUBLIC "${gmock_SOURCE_DIR}/include") 19 | target_link_libraries (jss_rcu_ptr_test gtest_main pthread ${ATOMICLIB}) 20 | target_compile_options(jss_rcu_ptr_test PRIVATE -DTEST_WITH_JSS_ASP) 21 | add_test(NAME jss_rcu_ptr_test COMMAND jss_rcu_ptr_test) 22 | -------------------------------------------------------------------------------- /tests/ExecuteInLoop.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace detail { 6 | 7 | struct RandomWait { 8 | 9 | std::mt19937 random_engine{std::random_device{}()}; 10 | static const constexpr std::size_t MAX = 100000; 11 | std::uniform_int_distribution dist{0, MAX}; 12 | 13 | void operator()() { 14 | std::size_t N = dist(random_engine); 15 | for (std::size_t i = 0; i < N; ++i) { 16 | // http://stackoverflow.com/questions/7083482/how-to-prevent-gcc-from-optimizing-out-a-busy-wait-loop 17 | __asm__(""); 18 | } 19 | } 20 | }; 21 | 22 | template 23 | struct InLoopExecutor { 24 | InLoopExecutor(F f) : f(f) {} 25 | RandomWait random_wait{}; 26 | void operator()() { 27 | for (std::size_t i = 0; i < N; ++i) { 28 | f(); 29 | random_wait(); 30 | } 31 | } 32 | F f; 33 | }; 34 | 35 | } // namespace detail 36 | 37 | template 38 | auto makeInLoopExecutor(F f) { 39 | return detail::InLoopExecutor{f}; 40 | } 41 | 42 | template 43 | void executeInLoop(F f) { 44 | detail::InLoopExecutor{f}(); 45 | } 46 | -------------------------------------------------------------------------------- /tests/countdown.hpp: -------------------------------------------------------------------------------- 1 | // countdown.hpp 2 | // 3 | #pragma once 4 | namespace tools { 5 | 6 | struct Countdown { 7 | public: 8 | explicit constexpr Countdown(const std::size_t From) noexcept 9 | : Value(From) {} 10 | 11 | constexpr operator std::size_t() const noexcept { return Value; } 12 | 13 | std::size_t operator--() noexcept { 14 | if (Value) Value--; 15 | return Value; 16 | } 17 | 18 | std::size_t operator--(int) noexcept { 19 | if (Value) return --Value; 20 | return Value; 21 | } 22 | 23 | private: 24 | std::size_t Value; 25 | }; 26 | 27 | } // tools 28 | -------------------------------------------------------------------------------- /tests/driver.cpp: -------------------------------------------------------------------------------- 1 | // driver.cpp 2 | // 3 | #include 4 | 5 | int main(int argc, char** argv) { 6 | ::testing::InitGoogleTest(&argc, argv); 7 | return RUN_ALL_TESTS(); 8 | } 9 | -------------------------------------------------------------------------------- /tests/orders.hpp: -------------------------------------------------------------------------------- 1 | // orders.hpp 2 | // 3 | #pragma once 4 | #include 5 | #include 6 | 7 | namespace mo { 8 | 9 | std::array constexpr LoadOrders = { 10 | {std::memory_order_seq_cst, std::memory_order_acquire, 11 | std::memory_order_consume, std::memory_order_relaxed}}; 12 | 13 | std::array constexpr StoreOrders = { 14 | {std::memory_order_seq_cst, std::memory_order_release, 15 | std::memory_order_relaxed}}; 16 | 17 | std::array constexpr ReadModifyWriteOrders = { 18 | {std::memory_order_seq_cst, std::memory_order_acq_rel, 19 | std::memory_order_acquire, std::memory_order_release, 20 | std::memory_order_relaxed}}; 21 | 22 | } // namespace mo 23 | -------------------------------------------------------------------------------- /tests/rcu_ptr_under_test.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef TEST_WITH_JSS_ASP 6 | 7 | #include 8 | #include 9 | 10 | using asp_traits = jss::atomic_shared_ptr_traits; 11 | 12 | template 13 | using rcu_ptr_under_test = rcu_ptr; 14 | 15 | #else 16 | 17 | using asp_traits = 18 | detail::atomic_shared_ptr_traits; 19 | 20 | template 21 | using rcu_ptr_under_test = rcu_ptr; 22 | 23 | #endif 24 | 25 | -------------------------------------------------------------------------------- /tests/rcu_race.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | struct RCUPtrRaceTest : public ::testing::Test {}; 10 | 11 | #if 0 12 | TEST_F(RCUPtrRaceTest, tsan_catches_data_race) 13 | { 14 | int a; 15 | 16 | std::thread t1{[&a]() { executeInLoop<1000>([&a]() { ++a; }); }}; 17 | executeInLoop<1000>([&a]() { --a; }); 18 | 19 | t1.join(); 20 | } 21 | #endif 22 | 23 | TEST_F(RCUPtrRaceTest, read_reset) { 24 | rcu_ptr_under_test p(asp_traits::make_shared(42)); 25 | 26 | std::thread t1{[&p]() { 27 | executeInLoop<10000>([&p]() { 28 | auto const new_ = asp_traits::make_shared(42); 29 | p.reset(new_); 30 | }); 31 | }}; 32 | 33 | executeInLoop<10000>([&p]() { 34 | auto& x = *p.read(); 35 | (void)x; 36 | }); 37 | 38 | t1.join(); 39 | } 40 | 41 | TEST_F(RCUPtrRaceTest, read_copy_update) { 42 | rcu_ptr_under_test p(asp_traits::make_shared(42)); 43 | 44 | std::thread t1{[&p]() { 45 | executeInLoop<10000>( 46 | [&p]() { p.copy_update([](auto cp) { *cp = 42; }); }); 47 | }}; 48 | 49 | executeInLoop<10000>([&p]() { 50 | auto& x = *p.read(); 51 | (void)x; 52 | }); 53 | 54 | t1.join(); 55 | 56 | auto const current = p.read(); 57 | ASSERT_TRUE(static_cast(current)); 58 | ASSERT_EQ(42, *current); 59 | } 60 | 61 | TEST_F(RCUPtrRaceTest, reset_reset) { 62 | rcu_ptr_under_test p; 63 | 64 | auto l = [&p]() { 65 | executeInLoop<10000>([&p]() { 66 | auto const new_ = asp_traits::make_shared(42); 67 | p.reset(new_); 68 | }); 69 | }; 70 | 71 | std::thread t1{l}; 72 | std::thread t2{l}; 73 | 74 | t1.join(); 75 | t2.join(); 76 | } 77 | 78 | // void test_read_update() { 79 | // auto p = rcu_ptr_under_test{}; 80 | 81 | // std::thread t1{[&p]() { 82 | // executeInLoop<10000>([&p]() { 83 | // p.update([](const asp_traits::shared_ptr& v) { 84 | // auto v2 = asp_traits::make_shared(42); 85 | // return v2; 86 | //}); 87 | //}); 88 | //}}; 89 | 90 | // executeInLoop<10000>([&p]() { auto& x = *p.read(); }); 91 | 92 | // t1.join(); 93 | // ASSERT(*p.read() == 42); 94 | //} 95 | 96 | // void test_update_update() { 97 | // auto p = rcu_ptr_under_test{}; 98 | // p.update([](const asp_traits::shared_ptr& v) { 99 | // return asp_traits::make_shared(0); 100 | //}); 101 | 102 | // std::thread t1{[&p]() { 103 | // executeInLoop<10000>([&p]() { 104 | // p.update([](const asp_traits::shared_ptr& v) { 105 | // return asp_traits::make_shared((*v) + 1); 106 | //}); 107 | //}); 108 | //}}; 109 | 110 | // std::thread t2{[&p]() { 111 | // executeInLoop<10000>([&p]() { 112 | // p.update([](const asp_traits::shared_ptr& v) { 113 | // return asp_traits::make_shared((*v) + 1); 114 | //}); 115 | //}); 116 | //}}; 117 | 118 | // t1.join(); 119 | // t2.join(); 120 | // ASSERT(*p.read() == 20000); 121 | //} 122 | 123 | TEST_F(RCUPtrRaceTest, copy_update_copy_update) { 124 | rcu_ptr_under_test p(asp_traits::make_shared(0)); 125 | 126 | auto l = [&p]() { 127 | executeInLoop<10000>( 128 | [&p]() { p.copy_update([](auto copy) { (*copy)++; }); }); 129 | }; 130 | 131 | std::thread t1{l}; 132 | std::thread t2{l}; 133 | 134 | t1.join(); 135 | t2.join(); 136 | 137 | auto const current = p.read(); 138 | ASSERT_TRUE(static_cast(current)); 139 | ASSERT_EQ(20000, *current); 140 | } 141 | 142 | TEST_F(RCUPtrRaceTest, copy_update_push_back) { 143 | using V = std::vector; 144 | rcu_ptr_under_test p(asp_traits::make_shared()); 145 | const int i = 2; 146 | 147 | auto l = [&p, &i]() { 148 | executeInLoop<1000>([&p, &i]() { 149 | p.copy_update([&i](auto copy) { copy->push_back(i); }); 150 | }); 151 | }; 152 | 153 | std::thread t1{l}; 154 | std::thread t2{l}; 155 | 156 | t1.join(); 157 | t2.join(); 158 | 159 | auto const current = p.read(); 160 | ASSERT_TRUE(static_cast(current)); 161 | ASSERT_EQ(2000ul, current->size()); 162 | } 163 | 164 | TEST_F(RCUPtrRaceTest, read_read) { 165 | rcu_ptr_under_test p(asp_traits::make_shared(42)); 166 | 167 | auto l = [&p]() { 168 | executeInLoop<10000>([&p]() { 169 | auto& x = *p.read(); 170 | (void)x; 171 | }); 172 | }; 173 | 174 | std::thread t1{l}; 175 | std::thread t2{l}; 176 | 177 | t1.join(); 178 | t2.join(); 179 | } 180 | 181 | TEST_F(RCUPtrRaceTest, assign_reset) { 182 | rcu_ptr_under_test p; 183 | 184 | std::thread t2{[&p]() { 185 | executeInLoop<10000>([&p]() { p = asp_traits::shared_ptr(); }); 186 | }}; 187 | 188 | std::thread t1{[&p]() { 189 | executeInLoop<10000>([&p]() { 190 | auto const new_ = asp_traits::make_shared(42); 191 | p.reset(new_); 192 | }); 193 | }}; 194 | 195 | t1.join(); 196 | t2.join(); 197 | } 198 | 199 | #if 0 200 | TEST_F(RCUPtrRaceTest, copyctor_reset) 201 | { 202 | rcu_ptr_under_test p; 203 | 204 | std::thread t2{[&p]() { executeInLoop<10000>([&p]() 205 | { auto const p2 = p; (void)p2; }); }}; 206 | 207 | std::thread t1{[&p]() { 208 | executeInLoop<10000>([&p]() { 209 | auto const new_ = asp_traits::make_shared(42); 210 | p.reset(new_); 211 | }); 212 | }}; 213 | 214 | t1.join(); 215 | t2.join(); 216 | } 217 | 218 | 219 | TEST_F(RCUPtrRaceTest, copyctor_assign) 220 | { 221 | rcu_ptr_under_test p; 222 | 223 | std::thread t1{[&p]() { executeInLoop<10000>([&p]() 224 | { auto const p2 = p; (void)p2; }); }}; 225 | 226 | std::thread t2{ 227 | [&p]() { executeInLoop<10000>([&p]() { p = rcu_ptr_under_test{}; }); }}; 228 | 229 | t1.join(); 230 | t2.join(); 231 | } 232 | #endif 233 | 234 | TEST_F(RCUPtrRaceTest, empty_rcu_ptr_reset_inside_copy_update) { 235 | rcu_ptr_under_test p; 236 | auto l = [&p]() { 237 | p.copy_update([&p](auto cp) { 238 | if (cp == nullptr) { 239 | p.reset(asp_traits::make_shared(42)); 240 | } else { 241 | (*cp)++; 242 | } 243 | }); 244 | }; 245 | std::thread t1{[l]() { executeInLoop<1000>(l); }}; 246 | std::thread t2{[l]() { executeInLoop<1000>(l); }}; 247 | 248 | t1.join(); 249 | t2.join(); 250 | 251 | auto const current = p.read(); 252 | ASSERT_TRUE(static_cast(current)); 253 | std::cout << *current << std::endl; 254 | ASSERT_EQ(2042, *current); 255 | } 256 | 257 | class X { 258 | rcu_ptr_under_test> v; 259 | 260 | public: 261 | X() { v.reset(asp_traits::make_shared>()); } 262 | int sum() const { // read operation 263 | asp_traits::shared_ptr> local_copy = v.read(); 264 | return std::accumulate(local_copy->begin(), local_copy->end(), 0); 265 | } 266 | void add(int i) { // write operation 267 | v.copy_update([i](auto copy) { copy->push_back(i); }); 268 | } 269 | }; 270 | 271 | TEST_F(RCUPtrRaceTest, sum_add) { 272 | X x; 273 | 274 | int sum = 0; 275 | std::thread t2{ 276 | [&x, &sum]() { executeInLoop<1000>([&x, &sum]() { sum = x.sum(); }); }}; 277 | 278 | std::thread t1{[&x]() { executeInLoop<1000>([&x]() { x.add(3); }); }}; 279 | 280 | std::thread t3{[&x]() { executeInLoop<1000>([&x]() { x.add(4); }); }}; 281 | 282 | t1.join(); 283 | t2.join(); 284 | t3.join(); 285 | 286 | std::cout << x.sum() << std::endl; 287 | ASSERT_EQ(7000, x.sum()); 288 | } 289 | -------------------------------------------------------------------------------- /tests/rcu_unit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct RCUPtrCoreTest : public ::testing::Test {}; 5 | 6 | TEST_F(RCUPtrCoreTest, default_constructible) { 7 | rcu_ptr_under_test p; 8 | (void)p; 9 | } 10 | 11 | TEST_F(RCUPtrCoreTest, resetable) { 12 | rcu_ptr_under_test p; 13 | auto const new_ = asp_traits::make_shared(42); 14 | p.reset(new_); 15 | 16 | auto const current = p.read(); 17 | ASSERT_TRUE(static_cast(current)); 18 | ASSERT_EQ(42, *current); 19 | } 20 | 21 | TEST_F(RCUPtrCoreTest, empty_rcu_ptr_calls_with_nullptr) { 22 | rcu_ptr_under_test p; 23 | p.copy_update([](auto cp) { ASSERT_EQ(cp, nullptr); }); 24 | } 25 | 26 | TEST_F(RCUPtrCoreTest, empty_rcu_ptr_reset_inside_copy_update) { 27 | rcu_ptr_under_test p; 28 | p.copy_update([&p](auto cp) { 29 | if (cp == nullptr) { 30 | p.reset(asp_traits::make_shared(42)); 31 | } else { 32 | ++(*cp); 33 | } 34 | }); 35 | 36 | auto const current = p.read(); 37 | ASSERT_TRUE(static_cast(current)); 38 | ASSERT_EQ(43, *current); 39 | } 40 | -------------------------------------------------------------------------------- /tests/scoped_thread.hpp: -------------------------------------------------------------------------------- 1 | // scoped_thread.hpp 2 | // 3 | #pragma once 4 | 5 | namespace tools { 6 | 7 | template 8 | class scoped_thread { 9 | public: 10 | using thread_type = Thread; 11 | 12 | scoped_thread() = default; 13 | ~scoped_thread() noexcept { Join(Thr); } 14 | 15 | template 16 | scoped_thread(F &&Fn) 17 | : Thr(std::forward(Fn)) {} 18 | 19 | scoped_thread(thread_type const &) = delete; 20 | scoped_thread(scoped_thread const &) = delete; 21 | scoped_thread(scoped_thread &&Other) noexcept 22 | : scoped_thread(std::move(Other.Thr)) {} 23 | 24 | scoped_thread(thread_type &&Thrd) noexcept : Thr(std::move(Thrd)) {} 25 | 26 | scoped_thread &operator=(thread_type const &) = delete; 27 | scoped_thread &operator=(scoped_thread const &) = delete; 28 | scoped_thread &operator=(scoped_thread &&Rhs) { 29 | return (*this = std::move(Rhs.Thr)); 30 | } 31 | 32 | scoped_thread &operator=(thread_type &&Thrd) { 33 | if (&Thrd != &Thr) { 34 | Join(Thr); 35 | Thr = std::move(Thrd); 36 | } 37 | return *this; 38 | } 39 | 40 | private: 41 | static void Join(thread_type &Thrd) noexcept { 42 | if (Thrd.joinable()) Thrd.join(); 43 | } 44 | 45 | thread_type Thr; 46 | }; 47 | 48 | } // namespace tools 49 | -------------------------------------------------------------------------------- /tests/std_asp_concurrent.cpp: -------------------------------------------------------------------------------- 1 | // std_asp_concurrent.cpp 2 | // 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace test { 15 | 16 | using namespace detail::__std; 17 | using MO = std::memory_order; 18 | using LoadOrder = MO; 19 | using StoreOrder = MO; 20 | using Thread = tools::scoped_thread; 21 | 22 | struct StdAtomicSharedPtrLoadStoresTest 23 | : public ::testing::TestWithParam> {}; 24 | 25 | TEST_P(StdAtomicSharedPtrLoadStoresTest, concurrent_stores_w_loads) { 26 | auto const params = GetParam(); 27 | auto const load_o = std::get<0>(params); 28 | auto const store_o = std::get<1>(params); 29 | 30 | atomic_shared_ptr asp(std::make_shared(21)); 31 | std::atomic go{false}; 32 | auto const N = 2; 33 | auto iterations = tools::Countdown{100}; 34 | 35 | auto reader = [&go, &asp, iterations, load_o ]() mutable noexcept { 36 | while (!go) { /*spin*/ 37 | } 38 | while (iterations--) { 39 | auto const sptr = asp.load(load_o); 40 | EXPECT_TRUE(static_cast(sptr)); 41 | } 42 | }; 43 | 44 | auto const expected_value = 13; 45 | auto writer = 46 | [&go, &asp, iterations, expected_value, store_o ]() mutable noexcept { 47 | while (!go) { /*spin*/ 48 | } 49 | while (iterations--) { 50 | auto const sptr = std::make_shared(expected_value); 51 | asp.store(sptr, store_o); 52 | } 53 | }; 54 | { 55 | std::vector threads; 56 | threads.reserve(N * 2); 57 | for (auto i = 0; i < N; i++) { 58 | threads.emplace_back(std::thread(reader)); 59 | threads.emplace_back(std::thread(writer)); 60 | } 61 | go = true; 62 | } 63 | 64 | auto const sptr = asp.load(load_o); 65 | ASSERT_TRUE(static_cast(sptr)); 66 | ASSERT_EQ(expected_value, *sptr); 67 | } 68 | 69 | INSTANTIATE_TEST_CASE_P( 70 | Concurrent, StdAtomicSharedPtrLoadStoresTest, 71 | ::testing::Combine(::testing::ValuesIn(mo::LoadOrders), 72 | ::testing::ValuesIn(mo::StoreOrders)), ); 73 | 74 | using RMWOrder = std::memory_order; 75 | 76 | struct StdAtomicSharedPtrReadModifyWriteTest 77 | : public ::testing::TestWithParam< 78 | std::tuple> { 79 | using ParamsType = std::tuple; 80 | 81 | template 82 | static void Test(ParamsType const &Params, LF &&LoadFn, SF &&StoreFn, 83 | RMWF &&RMWFn) { 84 | static auto const N = 2; 85 | auto const initial = std::make_shared(13); 86 | atomic_shared_ptr asp(initial); 87 | std::atomic go{false}; 88 | 89 | auto iterations = tools::Countdown{10}; 90 | auto const load_o = std::get<0>(Params); 91 | auto const store_o = std::get<1>(Params); 92 | auto const rmw_o = std::get<2>(Params); 93 | 94 | auto reader = [&, iterations ]() mutable noexcept { 95 | while (!go) 96 | ; 97 | while (iterations--) LoadFn(asp, load_o); 98 | }; 99 | 100 | auto writer = [&, iterations ]() mutable noexcept { 101 | while (!go) 102 | ; 103 | while (iterations--) StoreFn(asp, store_o); 104 | }; 105 | 106 | auto rmw = [&, iterations ]() mutable noexcept { 107 | while (!go) 108 | while (iterations--) RMWFn(asp, rmw_o); 109 | }; 110 | { 111 | std::vector threads; 112 | threads.reserve(N * 3); 113 | for (auto i = 0; i < N; i++) { 114 | threads.emplace_back(reader); 115 | threads.emplace_back(writer); 116 | threads.emplace_back(rmw); 117 | } 118 | go = true; 119 | } 120 | } 121 | }; 122 | 123 | TEST_P(StdAtomicSharedPtrReadModifyWriteTest, load_store_exchange) { 124 | auto const params = GetParam(); 125 | Test(params, 126 | [](auto &Asp, auto const LO) mutable noexcept { 127 | auto const sptr = Asp.load(LO); 128 | ASSERT_TRUE(static_cast(sptr)); 129 | } 130 | 131 | , 132 | [](auto &Asp, auto const SO) mutable noexcept { 133 | auto const sptr = std::make_shared(21); 134 | Asp.store(sptr, SO); 135 | } 136 | 137 | , 138 | [](auto &Asp, auto const RMWO) mutable noexcept { 139 | auto const new_ = std::make_shared(31); 140 | auto const prev = Asp.exchange(new_, RMWO); 141 | ASSERT_TRUE(static_cast(prev)); 142 | }); 143 | } 144 | 145 | TEST_P(StdAtomicSharedPtrReadModifyWriteTest, load_store_cas_strong) { 146 | auto const params = GetParam(); 147 | Test(params, 148 | [](auto &Asp, auto const LO) mutable noexcept { 149 | auto const sptr = Asp.load(LO); 150 | ASSERT_TRUE(static_cast(sptr)); 151 | } 152 | 153 | , 154 | [](auto &Asp, auto const SO) mutable noexcept { 155 | auto const sptr = std::make_shared(21); 156 | Asp.store(sptr, SO); 157 | } 158 | 159 | , 160 | [](auto &Asp, auto const RMWO) mutable noexcept { 161 | auto const desired = std::make_shared(42); 162 | auto expected = Asp.load(RMWO); 163 | do { 164 | ASSERT_TRUE(static_cast(expected)); 165 | } while ( 166 | !Asp.compare_exchange_strong(expected, desired, RMWO, RMWO)); 167 | }); 168 | } 169 | 170 | TEST_P(StdAtomicSharedPtrReadModifyWriteTest, load_store_cas_weak) { 171 | auto const params = GetParam(); 172 | Test( 173 | params, 174 | [](auto &Asp, auto const LO) mutable noexcept { 175 | auto const sptr = Asp.load(LO); 176 | ASSERT_TRUE(static_cast(sptr)); 177 | } 178 | 179 | , 180 | [](auto &Asp, auto const SO) mutable noexcept { 181 | auto const sptr = std::make_shared(21); 182 | Asp.store(sptr, SO); 183 | } 184 | 185 | , 186 | [](auto &Asp, auto const RMWO) mutable noexcept { 187 | auto const desired = std::make_shared(42); 188 | auto expected = Asp.load(RMWO); 189 | do { 190 | ASSERT_TRUE(static_cast(expected)); 191 | } while (!Asp.compare_exchange_weak(expected, desired, RMWO, RMWO)); 192 | }); 193 | } 194 | 195 | INSTANTIATE_TEST_CASE_P( 196 | ConcurrentRMWOperations, StdAtomicSharedPtrReadModifyWriteTest, 197 | ::testing::Combine(::testing::ValuesIn(mo::LoadOrders), 198 | ::testing::ValuesIn(mo::StoreOrders), 199 | ::testing::ValuesIn(mo::ReadModifyWriteOrders)), ); 200 | 201 | } // namespace test 202 | -------------------------------------------------------------------------------- /tests/std_asp_core.cpp: -------------------------------------------------------------------------------- 1 | // std_asp_core.cpp 2 | // 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace detail::__std; 11 | 12 | struct StdAtomicSharedPtrCore : public ::testing::Test {}; 13 | 14 | TEST_F(StdAtomicSharedPtrCore, constructible) { 15 | { atomic_shared_ptr asp; } 16 | { atomic_shared_ptr asp(std::shared_ptr{}); } 17 | { atomic_shared_ptr asp(std::make_shared(13)); } 18 | } 19 | 20 | TEST_F(StdAtomicSharedPtrCore, is_lock_free) { 21 | atomic_shared_ptr asp; 22 | std::shared_ptr sptr; 23 | ASSERT_EQ(std::atomic_is_lock_free(&sptr), asp.is_lock_free()); 24 | } 25 | 26 | struct StdAtomicSharedPtrSimpleOps 27 | : public ::testing::TestWithParam< 28 | std::tuple, std::memory_order>> {}; 29 | 30 | TEST_P(StdAtomicSharedPtrSimpleOps, load_returns_current_sptr) { 31 | auto const params = GetParam(); 32 | auto const expected_sptr = std::get<0>(params); 33 | atomic_shared_ptr asp(expected_sptr); 34 | 35 | auto const mo = std::get<1>(params); 36 | auto const actual_sptr = asp.load(mo); 37 | ASSERT_EQ(expected_sptr.get(), actual_sptr.get()); 38 | } 39 | 40 | TEST_P(StdAtomicSharedPtrSimpleOps, sptr_conversion_returns_current_sptr) { 41 | auto const params = GetParam(); 42 | auto const expected_sptr = std::get<0>(params); 43 | atomic_shared_ptr asp(expected_sptr); 44 | 45 | auto const actual_sptr = static_cast>(asp); 46 | ASSERT_EQ(expected_sptr.get(), actual_sptr.get()); 47 | } 48 | 49 | TEST_P(StdAtomicSharedPtrSimpleOps, store_makes_sptr_current) { 50 | auto const params = GetParam(); 51 | auto const expected_sptr = std::get<0>(params); 52 | atomic_shared_ptr asp; 53 | 54 | auto const mo = std::get<1>(params); 55 | asp.store(expected_sptr, mo); 56 | auto const actual_sptr = asp.load(); 57 | ASSERT_EQ(expected_sptr.get(), actual_sptr.get()); 58 | } 59 | 60 | TEST_P(StdAtomicSharedPtrSimpleOps, 61 | exchange_makes_sptr_current_and_returns_previous) { 62 | auto const params = GetParam(); 63 | auto const initial_sptr = std::get<0>(params); 64 | atomic_shared_ptr asp(initial_sptr); 65 | 66 | auto const mo = std::get<1>(params); 67 | auto const new_sptr = std::make_shared(13); 68 | auto const prev_sptr = asp.exchange(new_sptr, mo); 69 | ASSERT_EQ(prev_sptr.get(), initial_sptr.get()); 70 | 71 | auto const actual_sptr = asp.load(); 72 | ASSERT_EQ(new_sptr.get(), actual_sptr.get()); 73 | } 74 | 75 | using SptrVec = std::vector>; 76 | using MOVec = std::vector; 77 | 78 | SptrVec const Sptrs = {std::shared_ptr(), std::make_shared(21)}; 79 | MOVec const MemoryOrders = { 80 | std::memory_order_seq_cst, std::memory_order_acq_rel, 81 | std::memory_order_acquire, std::memory_order_release, 82 | std::memory_order_consume, std::memory_order_relaxed}; 83 | 84 | INSTANTIATE_TEST_CASE_P( 85 | SimpleAtomicOperations, StdAtomicSharedPtrSimpleOps, 86 | ::testing::Combine(::testing::ValuesIn(Sptrs), 87 | ::testing::ValuesIn(MemoryOrders)), ); 88 | 89 | // 90 | // Basically a member-fn Pointer.. 91 | // 92 | using CASFunction = std::function &, std::shared_ptr &, std::shared_ptr, 94 | std::memory_order, std::memory_order)>; 95 | 96 | struct StdAtomicSharedPtrCASOps 97 | : public ::testing::TestWithParam< 98 | std::tuple, std::memory_order, std::memory_order, 99 | CASFunction>> {}; 100 | 101 | TEST_P(StdAtomicSharedPtrCASOps, cas_succeeds) { 102 | auto const params = GetParam(); 103 | auto const initial_sptr = std::get<0>(params); 104 | auto const succ = std::get<1>(params); 105 | auto const fail = std::get<2>(params); 106 | auto const CAS = std::get<3>(params); 107 | 108 | atomic_shared_ptr asp(initial_sptr); 109 | 110 | auto const desired = std::make_shared(13); 111 | auto expected = initial_sptr; 112 | auto const success = CAS(asp, expected, desired, succ, fail); 113 | ASSERT_TRUE(success); 114 | ASSERT_EQ(expected.get(), initial_sptr.get()); 115 | 116 | auto const actual = asp.load(); 117 | ASSERT_EQ(desired.get(), actual.get()); 118 | } 119 | 120 | TEST_P(StdAtomicSharedPtrCASOps, cas_fails) { 121 | auto const params = GetParam(); 122 | auto const initial_sptr = std::get<0>(params); 123 | auto const succ = std::get<1>(params); 124 | auto const fail = std::get<2>(params); 125 | auto const CAS = std::get<3>(params); 126 | 127 | atomic_shared_ptr asp(initial_sptr); 128 | 129 | auto const desired = std::make_shared(13); 130 | auto expected = desired; 131 | auto const success = CAS(asp, expected, desired, succ, fail); 132 | ASSERT_FALSE(success); 133 | ASSERT_EQ(expected.get(), initial_sptr.get()); 134 | 135 | auto const actual = asp.load(); 136 | ASSERT_EQ(expected.get(), actual.get()); 137 | } 138 | 139 | using CASFnVec = std::vector; 140 | 141 | CASFnVec const CASFunctions = { 142 | CASFunction([](auto &asp, auto &&expected, auto desired, auto succ, 143 | auto fail) { 144 | return asp.compare_exchange_weak( 145 | std::forward(expected), desired, succ, fail); 146 | }) 147 | 148 | , 149 | CASFunction([](auto &asp, auto &&expected, auto desired, auto succ, 150 | auto fail) { 151 | return asp.compare_exchange_strong( 152 | std::forward(expected), desired, succ, fail); 153 | }) 154 | 155 | , 156 | CASFunction([](auto &asp, auto &&expected, auto desired, auto succ, auto) { 157 | return asp.compare_exchange_weak( 158 | std::forward(expected), desired, succ); 159 | }) 160 | 161 | , 162 | CASFunction([](auto &asp, auto &&expected, auto desired, auto succ, auto) { 163 | return asp.compare_exchange_strong( 164 | std::forward(expected), desired, succ); 165 | })}; 166 | 167 | INSTANTIATE_TEST_CASE_P( 168 | CASOps, StdAtomicSharedPtrCASOps, 169 | ::testing::Combine(::testing::ValuesIn(Sptrs), 170 | ::testing::ValuesIn(MemoryOrders), 171 | ::testing::ValuesIn(MemoryOrders), 172 | ::testing::ValuesIn(CASFunctions)), ); 173 | --------------------------------------------------------------------------------