├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md ├── deprecated ├── consumer.h ├── disruptor.h ├── producer.h └── test_disruptor.cpp ├── examples ├── Makefile ├── complex.cpp ├── rules.mk ├── simple.cpp ├── two_to_one_multi_put.cpp └── two_to_one_selector.cpp ├── include └── L3 │ ├── non_static │ └── disruptor.h │ ├── static │ ├── README │ ├── barrier.h │ ├── consume.h │ ├── disruptor.h │ ├── get.h │ ├── logger.h │ ├── put.h │ ├── selector.h │ ├── sequence.h │ └── spinpolicy.h │ └── util │ ├── README │ ├── allocator.h │ ├── cacheline.h │ ├── counter.h │ ├── fifo.h │ ├── flexififo.h │ ├── ring.h │ ├── scopedtimer.h │ ├── timer.h │ └── types.h ├── rules.mk ├── src ├── Makefile ├── allocator.cpp ├── barrier.cpp ├── cacheline.cpp ├── counter.cpp ├── disruptor.cpp ├── fifo.cpp ├── get.cpp ├── put.cpp ├── ring.cpp ├── rules.mk ├── sequence.cpp ├── timer.cpp └── types.cpp ├── test ├── Makefile ├── rules.mk ├── test_fifo.cpp ├── test_flexififo.cpp ├── test_latency.cpp ├── test_logger.cpp ├── test_min.cpp ├── test_putget.cpp ├── test_putget.py └── test_selector.cpp └── tums ├── .gitignore ├── .gitrepo ├── LICENSE ├── README.md ├── features ├── clean.mk ├── cpp.mk ├── dbg.mk ├── exec.mk ├── lib.mk ├── mkdir.mk ├── subdirs.mk └── test.mk ├── patterns ├── cpp.mk ├── exec.mk ├── lib.mk ├── mkdir.mk └── test.mk ├── recurse.mk ├── show_deps.mk ├── top.mk └── tums.mk /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.o 3 | a.out 4 | *.d 5 | test_disruptor 6 | test_fifo 7 | bld 8 | 9 | *.data 10 | 11 | results 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | tums/tums.mk -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # L3 - Low Latency Lib 2 | C++ tools for building low latency systems. Includes a very fast and light implementation of an [LMAX disruptor] (https://lmax-exchange.github.io/disruptor). Code is written in c++11, makes use of `std::atomic` and therefore requires a suitable compiler. This is a header only library. Built and tested with Apple LLVM 5.1 on OS 10.10.3. 3 | 4 | Simple example usage: 5 | ```C++ 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | // 12 | // Lets say we're going to use size_t as our message. 13 | // 14 | using Msg = size_t; 15 | // 16 | // Declare alias for a disruptor for this type of message and a ring 17 | // buffer of size 2^19 = 512K messages. 18 | // 19 | using D = L3::Disruptor; 20 | // 21 | // Alias Put and Get types using defaults. 22 | // 23 | using Get = D::Get<>; 24 | using Put = D::Put<>; 25 | // 26 | // We've now created a very basic disruptor with a single producer and 27 | // consumer. We can use it like this... 28 | // 29 | int 30 | main() 31 | { 32 | // 33 | // To do a put we make an instance of the Put class we've just 34 | // defined. 35 | // 36 | { 37 | // 38 | // Creating a put claims a slot in the ring. 39 | // 40 | Put p; 41 | // 42 | // To write a value use assignment. 43 | // 44 | p = 1; 45 | // 46 | // When p goes out of scope the put is commited and published 47 | // to any consumers. It's important to note that once you have 48 | // started a put it will always be commited. RAII takes care 49 | // of that. 50 | // 51 | } 52 | // 53 | // Since unnamed temporaries have the lifetime of the statement 54 | // they are created in we can shorten the above to: 55 | // 56 | Put() = 2; 57 | // 58 | // To get we do something similar but gets are batched so we need 59 | // to read in a loop. 60 | // 61 | for(Msg m: Get()) 62 | { 63 | std::cout << "got: " << m << std::endl; 64 | } 65 | // 66 | // We can send messages between two threads like so. 67 | // 68 | // 10 Million messages takes less than a second on my old macbook 69 | // pro. 70 | // 71 | const size_t loops = 10000000; 72 | // 73 | // Producer just writes sequencial integers. 74 | // 75 | std::thread producer([]{ for(size_t i = 1; i < loops; ++i) Put() = i; }); 76 | // 77 | // Consumer checks that these are read in the same order. 78 | // 79 | Msg previous = 0; 80 | // 81 | // Should get the same number of messages we put. 82 | // 83 | for(size_t i = 1; i < loops;) 84 | { 85 | for(Msg m: Get()) 86 | { 87 | if(m != previous + 1) 88 | { 89 | // 90 | // Will abort since producer not joined. 91 | // 92 | return -1; 93 | } 94 | previous = m; 95 | ++i; 96 | } 97 | } 98 | producer.join(); 99 | return 0; 100 | } 101 | ``` 102 | See examples directory for how more complex topologies are supported. 103 | -------------------------------------------------------------------------------- /deprecated/consumer.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSUMER_H 2 | #define CONSUMER_H 3 | 4 | #include "disruptor.h" 5 | #include "sequence.h" 6 | 7 | namespace L3 8 | { 9 | template 10 | struct ConsumerList; 11 | 12 | template 13 | class ConsumerT 14 | { 15 | template 16 | friend struct ConsumerList; 17 | 18 | template 19 | friend struct ConsumerArray; 20 | 21 | L3_CACHE_LINE Sequence _tail{Dstor::size + 1}; 22 | 23 | // constexpr ConsumerT(){} 24 | 25 | template 26 | Index claim(SpinProbe spinProbe=SpinProbe()) 27 | { 28 | // 29 | // Eventually slot will be used to calculate the address 30 | // of the next item read from the ring. It will therefore 31 | // form a dependency chain with this item so consume 32 | // memory order is sufficient. 33 | // 34 | Index slot = _tail.load(std::memory_order_acquire); 35 | while(slot > dstor._cursor.load(std::memory_order_acquire)) 36 | { 37 | spinProbe(); 38 | } 39 | return slot; 40 | } 41 | 42 | void commit() 43 | { 44 | _tail.fetch_add(1, std::memory_order_release); 45 | } 46 | 47 | public: 48 | using value_type = typename Dstor::value_type; 49 | 50 | template 51 | class Get 52 | { 53 | ConsumerT& _c; 54 | Index _slot; 55 | 56 | public: 57 | using value_type = typename Dstor::value_type; 58 | 59 | Get(ConsumerT& c): _c(c), _slot(c.claim(ClaimProbe())) {} 60 | ~Get() { _c.commit(); } 61 | 62 | operator const value_type&() const { return dstor[_slot]; } 63 | }; 64 | 65 | value_type get() 66 | { 67 | return Get<>(*this); 68 | } 69 | }; 70 | 71 | template 72 | struct ConsumerList 73 | { 74 | static bool lessThanEqual(Index i) { return false; } 75 | }; 76 | 77 | template 78 | struct ConsumerList: ConsumerList 79 | { 80 | static bool lessThanEqual(Index i) 81 | { 82 | return c._tail.load(std::memory_order_acquire) <= i || 83 | ConsumerList::lessThanEqual(i); 84 | } 85 | }; 86 | 87 | template 88 | struct ConsumerArray 89 | { 90 | static bool lessThanEqual(Index idx) 91 | { 92 | for(auto& i: consumers) 93 | { 94 | if(i._tail.load(std::memory_order_acquire) <= idx) 95 | { 96 | return true; 97 | } 98 | } 99 | return false; 100 | } 101 | }; 102 | } 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /deprecated/disruptor.h: -------------------------------------------------------------------------------- 1 | #ifndef DISRUPTOR_H 2 | #define DISRUPTOR_H 3 | 4 | #include "ring.h" 5 | #include "cacheline.h" 6 | #include "types.h" 7 | #include "sequence.h" 8 | 9 | #include 10 | 11 | namespace L3 // Low Latency Library 12 | { 13 | // using Sequence = std::atomic; 14 | 15 | template 16 | class DisruptorT: Ring 17 | { 18 | // 19 | // A valid ring buffer needs the cursor position to be 20 | // different from the head position. Therefore the minimum 21 | // size is 2 or 2^1. 22 | // 23 | static_assert(log2size > 0, "Minimun ring size is 2"); 24 | // 25 | // In the implementation of a disruptor there's a window 26 | // between a slot has been claimed and the cursor advanced 27 | // where the value must be copied into the ring buffer. If 28 | // this operation fails and the cursor is not advanced then 29 | // the whole system will block. To ensure the value copy 30 | // cannot fail constrain T to being a POD. 31 | // 32 | static_assert(std::is_pod::value, "PODs only"); 33 | 34 | public: 35 | using Ring = Ring; 36 | using value_type = T; 37 | 38 | using Ring::operator[]; 39 | 40 | static constexpr Index size{Ring::size}; 41 | L3_CACHE_LINE Sequence _cursor{size}; 42 | }; 43 | } 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /deprecated/producer.h: -------------------------------------------------------------------------------- 1 | #ifndef PRODUCER_H 2 | #define PRODUCER_H 3 | 4 | #include "types.h" 5 | #include "cacheline.h" 6 | #include "disruptor.h" 7 | #include "consumer.h" 8 | 9 | namespace L3 10 | { 11 | namespace ProducerType 12 | { 13 | struct Single {}; 14 | struct Multi {}; 15 | }; 16 | 17 | template struct ProducerTraits; 18 | 19 | template<> 20 | struct ProducerTraits 21 | { 22 | using HeadType = Index; 23 | }; 24 | 25 | template<> 26 | struct ProducerTraits 27 | { 28 | using HeadType = std::atomic; 29 | }; 30 | 31 | template 32 | using HeadType = typename ProducerTraits::HeadType; 33 | 34 | template 38 | class ProducerT 39 | { 40 | // 41 | // For when there is only a single thread putting. 42 | // 43 | // _head is only ever accessed by the single producing thread 44 | // and therefor does not need any synchronisation. 45 | // 46 | L3_CACHE_LINE HeadType _head {Dstor::size + 1}; 47 | 48 | Index incHead(ProducerType::Single) 49 | { 50 | // 51 | // For the single threaded producer no need to synchronise so 52 | // just inc. 53 | // 54 | return _head++; 55 | } 56 | 57 | Index incHead(ProducerType::Multi) 58 | { 59 | // 60 | // Multi-threaded producer must use atomic increment. 61 | // 62 | return _head.fetch_add(1, std::memory_order_acquire); 63 | } 64 | 65 | bool waitForCursor(ProducerType::Single, Index _Slot) 66 | { 67 | return false; 68 | } 69 | 70 | bool waitForCursor(ProducerType::Multi, Index slot) 71 | { 72 | // 73 | // For multiple producers it's possible that we've claimed a 74 | // slot while another producer is doing a put. If the other 75 | // producers have not yet committed then we must wait for 76 | // them. When cursor is 1 less than _slot we know that we are 77 | // next to commit. 78 | // 79 | return dstor._cursor.load(std::memory_order_acquire) < slot - 1; 80 | } 81 | 82 | template 83 | Index claim(const SpinProbe& sp=SpinProbe()) 84 | { 85 | // 86 | // Claim a slot. For the single producer we just do... 87 | // 88 | Index slot = incHead(type()); 89 | // 90 | // We have our slot but we cannot write to it until we 91 | // are sure we will not overwrite data that has not 92 | // yet been consumed. In other words _head cannot lap 93 | // _tail around the ring. 94 | // 95 | // To prevent _head catching up with _tail we must 96 | // ensure that the gap between _head and _tail is never 97 | // greater than size. Or 98 | // 99 | // _tail > _head - size 100 | // 101 | // We spin while this is not the case. 102 | // 103 | // To optimise the loop condition precompute _head - 104 | // size. To ensure that this difference is always positive 105 | // initialises _head and _tail using size as a baseline - 106 | // ie as if we have already done one lap around the ring. 107 | // 108 | Index wrapAt = slot - Dstor::size; 109 | // 110 | // We only need to know that this relation is satisfied at 111 | // this point. Since _tail monotonically increases the 112 | // condition cannot be invalidated by consumers. 113 | // 114 | while(ConsumerList::lessThanEqual(wrapAt)) 115 | { 116 | sp(); 117 | } 118 | return slot; 119 | } 120 | 121 | template 122 | void commit(Index slot, const SpinProbe& sp=SpinProbe()) 123 | { 124 | // 125 | // For multiple producers it's possible that we've claimed a 126 | // slot while another producer is doing a put. If the other 127 | // producers have not yet committed then we must wait for 128 | // them. When cursor is 1 less than _slot we know that we are 129 | // next to commit. 130 | // 131 | SpinProbe probe; 132 | while(waitForCursor(type(), slot)) 133 | { 134 | sp(); 135 | } 136 | // 137 | // No need to CAS. Only we could have been waiting for 138 | // this particular cursor value. 139 | // 140 | dstor._cursor.fetch_add(1, std::memory_order_release); 141 | } 142 | 143 | public: 144 | using value_type = typename Dstor::value_type; 145 | 146 | template 147 | friend class Put; 148 | 149 | template 150 | class Put 151 | { 152 | ProducerT& _p; 153 | Index _slot; 154 | public: 155 | using value_type = typename Dstor::value_type; 156 | 157 | Put(ProducerT& p): _p(p), _slot(p.claim(ClaimProbe())) {} 158 | ~Put() { _p.commit(_slot, CommitProbe()); } 159 | 160 | value_type& operator=(const value_type& rhs) const 161 | { return dstor[_slot] = rhs; } 162 | 163 | operator value_type&() const 164 | { return dstor[_slot]; } 165 | }; 166 | 167 | template 168 | void put(const value_type& msg) 169 | { 170 | Put p(*this); 171 | p = msg; 172 | } 173 | 174 | void put(const value_type& msg) 175 | { 176 | put<>(msg); 177 | } 178 | }; 179 | } 180 | 181 | #endif 182 | -------------------------------------------------------------------------------- /deprecated/test_disruptor.cpp: -------------------------------------------------------------------------------- 1 | #include "consumer.h" 2 | #include "disruptor.h" 3 | #include "producer.h" 4 | #include "sequence.h" 5 | #include "util.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace L3; 12 | typedef size_t Msg; 13 | 14 | #ifndef L3_ITERATIONS 15 | // 100 Million. 16 | #define L3_ITERATIONS (100000000) 17 | #endif 18 | 19 | constexpr size_t iterations {L3_ITERATIONS}; 20 | 21 | #ifndef L3_QSIZE 22 | #define L3_QSIZE 19 23 | #endif 24 | 25 | constexpr size_t qSize {L3_QSIZE}; 26 | 27 | template 28 | struct TestCase 29 | { 30 | using Disruptor = DisruptorT; 31 | L3_CACHE_LINE static Disruptor disruptor; 32 | 33 | // static SequenceList sl; 34 | 35 | L3_CACHE_LINE static size_t cursorSpinCount; 36 | L3_CACHE_LINE static size_t putSpinCount; 37 | L3_CACHE_LINE static size_t getSpinCount; 38 | 39 | using CursorSpinCounter = Counter; 40 | using PutSpinCounter = Counter; 41 | using GetSpinCounter = Counter; 42 | 43 | using Consumer = ConsumerT; 44 | 45 | static Consumer consumers[consumerInstances]; 46 | 47 | using CArray = ConsumerArray; 48 | 49 | using Producer = ProducerT< 50 | Disruptor, disruptor, 51 | CArray, 52 | Type>; 53 | static Producer producer; 54 | 55 | using PUT = typename Producer::template Put; 57 | 58 | using GET = typename Consumer::template Get; 59 | }; 60 | 61 | template 62 | L3_CACHE_LINE typename TestCase::Disruptor 63 | TestCase::disruptor; 64 | 65 | template 66 | L3_CACHE_LINE size_t TestCase::cursorSpinCount; 67 | 68 | template 69 | 70 | L3_CACHE_LINE size_t TestCase::putSpinCount; 71 | 72 | template 73 | L3_CACHE_LINE size_t TestCase::getSpinCount; 74 | 75 | template 76 | typename TestCase::Consumer 77 | TestCase::consumers[consumerInstances]; 78 | 79 | template 80 | typename TestCase::Producer 81 | TestCase::producer; 82 | 83 | 84 | namespace test1to1 85 | { 86 | using TC = TestCase; 87 | 88 | std::array msgs; 89 | 90 | bool run() 91 | { 92 | int result = 0; 93 | 94 | // std::atomic go { false }; 95 | 96 | { 97 | TC::PUT p(TC::producer); 98 | p = 42; 99 | } 100 | { 101 | TC::GET g(TC::consumers[0]); 102 | Msg m = g; 103 | std::cout << "Got: " << m << std::endl; 104 | } 105 | { 106 | TC::PUT p(TC::producer); 107 | p = 48; 108 | } 109 | { 110 | TC::GET g(TC::consumers[0]); 111 | Msg m = g; 112 | std::cout << "Got: " << m << std::endl; 113 | } 114 | 115 | std::thread prod1( 116 | [&](){ 117 | // while(!go); 118 | for(Msg i = 1; i < iterations; i++) 119 | { 120 | TC::PUT p(TC::producer); 121 | p = i; 122 | } 123 | std::cerr << "Prod done" << std::endl; 124 | }); 125 | 126 | auto consume = [&](){ 127 | // while(!go); 128 | Msg previous; 129 | { 130 | TC::GET g(TC::consumers[0]); 131 | previous = g; 132 | } 133 | msgs[0] = previous; 134 | for(size_t i = 1; i < iterations - 1; ++i) 135 | { 136 | Msg msg; 137 | { 138 | TC::GET g(TC::consumers[0]); 139 | msg = g; 140 | } 141 | msgs[i] = msg; 142 | long diff = (msg - previous) - 1; 143 | result += (diff * diff); 144 | previous = msg; 145 | } 146 | std::cerr << "Cons done" << std::endl; 147 | }; 148 | // std::thread consumer(consume); 149 | consume(); 150 | 151 | // go = true; 152 | 153 | prod1.join(); 154 | // consumer.join(); 155 | 156 | std::cout << "result: " << result << std::endl; 157 | 158 | std::cout << "putSpinCount = " << TC::putSpinCount << std::endl; 159 | std::cout << "getSpinCount = " << TC::getSpinCount << std::endl; 160 | std::cout << "cursorSpinCount = " << TC::cursorSpinCount << std::endl; 161 | 162 | Msg previous = 0; 163 | bool status = result == 0; 164 | for(auto i: msgs) 165 | { 166 | if(i == 0) 167 | { 168 | continue; 169 | } 170 | if(previous >= i) 171 | { 172 | std::cout << "Wrong at: " << i << std::endl; 173 | status = false; 174 | } 175 | previous = i; 176 | } 177 | // std::cout << "Dequeued." << std::endl; 178 | // std::copy( 179 | // msgs.begin(), 180 | // msgs.end(), 181 | // std::ostream_iterator(std::cout, "\n")); 182 | 183 | // std::cout << "Ring." << std::endl; 184 | // for(int i = 0; i < L3_QSIZE; i++) 185 | // { 186 | // std::cout << TC::disruptor[i] << std::endl; 187 | // } 188 | return status; 189 | } 190 | } 191 | 192 | namespace test2to1 193 | { 194 | using TC = TestCase; 195 | std::array msgs; 196 | 197 | bool run() 198 | { 199 | 200 | int result = 0; 201 | 202 | std::atomic go { false }; 203 | 204 | std::thread producer1( 205 | [&](){ 206 | while(!go); 207 | for(Msg i = 3; i < iterations; i += 2) 208 | { 209 | TC::PUT p(TC::producer); 210 | p = i; 211 | } 212 | std::cerr << "Prod 1 done" << std::endl; 213 | }); 214 | 215 | std::thread producer2( 216 | [&](){ 217 | while(!go); 218 | for(Msg i = 2; i < iterations; i += 2) 219 | { 220 | TC::PUT p(TC::producer); 221 | p = i; 222 | } 223 | std::cerr << "Prod 2 done" << std::endl; 224 | }); 225 | 226 | auto consume = [&](){ 227 | Msg oldOdd = 1; 228 | Msg oldEven = 0; 229 | for(size_t i = 1; i < iterations - 5; ++i) 230 | { 231 | Msg msg; 232 | { 233 | TC::GET g(TC::consumers[0]); 234 | msg = g; 235 | } 236 | Msg& old = msg & 0x1L ? oldOdd : oldEven; 237 | 238 | long diff = (msg - old) - 2; 239 | result += (diff * diff); 240 | old = msg; 241 | msgs[i] = msg; 242 | } 243 | std::cerr << "Cons done" << std::endl; 244 | }; 245 | 246 | go = true; 247 | consume(); 248 | producer1.join(); 249 | producer2.join(); 250 | 251 | std::cout << "result: " << result << std::endl; 252 | std::cout << "putSpinCount = " << TC::putSpinCount << std::endl; 253 | std::cout << "getSpinCount = " << TC::getSpinCount << std::endl; 254 | std::cout << "cursorSpinCount = " << TC::cursorSpinCount << std::endl; 255 | 256 | // Msg previous = 0; 257 | bool status = result == 0; 258 | 259 | return status; 260 | } 261 | } 262 | 263 | namespace test1to2 264 | { 265 | using TC = TestCase; 266 | 267 | bool run() 268 | { 269 | std::atomic result{0}; 270 | std::atomic go { false }; 271 | 272 | auto produce = [&](){ 273 | while(!go); 274 | for(Msg i = 1; i < iterations; i++) 275 | { 276 | TC::PUT p(TC::producer); 277 | p = i; 278 | } 279 | std::cerr << "Prod 1 done" << std::endl; 280 | }; 281 | 282 | auto consume = [&](TC::Consumer& cons){ 283 | Msg previous = 0; 284 | for(size_t i = 1; i < iterations; ++i) 285 | { 286 | Msg msg; 287 | { 288 | TC::GET g(cons); 289 | msg = g; 290 | } 291 | long diff = (msg - previous) - 1; 292 | result.fetch_add(diff * diff); 293 | previous = msg; 294 | } 295 | std::cerr << "Cons done" << std::endl; 296 | }; 297 | 298 | std::thread cons1([&](){ consume(TC::consumers[0]); }); 299 | std::thread cons2([&](){ consume(TC::consumers[1]); }); 300 | 301 | go = true; 302 | produce(); 303 | cons1.join(); 304 | cons2.join(); 305 | 306 | std::cout << "result: " << result << std::endl; 307 | std::cout << "putSpinCount = " << TC::putSpinCount << std::endl; 308 | std::cout << "getSpinCount = " << TC::getSpinCount << std::endl; 309 | std::cout << "cursorSpinCount = " << TC::cursorSpinCount << std::endl; 310 | 311 | // Msg previous = 0; 312 | bool status = result == 0; 313 | 314 | return status; 315 | } 316 | } 317 | 318 | int main() 319 | { 320 | bool status = true; 321 | status &= test1to1::run(); 322 | status &= test2to1::run(); 323 | status &= test1to2::run(); 324 | 325 | return status ? 0 : 1; 326 | } 327 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | srcs.cpp := $(wildcard *.cpp) 2 | srcs.c := $(wildcard *.c) 3 | objs := $(srcs.cpp:%.cpp=%.o) $(srcs.c:%.c=%.o) 4 | deps := $(srcs.cpp:%.cpp=%.d) $(srcs.c:%.c=%.d) 5 | execs := $(srcs.cpp:%.cpp=%) $(srcs.c:%.cpp=%) 6 | 7 | valgrind_home = /opt/valgrind-svn 8 | valgrind = $(valgrind_home)/bin/valgrind 9 | 10 | benchmark_params = RING_SIZE 11 | 12 | l3_macros = L3_ITERATIONS L3_QSIZE $(benchmark_params) 13 | 14 | macros = $(foreach m,$(l3_macros),$($m:%=-D$m=$($m))) 15 | 16 | opt_flag = -O3 17 | 18 | #defines = $(foreach p,$(benchmark_params),$(if $($p),-D$p=$($p))) 19 | 20 | # -pthread 21 | 22 | CPPFLAGS = -I../include -I $(valgrind_home)/include $(macros) 23 | CXXFLAGS = -g $(opt_flag) --std=c++11 -MMD -MP -MF $(<:%.cpp=%.d) -MT $@ 24 | CXX = clang++ 25 | 26 | 27 | rapidjson = rapidjson/include 28 | 29 | all: $(execs) 30 | 31 | test: $(execs:%=%.run) 32 | 33 | $(execs): %: %.o 34 | $(LINK.cc) $^ -o $@ 35 | 36 | clean: 37 | - rm -f $(execs) $(objs) $(deps) 38 | 39 | .PHONY: force_run 40 | 41 | run: $(execs:%=%.run) 42 | %.run: % force_run 43 | ./$< 44 | 45 | time: $(execs:%=%.time) 46 | %.time: % force_run 47 | bash -c "time ./$<" 48 | 49 | %.valgrind: % 50 | $(valgrind) --tool=callgrind --instr-atstart=no ./$< 51 | 52 | -include $(deps) 53 | -------------------------------------------------------------------------------- /examples/complex.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | // 31 | // We're going to set up the following topology. 32 | // 33 | // P1 34 | // \ 35 | // D1 - C1 36 | // / \ \ 37 | // P2 C2 - C3/P3 - D2 - C4 38 | // 39 | // Producers P1 and P2 feed into disruptor D1. Consumers C1 and C2 get 40 | // directly from the ring. Consumer C3 follows C1 and C2 and passes 41 | // the messages into the second disruptor D2. These are finally 42 | // consumed by C4. 43 | // 44 | // This might be a complex topology but we're not going to do anything 45 | // realistic with the messages. Still passing numbers around to make 46 | // it easy to test no messages are lost and order is preserved. 47 | // 48 | using Msg = size_t; 49 | // 50 | // Size of ring buffers as power of two. I've chosen this number 51 | // because 2^17 == 128K and 128K * sizeof(Msg) == 1MB. 52 | // 53 | constexpr size_t log2size = 17; 54 | // 55 | // First disruptor handles these messages. Third template parameter is 56 | // an instance tag. It defaults to void. I'm using the Tag utility 57 | // struct but you would most probably use a proper class to 58 | // disambiguate this. 59 | // 60 | using D1 = L3::Disruptor>; 61 | // 62 | // We must define Get types for our consumers first. C1 and C2 follow 63 | // the commit cursor in D1. This is the default. Need to supply 64 | // explicit instance number to C2 but I've give them for both to make 65 | // it explicit. 66 | // 67 | using Get1 = D1::Get>; 68 | using Get2 = D1::Get>; 69 | // 70 | // C3 follows C1 and C2. 71 | // 72 | using Get3 = D1::Get, L3::Barrier>; 73 | // 74 | // Define the Put type shared between P1 and P2. It follows C3. It 75 | // uses a shared commit policy. 76 | // 77 | using Put1 = D1::Put, L3::CommitPolicy::Shared>; 78 | // 79 | // Now define the second disruptor much like the first. 80 | // 81 | using D2 = L3::Disruptor>; 82 | // 83 | // It is single producer/single consumer which is what the default 84 | // Put/Get templates do. 85 | // 86 | using Get4 = D2::Get<>; 87 | using Put2 = D2::Put<>; 88 | // 89 | // That's it in terms of defining the disruptor. The rest of this 90 | // file is code that uses it. 91 | // 92 | // Going to produce sequences of numbers. P1 odd, P2 even. We reserve 93 | // 0 to as and end of stream message to shut things down. 94 | // 95 | constexpr Msg eos = 0; 96 | 97 | template 98 | inline void 99 | produce(Msg first, Msg last) 100 | { 101 | for(Msg i = first; i < last; i += 2) 102 | { 103 | Put() = i; 104 | } 105 | Put() = eos; 106 | } 107 | 108 | // 109 | // Consumers make sure sequence of messages is correct. 110 | // 111 | template 112 | inline void 113 | checkSequence() 114 | { 115 | Msg oldOdd = 1; 116 | Msg oldEven = 0; 117 | 118 | L3::CheckEOS checkEOS(2); 119 | L3::consume( 120 | checkEOS, 121 | [&](Msg msg) 122 | { 123 | // 124 | // Don't check sequence for eos event. 125 | // 126 | if(msg == eos) 127 | { 128 | return; 129 | } 130 | Msg& old = msg & 0x1L ? oldOdd : oldEven; 131 | if(msg != old + 2) 132 | { 133 | std::cout << "FAIL: old: " << old << ", new: " << msg 134 | << std::endl; 135 | } 136 | old = msg; 137 | }); 138 | } 139 | // 140 | // C3 acts as a bridge between D1 and D2. 141 | // 142 | inline void bridge() 143 | { 144 | L3::CheckEOS checkEOS(2); 145 | L3::consume(checkEOS, [](Msg msg){ Put2() = msg; }); 146 | } 147 | 148 | int 149 | main() 150 | { 151 | // 152 | // Stick it all together. 153 | // 154 | Msg max = 10000000; 155 | std::thread p1([&]{ produce(3, max); }); 156 | std::thread p2([&]{ produce(2, max); }); 157 | std::thread c1(checkSequence); 158 | std::thread c2(checkSequence); 159 | std::thread c3(bridge); 160 | std::thread c4(checkSequence); 161 | // 162 | // We love c++11. 163 | // 164 | for(auto t: {&p1, &p2, &c1, &c2, &c3, &c4}) 165 | { 166 | t->join(); 167 | } 168 | } 169 | 170 | -------------------------------------------------------------------------------- /examples/rules.mk: -------------------------------------------------------------------------------- 1 | complex.src = $(src)/complex.cpp 2 | $(call exec,complex) 3 | 4 | simple.src = $(src)/simple.cpp 5 | $(call exec,simple) 6 | 7 | two_to_one_multi_put.src = $(src)/two_to_one_multi_put.cpp 8 | $(call exec,two_to_one_multi_put) 9 | 10 | two_to_one_selector.src = $(src)/two_to_one_selector.cpp 11 | $(call exec,two_to_one_selector) 12 | -------------------------------------------------------------------------------- /examples/simple.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #ifndef RING_SIZE 33 | #define RING_SIZE (10) 34 | #endif 35 | 36 | using namespace std::chrono; 37 | 38 | using Clock = steady_clock; 39 | 40 | // 41 | // Our test message. 42 | // 43 | struct Msg 44 | { 45 | // 46 | // Write a sequence number to assert that we don't miss or reorder 47 | // messages. NB we're just doing this for test purposes. The 48 | // disruptor framework ensures this. 49 | // 50 | size_t sequence; 51 | // 52 | // We'll also pass a timestamp so we can measure latency. 53 | // 54 | Clock::time_point stamp; 55 | }; 56 | 57 | // 58 | // To gather stats. 59 | // 60 | struct Stat 61 | { 62 | size_t sequence; 63 | Clock::time_point start; 64 | Clock::time_point end; 65 | Clock::duration latency; 66 | }; 67 | // 68 | // Declare alias for a disruptor for this type of message and a ring 69 | // buffer of size 2^19 = 512K messages. 70 | // 71 | using D = L3::Disruptor; 72 | // 73 | // Alias Put and Get types using defaults. 74 | // 75 | using Get = D::Get<>; 76 | using Put = D::Put<>; 77 | 78 | using secs = duration; 79 | 80 | // 81 | // 82 | const size_t loops = 1 * std::mega::num; 83 | // 84 | // Store stats here. 85 | // 86 | Stat stats[loops]; 87 | 88 | 89 | template 90 | void 91 | processStats(Stat (&stats)[size], double time) 92 | { 93 | Clock::duration min{Clock::duration::max()}; 94 | Clock::duration max{0}; 95 | Clock::duration sum{0}; 96 | 97 | size_t count = 0; 98 | for(auto& s: stats) 99 | { 100 | // 101 | // Skip first ring worth of messages as these may have 102 | // been waiting on the queue for a while. 103 | // 104 | if(s.sequence < D::size) 105 | { 106 | continue; 107 | } 108 | count++; 109 | s.latency = s.end - s.start; 110 | if(s.latency < min) 111 | { 112 | min = s.latency; 113 | } 114 | if(s.latency > max) 115 | { 116 | max = s.latency; 117 | } 118 | sum += s.latency; 119 | } 120 | secs mean = duration_cast(sum) / count; 121 | 122 | // std::cout << "ringMsgs ringSize throughput messages mean min max std" << std::endl; 123 | 124 | std::cout << (1 << RING_SIZE) 125 | << " " << sizeof(D::Ring) 126 | << " " << (loops / time) / std::mega::num 127 | << " " << size 128 | << " " << duration_cast(mean).count() 129 | << " " << duration_cast(min).count() 130 | << " " << duration_cast(max).count(); 131 | 132 | secs deviationSum{0}; 133 | for(auto& s: stats) 134 | { 135 | secs tmp = duration_cast(s.latency - mean); 136 | deviationSum += secs(tmp.count() * tmp.count()); 137 | } 138 | secs stdDeviation = secs(std::sqrt(deviationSum.count() / size)); 139 | std::cout << " " 140 | << duration_cast(stdDeviation).count() 141 | << std::endl; 142 | } 143 | 144 | 145 | // 146 | // We've now created a very basic disruptor with a single producer and 147 | // consumer. We can use it like this... 148 | // 149 | int 150 | main() 151 | { 152 | // 153 | // To do a put we make an instance of the Put class we've just 154 | // defined. 155 | // 156 | { 157 | // 158 | // Creating a put claims a slot in the ring. 159 | // 160 | Put p; 161 | // 162 | // To write a value use assignment. 163 | // 164 | p = Msg{1, Clock::now()}; 165 | // 166 | // When p goes out of scope the put is commited and published 167 | // to any consumers. It's important to note that once you have 168 | // started a put it will always be commited. RAII takes care 169 | // of that. 170 | // 171 | } 172 | // 173 | // Since unnamed temporaries have the lifetime of the statement 174 | // they are created in we can shorten the above to: 175 | // 176 | Put() = Msg{2, Clock::now()}; 177 | // 178 | // To get we do something similar but gets are batched so we need 179 | // to read in a loop. 180 | // 181 | for(Msg m: Get()) 182 | { 183 | // std::cout << "got: " << m << std::endl; 184 | } 185 | // 186 | // Get is committed when the temporary goes out of scope. 187 | // 188 | 189 | // 190 | // We can send messages between two threads like so. 191 | // 192 | // We're going to send sequential integers. That way we can 193 | // confirm that we didn't loose any messages and they all came in 194 | // the right order. This is how we consume them. 195 | // 196 | std::thread consumer( 197 | [&]{ 198 | // 199 | // Consumer checks that these are read in the same order. 200 | // 201 | size_t previous = 0; 202 | 203 | for(auto s = std::begin(stats); s != std::end(stats);) 204 | { 205 | for(Msg m: Get()) 206 | { 207 | if(m.sequence != previous + 1) 208 | { 209 | // 210 | // Oops - something went wrong. 211 | // 212 | throw std::runtime_error("Bad sequence"); 213 | } 214 | previous = m.sequence; 215 | *s++ = {m.sequence, m.stamp, Clock::now()}; 216 | } 217 | } 218 | }); 219 | // 220 | // Measure the time. 221 | // 222 | L3::ScopedTimer<>::duration elapsedTime{0}; 223 | { 224 | L3::ScopedTimer<> timer(elapsedTime); 225 | // 226 | // Producer just writes sequencial integers. Start it in the 227 | // current thread. 228 | // 229 | for(size_t i = 1; i < loops + 1; ++i) Put() = Msg{i, Clock::now()}; 230 | // 231 | // Should get the same number of messages we put so this join 232 | // should work. 233 | // 234 | consumer.join(); 235 | } 236 | processStats(stats, duration_cast(elapsedTime).count()); 237 | 238 | return 0; 239 | } 240 | -------------------------------------------------------------------------------- /examples/two_to_one_multi_put.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | This is comparing performance two threads feeding a single consumer 25 | implemented as either a shared put or as two separate disruptors with 26 | a selector combining the streams. This version does the former. Time for 27 | 28 | 100,000,000 iterations 29 | 30 | real 0m15.527s 31 | user 0m29.391s 32 | sys 0m0.113s 33 | 34 | This is about 3x the time of the selector version. 35 | 36 | */ 37 | #include 38 | #include 39 | 40 | #include 41 | #include 42 | 43 | using Msg = size_t; 44 | using D = L3::Disruptor; 45 | using Get = D::Get<>; 46 | using Put = D::Put, L3::CommitPolicy::Shared>; 47 | 48 | int 49 | main() 50 | { 51 | struct Producer 52 | { 53 | Msg begin; 54 | Msg end; 55 | void operator()() { for(Msg i = begin; i < end; i += 2) Put() = i; } 56 | }; 57 | 58 | Msg iterations{100 * 1000 * 1000}; 59 | 60 | std::thread p1(Producer{3, iterations}); 61 | std::thread p2(Producer{2, iterations}); 62 | 63 | D::Msg oldOdd = 1; 64 | D::Msg oldEven = 0; 65 | for(size_t i = 2; i < iterations;) 66 | { 67 | for(auto msg: Get()) 68 | { 69 | ++i; 70 | D::Msg& old = msg & 0x1L ? oldOdd : oldEven; 71 | if(msg != old + 2) 72 | { 73 | std::cout << "old: " << old << ", new: " << msg 74 | << std::endl; 75 | return -1; 76 | } 77 | old = msg; 78 | } 79 | } 80 | std::cout << "Done" << std::endl; 81 | p1.join(); 82 | p2.join(); 83 | return 0; 84 | } 85 | 86 | 87 | -------------------------------------------------------------------------------- /examples/two_to_one_selector.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | This is comparing performance two threads feeding a single consumer 25 | implemented as either a shared put or as two separate disruptors with 26 | a selector combining the streams and forwarding this to a third 27 | disruptor. This version does the latter. Time for 28 | 29 | 100,000,000 iterations 30 | 31 | real 0m5.734s 32 | user 0m10.688s 33 | sys 0m0.048s 34 | 35 | with output disruptor 36 | 37 | real 0m22.028s 38 | user 0m41.619s 39 | sys 0m0.135s 40 | 41 | 42 | This is about 1/3 the time of the shared put version. 43 | 44 | */ 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | #include 52 | #include 53 | 54 | using Msg = size_t; 55 | 56 | struct In1 {}; 57 | struct In2 {}; 58 | struct Out {}; 59 | 60 | // 61 | // Note the shared put version uses 2^17 size ring. To compare like 62 | // for like since this test has two rings each is size 2^16. 63 | // 64 | template 65 | using D = L3::Disruptor; 66 | 67 | //using D1 = L3::Disruptor; 68 | using Put1 = D::Put<>; 69 | // L3::Barrier::Put<>>, 70 | // L3::CommitPolicy::Unique, 71 | // L3::SpinPolicy::Yield>; 72 | 73 | using Put2 = D::Put<>; 74 | // L3::Barrier::Put<>>, 75 | // L3::CommitPolicy::Unique, 76 | // L3::SpinPolicy::Yield>; 77 | 78 | using PutOut = D::Put<>; 79 | // L3::Barrier::Put<>>, 80 | // L3::CommitPolicy::Unique, 81 | // L3::SpinPolicy::Yield>; 82 | 83 | //using D2 = L3::Disruptor; 84 | 85 | const Msg eos{0}; 86 | 87 | #if 0 88 | struct Handler 89 | { 90 | static Msg oldOdd; 91 | static Msg oldEven; 92 | static Msg eosRemaining; 93 | 94 | void operator()(Msg& m) 95 | { 96 | Msg& old = m & 0x1L ? oldOdd : oldEven; 97 | if(m == eos) 98 | { 99 | --eosRemaining; 100 | return; 101 | } 102 | if(m != old + 2) 103 | { 104 | std::cout << "old: " << old << ", new: " << m 105 | << std::endl; 106 | } 107 | old = m; 108 | } 109 | }; 110 | 111 | Msg Handler::oldOdd{1}; 112 | Msg oldEven{2}; 113 | Msg eosRemaining{2}; 114 | 115 | #endif 116 | 117 | struct Handler 118 | { 119 | static Msg eosRemaining; 120 | 121 | void operator()(Msg& m) 122 | { 123 | if(m == eos) 124 | { 125 | --eosRemaining; 126 | } 127 | PutOut() = m; 128 | } 129 | }; 130 | 131 | Msg Handler::eosRemaining{2}; 132 | 133 | using S1 = L3::Selector::Get<>, Handler, 134 | D::Get<>, Handler>; 135 | 136 | template 137 | struct Producer 138 | { 139 | Msg begin; 140 | Msg end; 141 | void operator()() 142 | { 143 | for(Msg i = begin; i < end; i += 2) Put() = i; 144 | Put() = eos; 145 | } 146 | }; 147 | 148 | int 149 | main() 150 | { 151 | Msg iterations{100 * 1000 * 1000}; 152 | 153 | L3::ScopedTimer<>::duration testTime; 154 | { 155 | L3::ScopedTimer<> timer(testTime); 156 | std::thread p1(Producer{3, iterations}); 157 | std::thread p2(Producer{4, iterations}); 158 | std::thread bridge([]{ while(Handler::eosRemaining) S1::select(); }); 159 | 160 | Msg oldOdd = 1; 161 | Msg oldEven = 2; 162 | Msg eosRemaining = 2; 163 | 164 | while(eosRemaining) 165 | { 166 | for(auto m: D::Get<>()) 167 | { 168 | Msg& old = m & 0x1L ? oldOdd : oldEven; 169 | if(m == eos) 170 | { 171 | --eosRemaining; 172 | continue; 173 | } 174 | if(m != old + 2) 175 | { 176 | std::cout << "old: " << old << ", new: " << m 177 | << std::endl; 178 | } 179 | old = m; 180 | } 181 | } 182 | p1.join(); 183 | p2.join(); 184 | bridge.join(); 185 | } 186 | std::cout << "Done in " << testTime.count() << "us" << std::endl; 187 | return 0; 188 | } 189 | -------------------------------------------------------------------------------- /include/L3/non_static/disruptor.h: -------------------------------------------------------------------------------- 1 | #ifndef DISRUPTOR_H 2 | #define DISRUPTOR_H 3 | /* 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | namespace L3 // Low Latency Library 32 | { 33 | using Cursor = std::atomic; 34 | 35 | template 36 | class Barrier 37 | { 38 | const Cursor* const _cursors[size]; 39 | 40 | Index least() const 41 | { 42 | Index result = cursors[0]; 43 | for(auto c: cursors) 44 | { 45 | Index i = c.load(std::memory_order_acquire); 46 | if(i < result) 47 | { 48 | result = i; 49 | } 50 | } 51 | return result; 52 | } 53 | }; 54 | 55 | template 56 | class Consumer 57 | { 58 | L3_CACHE_LINE Cursor _cursor; 59 | const Barrier _barrier; 60 | 61 | Index cursor() const 62 | { 63 | // 64 | // When we read our cursor we know that only us - this 65 | // thread - can have modified the value therefore relaxed 66 | // memrory order is sufficient. 67 | // 68 | return cursor.load(std::memory_order_relaxed); 69 | } 70 | 71 | Index claim(Index begin) 72 | { 73 | // 74 | // The cursor is the start of a dependency chain 75 | // leading to reading a message from the ring 76 | // buffer. Therefore consume semantics are sufficient to 77 | // ensure synchronisation. 78 | // 79 | Index end; 80 | SpinPolicy sp; 81 | while((end = _barrier.least()) <= begin) 82 | { 83 | sp(); 84 | } 85 | return end; 86 | } 87 | 88 | void cursor(Index end) 89 | { 90 | // 91 | // When writing to our cursor we have to ensure that the 92 | // new value is released to other threads to aquire. 93 | // 94 | cursor.store(end, std::memory_order_release); 95 | } 96 | 97 | public: 98 | friend class Get; 99 | 100 | // 101 | // RAII semantics to perform a get. Ensures that we always 102 | // commit. 103 | // 104 | class Get 105 | { 106 | Get(const Consumer& consumer): 107 | // 108 | // Only a single thread should be modifying the read cursor. 109 | // 110 | _begin{consumer.cursor.load(std::memory_order_relaxed)}, 111 | _end{claim(_begin)} 112 | {} 113 | 114 | Get(size_t maxBatchSize): 115 | _begin{cursor.load(std::memory_order_relaxed)}, 116 | _end{std::min(claim(_begin), _begin + maxBatchSize)} 117 | { 118 | } 119 | 120 | enum NoBlock { noBlock }; 121 | Get(size_t maxBatchSize, NoBlock): 122 | _begin(cursor.load(std::memory_order_relaxed)), 123 | _end(std::min(Barrier::least(), _begin + maxBatchSize)) 124 | { 125 | } 126 | 127 | Get(NoBlock): 128 | _begin(cursor.load(std::memory_order_relaxed)), 129 | _end(Barrier::least()) 130 | {} 131 | 132 | ~Get() 133 | { 134 | if(_begin != _end) 135 | { 136 | cursor.store(_end, std::memory_order_release); 137 | } 138 | } 139 | 140 | using Iterator = T*; 141 | Iterator begin() const { return _begin; } 142 | Iterator end() const { return _end; } 143 | 144 | L3_CACHE_LINE static L3::Sequence cursor; 145 | 146 | private: 147 | Iterator _begin; 148 | Iterator _end; 149 | 150 | Get(Index b, Index e): 151 | _begin(b), 152 | _end(e) 153 | {} 154 | 155 | static Index claim(Index begin) 156 | { 157 | // 158 | // The cursor is the start of a dependency chain 159 | // leading to reading a message from the ring 160 | // buffer. Therefore consume semantics are sufficient to 161 | // ensure synchronisation. 162 | // 163 | Index end; 164 | SpinPolicy sp; 165 | while((end = Barrier::least()) <= begin) 166 | { 167 | sp(); 168 | } 169 | return end; 170 | } 171 | }; 172 | }; 173 | 174 | // 175 | // Disruptor containing buffer of size Msg objects. Getters is the 176 | // number of consumers. 177 | // 178 | template 179 | class Disruptor 180 | { 181 | public: 182 | using Ring = L3::Ring, size>; 183 | using Iterator = typename Ring::template Iterator; 184 | 185 | Ring, size> _ring; 186 | // 187 | // Allow one extra space for the write cursor. 188 | // 189 | Barrier getters; 190 | 191 | Cursor cursor; 192 | }; 193 | 194 | template 195 | class Get 196 | { 197 | public: 198 | 199 | Cursor& cursor(); 200 | Get(Disruptor& ): 201 | _begin{ 202 | 203 | Msg* _begin; 204 | Msg* _end; 205 | 206 | 207 | }; 208 | } 209 | 210 | #endif 211 | -------------------------------------------------------------------------------- /include/L3/static/README: -------------------------------------------------------------------------------- 1 | Disruptor framework where everything is statically allocated and all 2 | the addresses of all objects are compile time constants. The idea is 3 | to give the optimiser the very best chance of generating the tightest 4 | code by using a little tiny bit of template meta programming. At least 5 | that's the plan. In most cases where the target is a shared library 6 | this isn't going to make much of a difference since all the symbols 7 | will be relocatable anyway. However it may have applications in 8 | embedded systems and it was a fun exercsie to write. 9 | -------------------------------------------------------------------------------- /include/L3/static/barrier.h: -------------------------------------------------------------------------------- 1 | #ifndef BARRIER_H 2 | #define BARRIER_H 3 | /* 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | #include "sequence.h" 28 | 29 | namespace L3 30 | { 31 | template struct Barrier; 32 | 33 | template 34 | struct Barrier 35 | { 36 | static constexpr Index least() 37 | { 38 | return T::cursor.load(std::memory_order_acquire); 39 | } 40 | }; 41 | 42 | template 43 | struct Barrier: Barrier 44 | { 45 | static constexpr Index least() 46 | { 47 | return std::min( 48 | Head::cursor.load(std::memory_order_acquire), 49 | Barrier::least()); 50 | } 51 | }; 52 | } 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /include/L3/static/consume.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSUME_H 2 | #define CONSUME_H 3 | 4 | namespace L3 5 | { 6 | // 7 | // General purpose consumer loop. 8 | // 9 | template 10 | inline void 11 | consume(EOS& checkEOS, const F& f) 12 | { 13 | for(;;) 14 | { 15 | for(auto msg: Get()) 16 | { 17 | f(msg); 18 | 19 | if(checkEOS(msg)) 20 | { 21 | return; 22 | } 23 | } 24 | } 25 | } 26 | 27 | template 28 | struct CheckEOS 29 | { 30 | CheckEOS(size_t n): _remaining(n) {} 31 | 32 | bool operator()(Msg m) { return m == eos && !--_remaining; } 33 | size_t _remaining; 34 | }; 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /include/L3/static/disruptor.h: -------------------------------------------------------------------------------- 1 | #ifndef DISRUPTOR_H 2 | #define DISRUPTOR_H 3 | /* 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | #include "barrier.h" 28 | #include "get.h" 29 | #include "put.h" 30 | #include "sequence.h" 31 | 32 | #include 33 | #include 34 | 35 | namespace L3 // Low Latency Library 36 | { 37 | template 38 | struct Disruptor 39 | { 40 | using DISRUPTOR = Disruptor; 41 | using Msg = T; 42 | using Tag = TAG; 43 | using Ring = L3::Ring, s>; 44 | static const size_t size = Ring::size; 45 | L3_CACHE_LINE static Ring ring; 46 | 47 | using Iterator = typename Ring::template StaticIterator; 48 | 49 | L3_CACHE_LINE static L3::Sequence cursor; 50 | 51 | template, 53 | typename SpinPolicy=NoOp> 54 | using Get = Get; 55 | 56 | template>, 57 | typename COMMITPOLICY=CommitPolicy::Unique, 58 | typename ClaimSpinPolicy=NoOp, 59 | typename CommitSpinPolicy=NoOp> 60 | using Put = L3::Put; 65 | }; 66 | 67 | template 68 | L3_CACHE_LINE typename Disruptor::Ring 69 | Disruptor::ring; 70 | 71 | template 72 | L3_CACHE_LINE L3::Sequence 73 | Disruptor::cursor{Disruptor::Ring::size}; 74 | } 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /include/L3/static/get.h: -------------------------------------------------------------------------------- 1 | #ifndef GET_H 2 | #define GET_H 3 | /* 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | #include "sequence.h" 28 | 29 | #include 30 | 31 | #include 32 | 33 | namespace L3 34 | { 35 | template 39 | struct Get 40 | { 41 | Get(): 42 | // 43 | // Only a single thread should be modifying the read cursor. 44 | // 45 | _begin{cursor.load(std::memory_order_relaxed)}, 46 | _end{claim(_begin)} 47 | {} 48 | 49 | Get(size_t maxBatchSize): 50 | _begin{cursor.load(std::memory_order_relaxed)}, 51 | _end{std::min(claim(_begin), _begin + maxBatchSize)} 52 | { 53 | } 54 | 55 | enum NoBlock { noBlock }; 56 | Get(size_t maxBatchSize, NoBlock): 57 | _begin(cursor.load(std::memory_order_relaxed)), 58 | _end(std::min(Barrier::least(), _begin + maxBatchSize)) 59 | { 60 | } 61 | 62 | Get(NoBlock): 63 | _begin(cursor.load(std::memory_order_relaxed)), 64 | _end(Barrier::least()) 65 | {} 66 | 67 | ~Get() 68 | { 69 | if(_begin != _end) 70 | { 71 | cursor.store(_end, std::memory_order_release); 72 | } 73 | } 74 | 75 | using Iterator = typename Disruptor::Iterator; 76 | Iterator begin() const { return _begin; } 77 | Iterator end() const { return _end; } 78 | 79 | L3_CACHE_LINE static L3::Sequence cursor; 80 | 81 | private: 82 | Iterator _begin; 83 | Iterator _end; 84 | 85 | Get(Index b, Index e): 86 | _begin(b), 87 | _end(e) 88 | {} 89 | 90 | static Index claim(Index begin) 91 | { 92 | // 93 | // The cursor is the start of a dependency chain 94 | // leading to reading a message from the ring 95 | // buffer. Therefore consume semantics are sufficient to 96 | // ensure synchronisation. 97 | // 98 | Index end; 99 | SpinPolicy sp; 100 | while((end = Barrier::least()) <= begin) 101 | { 102 | sp(); 103 | } 104 | return end; 105 | } 106 | }; 107 | 108 | template 112 | L3_CACHE_LINE L3::Sequence 113 | Get::cursor{Disruptor::size}; 114 | } 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /include/L3/static/logger.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGGER_H 2 | #define LOGGER_H 3 | /* 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | namespace L3 // Low Latency Library 34 | { 35 | class Logger 36 | { 37 | using Buffer = Disruptor; 38 | using Put = Buffer::Put>, CommitPolicy::Shared>; 39 | 40 | public: 41 | Logger(): os() {} 42 | ~Logger() 43 | { 44 | Put() = std::move(os.str()); 45 | } 46 | 47 | template 48 | friend std::ostream& operator<<(Logger&& l, const T& rhs) 49 | { 50 | l.os << rhs; 51 | return l.os; 52 | } 53 | 54 | struct Writer 55 | { 56 | Writer(std::ostream* os = &std::cout): m_os(os), m_running(true) {} 57 | 58 | void operator()() const 59 | { 60 | std::ostream_iterator oi(*m_os, "\n"); 61 | 62 | while(m_running) 63 | { 64 | Buffer::Get<> g; 65 | std::copy(g.begin(), g.end(), oi); 66 | } 67 | } 68 | std::thread start() 69 | { 70 | return std::thread([=]{ (*this)(); }); 71 | } 72 | void stop() { Logger() << "stoping logger"; m_running = false; } 73 | private: 74 | std::ostream* const m_os; 75 | std::atomic m_running; 76 | }; 77 | protected: 78 | std::ostringstream os; 79 | }; 80 | } 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /include/L3/static/put.h: -------------------------------------------------------------------------------- 1 | #ifndef PUT_H 2 | #define PUT_H 3 | /* 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | #include "sequence.h" 28 | //#include "barrier.h" 29 | 30 | #include 31 | #include 32 | 33 | namespace L3 34 | { 35 | namespace CommitPolicy 36 | { 37 | struct Unique 38 | { 39 | template 40 | bool operator()(Cursor&, Index) { return false; } 41 | static constexpr std::memory_order order{std::memory_order_relaxed}; 42 | }; 43 | 44 | struct Shared 45 | { 46 | template 47 | bool operator()(Cursor& commitCursor, Index slot) 48 | { 49 | // 50 | // For multiple producers it's possible that we've 51 | // claimed a slot while another producer is doing a 52 | // put. If the other producers have not yet committed 53 | // then we must wait for them. When cursor is 1 less 54 | // than _slot we know that we are next to commit. 55 | // 56 | return commitCursor.load(std::memory_order_acquire) < slot; 57 | } 58 | static constexpr std::memory_order order{std::memory_order_consume}; 59 | }; 60 | } 61 | 62 | template< 63 | typename Disruptor, 64 | typename Barrier, 65 | typename CommitPolicy, 66 | typename ClaimSpinPolicy=NoOp, 67 | typename CommitSpinPolicy=NoOp> 68 | struct Put 69 | { 70 | Put(): _slot(claim()) {} 71 | ~Put() { commit(_slot); } 72 | 73 | template 74 | Put& operator=(const T& rhs) { *_slot = rhs; return *this; } 75 | 76 | template 77 | Put& operator=(const T&& rhs) { *_slot = rhs; return *this; } 78 | 79 | L3_CACHE_LINE static L3::Sequence cursor; 80 | 81 | private: 82 | using Iterator = typename Disruptor::Iterator; 83 | const Iterator _slot; 84 | 85 | // 86 | // Claim 1 slot and block if we can't. 87 | // 88 | Index claim() 89 | { 90 | // 91 | // Claim a slot. Memory order depends on whether producer 92 | // is shared. 93 | // 94 | Index slot = cursor.fetch_add(1, CommitPolicy::order); 95 | // 96 | // We have our slot but we cannot write to it until we 97 | // are sure we will not overwrite data that has not 98 | // yet been consumed. In other words _head cannot lap 99 | // _tail around the ring. 100 | // 101 | // To prevent _head catching up with _tail we must 102 | // ensure that the gap between _head and _tail is never 103 | // greater than size. Or 104 | // 105 | // _tail > _head - size 106 | // 107 | // We spin while this is not the case. 108 | // 109 | // To optimise the loop condition precompute _head - 110 | // size. To ensure that this difference is always positive 111 | // initialises _head and _tail using size as a baseline - 112 | // ie as if we have already done one lap around the ring. 113 | // 114 | Index wrapAt = slot - Disruptor::size; 115 | // 116 | // We only need to know that this relation is satisfied at 117 | // this point. Since _tail monotonically increases the 118 | // condition cannot be invalidated by consumers. 119 | // 120 | ClaimSpinPolicy sp; 121 | while(Barrier::least() <= wrapAt) 122 | { 123 | sp(); 124 | } 125 | return slot; 126 | } 127 | 128 | // 129 | // Claim up to batchSize. Don't block. Only works for unique 130 | // producer. 131 | // 132 | Index claim(size_t batchSize) 133 | { 134 | Index begin = cursor.load(std::memory_order_relaxed); 135 | Index end = std::min(begin + batchSize, Barrier::least() - 1); 136 | 137 | cursor.store(end, std::memory_order_relaxed); 138 | } 139 | 140 | void commit(Index slot) 141 | { 142 | // 143 | // For multiple producers it's possible that we've claimed a 144 | // slot while another producer is doing a put. If the other 145 | // producers have not yet committed then we must wait for 146 | // them. When cursor is 1 less than _slot we know that we are 147 | // next to commit. 148 | // 149 | CommitSpinPolicy sp; 150 | CommitPolicy shouldSpin; 151 | while(shouldSpin(Disruptor::cursor, slot)) 152 | { 153 | sp(); 154 | } 155 | // 156 | // No need to CAS. Only we could have been waiting for 157 | // this particular cursor value. 158 | // 159 | Disruptor::cursor.fetch_add(1, std::memory_order_release); 160 | } 161 | }; 162 | 163 | template 168 | L3_CACHE_LINE L3::Sequence 169 | Put::cursor{Disruptor::size}; 174 | } 175 | 176 | #endif 177 | -------------------------------------------------------------------------------- /include/L3/static/selector.h: -------------------------------------------------------------------------------- 1 | #ifndef SELECTOR_H 2 | #define SELECTOR_H 3 | 4 | namespace L3 5 | { 6 | template 7 | struct Selector 8 | { 9 | static void select() {} 10 | }; 11 | 12 | template 13 | struct Selector: Selector 14 | { 15 | static void select() 16 | { 17 | F f; 18 | for(auto& m: Get(Get::noBlock)) 19 | { 20 | f(m); 21 | } 22 | Selector::select(); 23 | } 24 | }; 25 | 26 | #if 1 27 | template 28 | struct Handler //: public std::function 29 | { 30 | F f; 31 | using Get = G; 32 | }; 33 | 34 | template 35 | inline Handler makeHandler(const F& f) 36 | { 37 | return Handler{f}; 38 | } 39 | #endif 40 | 41 | template void select(size_t, Tail&...); 42 | 43 | template 44 | inline void select(size_t batchSize, Handler& h) 45 | { 46 | using Get = typename Handler::Get; 47 | for(auto& m: Get(batchSize, Get::noBlock)) 48 | { 49 | h(m); 50 | } 51 | } 52 | 53 | template 54 | inline void select(size_t batchSize, Handler& h, Tail&... tail) 55 | { 56 | select(batchSize, h); 57 | select(batchSize, tail...); 58 | } 59 | } 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /include/L3/static/sequence.h: -------------------------------------------------------------------------------- 1 | #ifndef SEQUENCE_H 2 | #define SEQUENCE_H 3 | /* 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | namespace L3 34 | { 35 | template struct Get; 36 | template struct Put; 37 | template struct Barrier; 38 | 39 | namespace CommitPolicy { struct Shared; } 40 | 41 | class Sequence: std::atomic 42 | { 43 | template 44 | friend struct Get; 45 | template 46 | friend struct Put; 47 | template 48 | friend struct Barrier; 49 | 50 | friend struct CommitPolicy::Shared; 51 | 52 | public: 53 | using std::atomic::operator Index; 54 | Sequence(): std::atomic{} {} 55 | Sequence(Index i): std::atomic{i} {} 56 | }; 57 | 58 | } 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /include/L3/static/spinpolicy.h: -------------------------------------------------------------------------------- 1 | #ifndef SPINPOLICY_H 2 | #define SPINPOLICY_H 3 | 4 | #include 5 | 6 | namespace L3 7 | { 8 | namespace SpinPolicy 9 | { 10 | struct Yield 11 | { 12 | void operator()() const 13 | { 14 | std::this_thread::yield(); 15 | } 16 | }; 17 | } 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /include/L3/util/README: -------------------------------------------------------------------------------- 1 | Some notes of lock free techniques using counters and ring buffers. 2 | 3 | 4 | Counters 5 | 6 | 2^64 is a very big number. If you increment a 64 bit counter 7 | continuously a billion times a second it will take over 500 years for 8 | the value to wrap. 64 bit counters have such a great range that in 9 | practical terms they can be used as an infinite non-decreasing 10 | sequence. We can progress through the sequence using fast, atomic 11 | increment operations. 12 | 13 | The non-decreasing property is very useful when creating lock free 14 | data structures. We know that once a point in the sequence reached it 15 | will never be repeated - the sequence will never go backwards. Because 16 | of this boundary conditions can be expressed with atomic comparisons. 17 | 18 | We can implement a ring buffer using 64 bit counter to index into a 19 | fixed size array by using 20 | 21 | index = counter % size 22 | 23 | If we make size a power of two then the mod operation simplifies to 24 | bitwise and of the log2(size) lower order bits of counter. 25 | 26 | 27 | Queues 28 | 29 | We can think of the queue as an infinite series of slots addressed by 30 | head and tail cursors. We write to head and read from tail. A ring 31 | buffer represents a sliding window of physical slots moving along this 32 | series. 33 | 34 | ^ 35 | | 36 | | 37 | R <- head 38 | I 39 | N 40 | G <- tail 41 | | 42 | | 43 | ^ 44 | 45 | Head and tail are constrained as follows. Firstly tail cannot catch up 46 | with head. ie 47 | 48 | tail < head 49 | 50 | Secondly head cannot get so far ahead of tail that it wraps around the 51 | ring. Or in other words. 52 | 53 | head < tail + size 54 | 55 | 56 | If head is at the front of the window and tail at the end then head 57 | cannot advance without wrapping round and overwriting tail. Or in 58 | other words 59 | 60 | head - size <= tail 61 | 62 | We don't want to have to worry about head - size being negative. This 63 | is the reason why we initialise the cursors as if they had already 64 | been around the ring once. 65 | 66 | 67 | Claim and Commit 68 | 69 | Two key concepts 70 | 71 | 72 | 73 | To commit a put we advance the cursor to show that slot is now 74 | available for reading. 75 | 76 | If there are multiple producers it's possible that we've claimed a 77 | slot while another producer is doing a put. If the other producers 78 | have not yet commited then we must wait for them. When cursor is 1 79 | less than _slot we know that we are next to commit. 80 | 81 | For single producer no other thread could have commited puts. So this 82 | code would be unnecessary. 83 | 84 | Multi Consume 85 | 86 | t1 claims 87 | t2 claims 88 | 89 | t2 tries commit, cannot t1 must commit first, pushes commit. 90 | 91 | 92 | -------------------------------------------------------------------------------- /include/L3/util/allocator.h: -------------------------------------------------------------------------------- 1 | #ifndef ALLOCATOR_H 2 | #define ALLOCATOR_H 3 | 4 | #include "ring.h" 5 | 6 | #include 7 | 8 | namespace L3 9 | { 10 | template 11 | struct Slot 12 | { 13 | alignas(T) char _space[sizeof(T)]; 14 | std::atomic _inUse; 15 | }; 16 | 17 | template 18 | struct Allocator 19 | { 20 | using value_type = T; 21 | using pointer = T*; 22 | using reference = T&; 23 | using const_pointer = const T*; 24 | using const_reference = const T&; 25 | using size_type = size_t; 26 | using difference_type = ptrdiff_t; 27 | 28 | pointer allocate(size_type n, const_pointer hint=0) 29 | { 30 | size_t result; 31 | // 32 | // This could spin forever if allocator is full. 33 | // 34 | while(_ring[result = _nextFree++]._inUse); 35 | return reinterpret_cast(&_ring[result]._space); 36 | } 37 | 38 | Ring, log2size> _ring; 39 | using Idx = Ring, log2size>::Idx<_ring>; 40 | std::atomic, log2size>::Index> _nextFree; 41 | }; 42 | } 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /include/L3/util/cacheline.h: -------------------------------------------------------------------------------- 1 | #ifndef CACHELINE_H 2 | #define CACHELINE_H 3 | 4 | #include 5 | #include 6 | 7 | #ifndef L3_CACHE_LINE_SIZE 8 | # define L3_CACHE_LINE_SIZE 64 9 | #endif 10 | 11 | namespace L3 // Low Latency Library 12 | { 13 | static constexpr size_t cache_line_size = L3_CACHE_LINE_SIZE; 14 | 15 | #ifndef L3_CACHE_LINE 16 | # define L3_CACHE_LINE alignas(L3::cache_line_size) 17 | #endif 18 | 19 | template 20 | struct CacheLineImpl; 21 | 22 | template 23 | struct CacheLineImpl: T 24 | { 25 | using T::operator=; 26 | // CacheLineImpl& operator=(const T& rhs) 27 | // { 28 | // T::operator=(rhs); 29 | // return *this; 30 | // } 31 | 32 | // CacheLineImpl& operator=(T&& rhs) 33 | // { 34 | // T::operator=(rhs); 35 | // return *this; 36 | // } 37 | }; 38 | 39 | template 40 | struct CacheLineImpl 41 | { 42 | T value; 43 | 44 | CacheLineImpl& operator=(const T& rhs) 45 | { 46 | value = rhs; 47 | return *this; 48 | } 49 | 50 | CacheLineImpl& operator=(T&& rhs) 51 | { 52 | value = rhs; 53 | return *this; 54 | } 55 | 56 | operator T&() { return value; } 57 | operator const T&() const { return value; } 58 | }; 59 | 60 | template 61 | struct L3_CACHE_LINE CacheLine: CacheLineImpl::value> 62 | { 63 | CacheLine& operator=(const T& rhs) 64 | { 65 | CacheLineImpl::value>::operator=(rhs); 66 | return *this; 67 | } 68 | 69 | CacheLine& operator=(T&& rhs) 70 | { 71 | CacheLineImpl::value>::operator=(rhs); 72 | return *this; 73 | } 74 | }; 75 | } 76 | 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /include/L3/util/counter.h: -------------------------------------------------------------------------------- 1 | #ifndef COUNTER_H 2 | #define COUNTER_H 3 | 4 | #include 5 | 6 | namespace L3 7 | { 8 | template 9 | struct Counter 10 | { 11 | void operator()() const { ++counter; } 12 | }; 13 | } 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /include/L3/util/fifo.h: -------------------------------------------------------------------------------- 1 | #ifndef FIFO_H 2 | #define FIFO_H 3 | /* 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /******************************************************************************* 28 | * 29 | * Fifo is a simple lock free queue. It's constructed using the same 30 | * techniques as the Disruptor pattern but lacks its pluggability. As 31 | * such it's a good introduction to how the disruptor works without 32 | * getting bogged down in the template code involved in separating out 33 | * the producers from the consumers complete with the difference 34 | * waiting strategies. 35 | * 36 | ******************************************************************************/ 37 | 38 | #include "cacheline.h" 39 | #include "ring.h" 40 | #include "types.h" 41 | 42 | namespace L3 // Low Latency Library 43 | { 44 | template 45 | class Fifo 46 | { 47 | // 48 | // A valid ring buffer needs the cursor position to be 49 | // different from the head position. Therefore the minimum 50 | // size is 2 or 2^1. 51 | // 52 | static_assert(log2size > 0, "Minimun ring size is 2"); 53 | // 54 | // In the implementation of a disruptor there's a window 55 | // between a slot has been claimed and the cursor advanced 56 | // where the value must be copied into the ring buffer. If 57 | // this operation fails and the cursor is not advanced then 58 | // the whole system will block. To ensure the value copy 59 | // cannot fail constrain T to being a POD. 60 | // 61 | static_assert(std::is_pod::value, "PODs only"); 62 | 63 | public: 64 | using Ring = Ring; 65 | using value_type = T; 66 | 67 | static constexpr Index size = Ring::size; 68 | 69 | L3_CACHE_LINE Ring _ring; 70 | // 71 | // See Put::claim for why initial values are based on size. 72 | // 73 | // The next put position. 74 | // 75 | L3_CACHE_LINE std::atomic _head{size + 1}; 76 | // 77 | // The next get. 78 | // 79 | L3_CACHE_LINE std::atomic _tail{size + 1}; 80 | // 81 | // The latest commited put. 82 | // 83 | L3_CACHE_LINE std::atomic _cursor{size}; 84 | 85 | template 86 | Index claimHead(SpinProbe spinProbe=SpinProbe()) 87 | { 88 | // 89 | // Claim a slot. For the single producer case we don't 90 | // care about memory order because only this thread can 91 | // write to _head. More generally if there are multiple 92 | // threads putting to head then we must ensure that this 93 | // increment is synchronised. 94 | // 95 | Index slot = _head.fetch_add(1, std::memory_order_acquire); 96 | // 97 | // We have our slot but we cannot write to it until we 98 | // are sure we will not overwrite data that has not 99 | // yet been consumed. In other words _head cannot lap 100 | // _tail around the ring. 101 | // 102 | // To prevent _head catching up with _tail we must 103 | // ensure that the gap between _head and _tail is never 104 | // greater than size. Or 105 | // 106 | // _tail <= _head - size 107 | // 108 | // We spin while this is not the case. 109 | // 110 | // To optimise the loop condition precompiute _head - 111 | // size. To ensure that this difference is always 112 | // positive the Disruptor ctor initialises _head and 113 | // _tail using size as a baseline - ie as if we have 114 | // already done one lap around the ring. 115 | // 116 | Index wrapAt = slot - size; 117 | // 118 | // Must ensure that _tail is synchronised with writes 119 | // from consumers. We only need to know that this 120 | // relation is satisfied at this point. Since _tail 121 | // monotonically increases the condition cannot be 122 | // invalidated by consumers. 123 | // 124 | while(_tail.load(std::memory_order_acquire) <= wrapAt) 125 | { 126 | spinProbe(); 127 | } 128 | return slot; 129 | } 130 | 131 | template 132 | void commitHead(Index slot, SpinProbe spinProbe=SpinProbe()) 133 | { 134 | // 135 | // To commit a put we advance the cursor to show that 136 | // slot is now available for reading. 137 | // 138 | // If there are multiple producers it's possible that 139 | // we've claimed a slot while another producer is 140 | // doing a put. If the other producers have not yet 141 | // commited then we must wait for them. When cursor is 142 | // 1 less than _slot we know that we are next to 143 | // commit. 144 | // 145 | --slot; 146 | // 147 | // For single producer no other thread could have 148 | // commited puts. So this code would be unnecessary. 149 | // 150 | while(_cursor.load(std::memory_order_acquire) < slot) 151 | { 152 | spinProbe(); 153 | } 154 | // 155 | // No need to CAS. Only we could have been waiting for 156 | // this particular cursor value. 157 | // 158 | _cursor.fetch_add(1, std::memory_order_release); 159 | } 160 | 161 | template 162 | class Put 163 | { 164 | Fifo& _fifo; 165 | Index _slot; 166 | public: 167 | Put(Fifo& fifo): _fifo(fifo), _slot(fifo.claimHead(ClaimProbe())) {} 168 | ~Put() { _fifo.commitHead(_slot, CommitProbe()); } 169 | 170 | value_type& operator=(const T& rhs) const 171 | { return _fifo._ring[_slot] = rhs; } 172 | 173 | value_type& operator=(const T&& rhs) const 174 | { return _fifo._ring[_slot] = rhs; } 175 | 176 | operator value_type&() const 177 | { return _fifo.ring[_slot]; } 178 | 179 | operator value_type&&() const 180 | { return _fifo.ring[_slot]; } 181 | }; 182 | 183 | void put(const T& rhs) 184 | { 185 | Put<>(*this) = rhs; 186 | } 187 | 188 | template 189 | Index claimTail(SpinProbe spinProbe=SpinProbe()) 190 | { 191 | Index slot = _tail.load(std::memory_order_acquire); 192 | while(_cursor.load(std::memory_order_acquire) < slot) 193 | { 194 | spinProbe(); 195 | } 196 | return slot; 197 | } 198 | 199 | void commitTail() 200 | { 201 | _tail.fetch_add(1, std::memory_order_release); 202 | } 203 | 204 | template 205 | class Get 206 | { 207 | Fifo& _fifo; 208 | Index _slot; 209 | 210 | public: 211 | Get(Fifo& fifo): _fifo(fifo), _slot(fifo.claimTail(ClaimProbe())) {} 212 | ~Get() { _fifo.commitTail(); } 213 | 214 | operator const value_type&() const { return _fifo._ring[_slot]; } 215 | }; 216 | 217 | T get() 218 | { 219 | return Get<>(*this); 220 | } 221 | }; 222 | } 223 | 224 | #endif 225 | -------------------------------------------------------------------------------- /include/L3/util/flexififo.h: -------------------------------------------------------------------------------- 1 | #ifndef FLEXIFIFO_H 2 | #define FLEXIFIFO_H 3 | /* 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /******************************************************************************* 28 | * 29 | * FlexiFifo is pluggable lock free queue. The ends are pluggable to 30 | * support 1 => 1, n => 1, 1 => n and n => n patterns. 31 | * 32 | ******************************************************************************/ 33 | 34 | #include "cacheline.h" 35 | #include "ring.h" 36 | #include "types.h" 37 | 38 | #include 39 | #include 40 | 41 | namespace L3 // Low Latency Library 42 | { 43 | template 44 | struct UniqueHead 45 | { 46 | static constexpr size_t size = Ring::size; 47 | L3_CACHE_LINE Counter _claim{size}; 48 | L3_CACHE_LINE Counter _head{size}; 49 | 50 | Index claim(Counter& tail) 51 | { 52 | Index slot = _claim.fetch_add(1, std::memory_order_relaxed); 53 | Index min = slot - size; 54 | while(tail.load(std::memory_order_acquire) <= min); 55 | return slot; 56 | } 57 | void commit(Index){ _head.fetch_add(1, std::memory_order_release); } 58 | }; 59 | 60 | template 61 | struct SharedHead 62 | { 63 | static constexpr size_t size = Ring::size; 64 | L3_CACHE_LINE Counter _claim{size}; 65 | L3_CACHE_LINE Counter _head{size}; 66 | 67 | Index claim(Counter& tail) 68 | { 69 | Index slot = _claim.fetch_add(1, std::memory_order_acquire); 70 | Index min = slot - size; 71 | while(tail.load(std::memory_order_acquire) <= min); 72 | return slot; 73 | } 74 | void commit(Index slot) 75 | { 76 | // 77 | // Wait until it's our turn to commit. 78 | // 79 | while(_head.load(std::memory_order_acquire) < slot); 80 | _head.fetch_add(1, std::memory_order_release); 81 | } 82 | }; 83 | 84 | template 85 | struct UniqueTail 86 | { 87 | static constexpr size_t size = Ring::size; 88 | L3_CACHE_LINE Counter _tail{size}; 89 | 90 | template 91 | Index claim(Counter& head) 92 | { 93 | Index slot = _tail.load(std::memory_order_acquire); 94 | while(head.load(std::memory_order_acquire) <= slot); 95 | return slot; 96 | } 97 | 98 | void commitTail() 99 | { 100 | _tail.fetch_add(1, std::memory_order_release); 101 | } 102 | }; 103 | 104 | template 105 | struct SharedTail 106 | { 107 | static constexpr size_t size = Ring::size; 108 | L3_CACHE_LINE Counter _claim{size}; 109 | L3_CACHE_LINE Counter _tail{size}; 110 | 111 | Index claim(Counter& head) 112 | { 113 | Index slot = _claim.fetch_add(1, std::memory_order_acq_rel); 114 | while(head.load(std::memory_order_acquire) <= slot); 115 | return slot; 116 | } 117 | 118 | void commit(Index slot) 119 | { 120 | // 121 | // Wait until it's our turn to commit. 122 | // 123 | while(_tail.load(std::memory_order_acquire) < slot - 1); 124 | _tail.fetch_add(1, std::memory_order_release); 125 | } 126 | 127 | bool tryCommit(Index slot) 128 | { 129 | if(_tail.load(std::memory_order_acquire) == slot - 1) 130 | { 131 | _tail.fetch_add(1, std::memory_order_release); 132 | return true; 133 | } 134 | return false; 135 | } 136 | }; 137 | 138 | template class Head, 140 | template class Tail> 141 | class FlexiFifo: Ring 142 | { 143 | Head _head; 144 | Tail _tail; 145 | public: 146 | Index claimHead() { return _head.claim(_tail._tail); } 147 | void commitHead(Index slot) { _head.commit(slot); } 148 | 149 | Index claimTail() { return _tail.claim(_head._head); } 150 | void commitTail(Index slot) { _tail.commit(slot); } 151 | bool tryCommitTail(Index slot) { return _tail.tryCommit(slot); } 152 | }; 153 | 154 | /** 155 | * Simple single threaded queue. 156 | */ 157 | template 158 | class Queue 159 | { 160 | using Ring = Ring; 161 | Ring _ring; 162 | Index _head{0}; // Next availble slot to write. 163 | Index _tail{0}; // Next available to read. 164 | 165 | public: 166 | bool empty() const { return _head == _tail; } 167 | bool full() const { return _head == _tail + Ring::size; } 168 | T& tail() { return _ring[_tail]; } 169 | 170 | void put(const T&& t) { _ring[_head++] = t; } 171 | void put(const T& t) { _ring[_head++] = t; } 172 | T& get() { return _ring[_tail++]; } 173 | void pop() { _tail++; } 174 | }; 175 | 176 | template 177 | class SharedConsumer 178 | { 179 | Fifo* _fifo; 180 | Queue _pendingCommits; 181 | 182 | public: 183 | SharedConsumer(Fifo* p): _fifo(p) {} 184 | 185 | Index claim() 186 | { 187 | if(!_pendingCommits.empty()) 188 | { 189 | commit(); 190 | } 191 | if(_pendingCommits.full()) 192 | { 193 | _fifo->commitTail(_pendingCommits.get()); 194 | } 195 | Index result = _fifo->claimTail(); 196 | _pendingCommits.put(result); 197 | return result; 198 | } 199 | 200 | void commit() 201 | { 202 | // 203 | // We know there must be at least one pending commit 204 | // otherwise this commit would be logically inconsistent. 205 | // 206 | if(_fifo->tryCommitTail(_pendingCommits.tail())) 207 | { 208 | _pendingCommits.pop(); 209 | // 210 | // We don't know how many more there may be. 211 | // 212 | while(!_pendingCommits.empty() && 213 | _fifo->tryCommitTail(_pendingCommits.tail())) 214 | { 215 | _pendingCommits.pop(); 216 | } 217 | } 218 | } 219 | }; 220 | } 221 | 222 | #endif 223 | -------------------------------------------------------------------------------- /include/L3/util/ring.h: -------------------------------------------------------------------------------- 1 | #ifndef RING_H 2 | #define RING_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace L3 // Low Latency Library 9 | { 10 | // 11 | // With a 64 bit index we could use values at a rate of 4GHz 12 | // for 136 years before worrying about wrapping. 13 | // 14 | using Index = uint64_t; 15 | /** 16 | * A buffer of Ts indexed in a circular manner using 17 | * 18 | * index % size. 19 | * 20 | * As an optimisation we constrain that size = 2^n. This allows 21 | * the remainder to be calculated by masking out the lower order 22 | * bits in the index. 23 | * 24 | * Since we are constraining size to powers of 2 we express this 25 | * parameter directly as n where size = 2^n. 26 | * 27 | * Items stored in a ring can be addressed via an index. Since the 28 | * index wraps back round it can address a bounded buffer but also 29 | * be monotonically increasing. This property can be used in to 30 | * solve the ABA problem in algorithms involving CAS. 31 | */ 32 | template 33 | class Ring 34 | { 35 | static_assert(log2size < 32, "Unreasonable RingBuf size."); 36 | static_assert(log2size > 0, "Minimun ring size is 2"); 37 | public: 38 | using value_type = T; 39 | static constexpr Index size = 1L << log2size; 40 | 41 | Ring(): _storage{} {} 42 | 43 | T& operator[](Index idx) 44 | { 45 | // 46 | // Circularise indexing by using (idx mod size) 47 | // implemented by masking off the high order bits. 48 | // 49 | static constexpr Index _mask = size - 1; 50 | return _storage[idx & _mask]; 51 | } 52 | 53 | const T* begin() const { return std::begin(_storage); } 54 | const T* end() const { return std::end(_storage); } 55 | 56 | template 57 | struct IteratorT: std::iterator 58 | { 59 | IteratorT(Ring* r, Index i): _ring(r), _index(i) {} 60 | 61 | T& operator*() const { return (*_ring)[_index]; } 62 | T* operator->() const { return &(*_ring)[_index]; } 63 | 64 | I& operator++() 65 | { 66 | ++_index; 67 | return *static_cast(this); 68 | } 69 | 70 | I operator++(int) 71 | { 72 | I result{*this}; 73 | ++_index; 74 | return result; 75 | } 76 | operator Index() const { return _index; } 77 | 78 | private: 79 | Ring* const _ring; 80 | Index _index; 81 | }; 82 | 83 | struct Iterator: IteratorT 84 | { 85 | Iterator(Ring* r, Index i): IteratorT(r, i) {} 86 | }; 87 | // 88 | // An index typed on the ring instance it came from. 89 | // 90 | #if 1 91 | template 92 | struct StaticIterator: std::iterator 93 | { 94 | using Ring = Ring; 95 | StaticIterator(Index i): _index(i) {} 96 | static constexpr Ring& _ring = r; 97 | T& operator*() const { return _ring[_index]; } 98 | T* operator->() const { return &_ring[_index]; } 99 | 100 | StaticIterator& operator++() 101 | { 102 | ++_index; 103 | return *this; 104 | } 105 | 106 | StaticIterator operator++(int) 107 | { 108 | StaticIterator result{*this}; 109 | ++_index; 110 | return result; 111 | } 112 | operator Index() const { return _index; } 113 | 114 | private: 115 | Index _index; 116 | }; 117 | #else 118 | template 119 | struct StaticIterator: IteratorT> 120 | { 121 | // using Ring = Ring; 122 | StaticIterator(Index i): IteratorT>(&r, i) {} 123 | }; 124 | #endif 125 | 126 | protected: 127 | T _storage[size]; 128 | }; 129 | 130 | template 131 | OS& operator<<(OS& os, const Ring& ring) 132 | { 133 | using Ring = Ring; 134 | std::ostream_iterator oi(os, ", "); 135 | std::copy(ring.begin(), ring.end(), oi); 136 | return os; 137 | } 138 | } 139 | 140 | #endif 141 | -------------------------------------------------------------------------------- /include/L3/util/scopedtimer.h: -------------------------------------------------------------------------------- 1 | #ifndef SCOPEDTIMER_H 2 | #define SCOPEDTIMER_H 3 | 4 | #include 5 | 6 | namespace L3 7 | { 8 | template 10 | struct ScopedTimer 11 | { 12 | using duration = D; 13 | using clock = C; 14 | 15 | typename clock::time_point _start; 16 | 17 | duration& _result; 18 | 19 | ScopedTimer(duration& result): 20 | _result(result), 21 | _start(clock::now()) 22 | { 23 | } 24 | 25 | ~ScopedTimer() 26 | { 27 | typename clock::time_point end(clock::now()); 28 | _result = std::chrono::duration_cast(end - _start); 29 | } 30 | }; 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /include/L3/util/timer.h: -------------------------------------------------------------------------------- 1 | #ifndef TIMER_H 2 | #define TIMER_H 3 | 4 | #include 5 | 6 | namespace L3 7 | { 8 | class Timer 9 | { 10 | Timer(): start{} 11 | { 12 | gettimeofday(&start, nullptr); 13 | } 14 | ~Timer() 15 | { 16 | timeval finish; 17 | gettimeofday(&finish, nullptr); 18 | } 19 | 20 | timeval start; 21 | }; 22 | } 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /include/L3/util/types.h: -------------------------------------------------------------------------------- 1 | #ifndef TYPES_H 2 | #define TYPES_H 3 | /* 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | 30 | namespace L3 31 | { 32 | struct NoOp { void operator()() const {}; }; 33 | using Index = uint64_t; 34 | 35 | using Counter = std::atomic; 36 | // 37 | // Generate a type for tagging purposes. 38 | // 39 | template struct Tag { static constexpr size_t tag = t; }; 40 | } 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /rules.mk: -------------------------------------------------------------------------------- 1 | # $(warning in $(src)) 2 | 3 | CPPFLAGS = -Iinclude 4 | 5 | $(subdirs) 6 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | srcs.cpp := $(wildcard *.cpp) 2 | srcs.c := $(wildcard *.c) 3 | objs := $(srcs.cpp:%.cpp=%.o) $(srcs.c:%.c=%.o) 4 | deps := $(srcs.cpp:%.cpp=%.d) $(srcs.c:%.c=%.d) 5 | execs := $(srcs.cpp:%.cpp=%) $(srcs.c:%.cpp=%) 6 | 7 | valgrind_home = /opt/valgrind-svn 8 | valgrind = $(valgrind_home)/bin/valgrind 9 | 10 | l3_macros = L3_ITERATIONS L3_QSIZE 11 | 12 | macros = $(foreach m,$(l3_macros),$($m:%=-D$m=$($m))) 13 | 14 | opt_flag = -O3 15 | 16 | CPPFLAGS = -I../include -I $(valgrind_home)/include $(macros) 17 | CXXFLAGS = -pthread -g $(opt_flag) --std=c++11 -MMD -MP -MF $(<:%.cpp=%.d) -MT $@ 18 | CXX = clang++ 19 | 20 | 21 | rapidjson = rapidjson/include 22 | 23 | all: $(execs) 24 | 25 | objs: $(objs) 26 | 27 | $(execs): %: %.o 28 | $(LINK.cc) $^ -o $@ 29 | 30 | clean: 31 | - rm -f $(execs) $(objs) $(deps) 32 | 33 | .PHONY: force_run 34 | 35 | run: $(execs:%=%.run) 36 | %.run: % force_run 37 | ./$< 38 | 39 | time: $(execs:%=%.time) 40 | %.time: % force_run 41 | bash -c "time ./$<" 42 | 43 | %.valgrind: % 44 | $(valgrind) --tool=callgrind --instr-atstart=no ./$< 45 | 46 | -include $(deps) 47 | -------------------------------------------------------------------------------- /src/allocator.cpp: -------------------------------------------------------------------------------- 1 | //#include <../include/L3/util/allocator.h> 2 | -------------------------------------------------------------------------------- /src/barrier.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/cacheline.cpp: -------------------------------------------------------------------------------- 1 | #include <../include/L3/util/cacheline.h> 2 | -------------------------------------------------------------------------------- /src/counter.cpp: -------------------------------------------------------------------------------- 1 | #include <../include/L3/util/counter.h> 2 | -------------------------------------------------------------------------------- /src/disruptor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/fifo.cpp: -------------------------------------------------------------------------------- 1 | #include <../include/L3/util/fifo.h> 2 | -------------------------------------------------------------------------------- /src/get.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/put.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/ring.cpp: -------------------------------------------------------------------------------- 1 | #include <../include/L3/util/ring.h> 2 | -------------------------------------------------------------------------------- /src/rules.mk: -------------------------------------------------------------------------------- 1 | # $(warning In $(src)) 2 | 3 | disruptor.src = $(cpp_files) 4 | 5 | $(call lib,disruptor) 6 | -------------------------------------------------------------------------------- /src/sequence.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /src/timer.cpp: -------------------------------------------------------------------------------- 1 | #include <../include/L3/util/timer.h> 2 | -------------------------------------------------------------------------------- /src/types.cpp: -------------------------------------------------------------------------------- 1 | #include <../include/L3/util/types.h> 2 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | srcs.cpp := $(wildcard *.cpp) 2 | srcs.c := $(wildcard *.c) 3 | objs := $(srcs.cpp:%.cpp=%.o) $(srcs.c:%.c=%.o) 4 | deps := $(srcs.cpp:%.cpp=%.d) $(srcs.c:%.c=%.d) 5 | execs := $(srcs.cpp:%.cpp=%) $(srcs.c:%.cpp=%) 6 | 7 | valgrind_home = /opt/valgrind-svn 8 | valgrind = $(valgrind_home)/bin/valgrind 9 | 10 | l3_macros = L3_ITERATIONS L3_QSIZE 11 | 12 | macros = $(foreach m,$(l3_macros),$($m:%=-D$m=$($m))) 13 | 14 | opt_flag = -O3 15 | 16 | CPPFLAGS = -I../include -I $(valgrind_home)/include $(macros) 17 | CXXFLAGS = -pthread -g $(opt_flag) --std=c++11 -MMD -MP -MF $(<:%.cpp=%.d) -MT $@ 18 | CXX = clang++ 19 | 20 | 21 | rapidjson = rapidjson/include 22 | 23 | all: $(execs) 24 | 25 | $(execs): %: %.o 26 | $(LINK.cc) $^ -o $@ 27 | 28 | clean: 29 | - rm -f $(execs) $(objs) $(deps) 30 | 31 | .PHONY: force_run 32 | 33 | test run: $(execs:%=%.run) 34 | %.run: % force_run 35 | ./$< 36 | 37 | time: $(execs:%=%.time) 38 | %.time: % force_run 39 | bash -c "time ./$<" 40 | 41 | %.valgrind: % 42 | $(valgrind) --tool=callgrind --instr-atstart=no ./$< 43 | 44 | -include $(deps) 45 | -------------------------------------------------------------------------------- /test/rules.mk: -------------------------------------------------------------------------------- 1 | #$(warning In $(src)) 2 | #$(warning bld $(bld)) 3 | 4 | $(src).tests = $(wildcard $(src)/test_*.cpp) 5 | 6 | $(foreach t,$($(src).tests:$(src)/%.cpp=%),\ 7 | $(eval $t.src = $(src)/$t.cpp) \ 8 | $(eval $(call test,$t))) 9 | -------------------------------------------------------------------------------- /test/test_fifo.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | using namespace L3; 31 | 32 | L3_CACHE_LINE size_t putSpinCount = 0; 33 | L3_CACHE_LINE size_t getSpinCount = 0; 34 | L3_CACHE_LINE size_t cursorSpinCount = 0; 35 | 36 | template 37 | struct Counter 38 | { 39 | void operator()() { ++counter; } 40 | }; 41 | 42 | using PutSpinCounter = Counter; 43 | using GetSpinCounter = Counter; 44 | using CursorSpinCounter = Counter; 45 | 46 | typedef size_t Msg; 47 | 48 | using DR = Fifo; 49 | using PUT = DR::Put; 50 | using GET = DR::Get; 51 | 52 | DR buf; 53 | 54 | constexpr size_t iterations = 100000000; // 100 Million. 55 | 56 | std::array msgs; 57 | 58 | bool testSingleProducerSingleConsumer() 59 | { 60 | int result = 0; 61 | 62 | std::thread producer( 63 | [&](){ 64 | for(Msg i = 1; i < iterations; i++) 65 | { 66 | PUT p(buf); 67 | p = i; 68 | } 69 | std::cerr << "Prod done" << std::endl; 70 | }); 71 | 72 | std::thread consumer( 73 | [&](){ 74 | Msg previous = buf.get(); 75 | for(size_t i = 1; i < iterations - 1; ++i) 76 | { 77 | Msg msg; 78 | { 79 | msg = GET(buf); 80 | } 81 | msgs[i] = msg; 82 | long diff = (msg - previous) - 1; 83 | result += (diff * diff); 84 | previous = msg; 85 | } 86 | std::cerr << "Cons done" << std::endl; 87 | }); 88 | 89 | 90 | producer.join(); 91 | consumer.join(); 92 | 93 | std::cout << "result: " << result << std::endl; 94 | 95 | std::cout << "putSpinCount = " << putSpinCount << std::endl; 96 | std::cout << "getSpinCount = " << getSpinCount << std::endl; 97 | std::cout << "cursorSpinCount = " << cursorSpinCount << std::endl; 98 | 99 | Msg previous = 0; 100 | bool status = true; 101 | for(auto i: msgs) 102 | { 103 | if(i == 0) 104 | { 105 | continue; 106 | } 107 | if(previous >= i) 108 | { 109 | std::cout << "Wrong at: " << i << std::endl; 110 | status = false; 111 | } 112 | previous = i; 113 | } 114 | 115 | return status; 116 | } 117 | 118 | bool testTwoProducerSingleConsumer() 119 | { 120 | int result = 0; 121 | 122 | std::atomic go { false }; 123 | 124 | std::thread producer1( 125 | [&](){ 126 | while(!go); 127 | for(Msg i = 3; i < iterations; i += 2) 128 | { 129 | PUT p(buf); 130 | p = i; 131 | } 132 | std::cerr << "Prod 1 done" << std::endl; 133 | }); 134 | 135 | std::thread producer2( 136 | [&](){ 137 | while(!go); 138 | for(Msg i = 2; i < iterations; i += 2) 139 | { 140 | PUT p(buf); 141 | p = i; 142 | } 143 | std::cerr << "Prod 2 done" << std::endl; 144 | }); 145 | 146 | 147 | std::thread consumer( 148 | [&](){ 149 | while(!go); 150 | Msg oldOdd = 1; 151 | Msg oldEven = 0; 152 | for(size_t i = 1; i < iterations - 5; ++i) 153 | { 154 | Msg msg; 155 | { 156 | msg = GET(buf); 157 | } 158 | 159 | Msg& old = msg & 0x1L ? oldOdd : oldEven; 160 | 161 | long diff = (msg - old) - 2; 162 | result += (diff * diff); 163 | old = msg; 164 | msgs[i] = msg; 165 | } 166 | std::cerr << "Cons done" << std::endl; 167 | }); 168 | 169 | go = true; 170 | 171 | producer1.join(); 172 | producer2.join(); 173 | consumer.join(); 174 | 175 | std::cout << "result: " << result << std::endl; 176 | std::cout << "putSpinCount = " << putSpinCount << std::endl; 177 | std::cout << "getSpinCount = " << getSpinCount << std::endl; 178 | std::cout << "cursorSpinCount = " << cursorSpinCount << std::endl; 179 | 180 | bool status = result == 0; 181 | 182 | return status; 183 | } 184 | 185 | int main() 186 | { 187 | bool status = true; 188 | status = testSingleProducerSingleConsumer(); 189 | status = testTwoProducerSingleConsumer() && status; 190 | 191 | return status ? 0 : 1; 192 | } 193 | -------------------------------------------------------------------------------- /test/test_flexififo.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | using namespace L3; 31 | 32 | using Ring4 = Ring; 33 | 34 | FlexiFifo fifo1To1; 35 | FlexiFifo fifo1ToN; 36 | FlexiFifo fifoNTo1; 37 | FlexiFifo fifoNToN; 38 | 39 | using FifoNToN = FlexiFifo; 40 | 41 | int 42 | main() 43 | { 44 | SharedConsumer consumer(&fifoNToN); 45 | SharedConsumer consumer2(&fifoNToN); 46 | std::cout << "sizeof: " << sizeof(consumer) << std::endl; 47 | 48 | fifoNToN.commitHead(fifoNToN.claimHead()); 49 | fifoNToN.commitHead(fifoNToN.claimHead()); 50 | 51 | consumer.claim(); 52 | consumer2.claim(); 53 | consumer2.commit(); 54 | consumer.commit(); 55 | } 56 | 57 | 58 | -------------------------------------------------------------------------------- /test/test_latency.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | using namespace std::chrono; 31 | 32 | struct Msg 33 | { 34 | //L3_CACHE_LINE 35 | size_t sequence; 36 | steady_clock::time_point timestamp; 37 | }; 38 | 39 | using D = L3::Disruptor; 40 | using Get = D::Get<>; 41 | using Put = D::Put<>; 42 | 43 | int 44 | main() 45 | { 46 | // 47 | // We can send messages between two threads like so. 48 | // 49 | // 10 Million messages takes less than a second on my old macbook 50 | // pro. 51 | // 52 | const size_t loops = 100 * 1000 * 1000; 53 | // 54 | // Producer just writes sequencial integers. 55 | // 56 | std::thread producer( 57 | []{ 58 | for(size_t i = 1; i < loops; ++i) 59 | { 60 | Put() = Msg{i, steady_clock::now()}; 61 | } 62 | }); 63 | // 64 | // Consumer checks that these are read in the same order. 65 | // 66 | size_t previous{0}; 67 | uint64_t totalLatency{0}; 68 | // 69 | // Should get the same number of messages we put. 70 | // 71 | for(size_t i = 1; i < loops;) 72 | { 73 | for(Msg m: Get()) 74 | { 75 | if(m.sequence != previous + 1) 76 | { 77 | // 78 | // Oops - something went wrong. Return here will abort 79 | // since producer not joined. 80 | // 81 | return -1; 82 | } 83 | steady_clock::time_point end{steady_clock::now()}; 84 | 85 | totalLatency += duration_cast( 86 | end - m.timestamp).count(); 87 | 88 | previous = m.sequence; 89 | ++i; 90 | } 91 | } 92 | producer.join(); 93 | std::cout << "total latency: " << (totalLatency / 1000000) << "ms" 94 | << std::endl; 95 | 96 | std::cout << "average latency: " << (totalLatency / loops) << "ns" 97 | << std::endl; 98 | 99 | return 0; 100 | } 101 | -------------------------------------------------------------------------------- /test/test_logger.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | #include 25 | 26 | int main() 27 | { 28 | using Log = L3::Logger; 29 | 30 | auto generate = [](int instance){ 31 | for(int i = 0; i < 1000; i++) 32 | { 33 | Log() << "logging instance: " << instance 34 | << ", i = " << i; 35 | } 36 | }; 37 | L3::Logger::Writer writer; 38 | std::thread writerThread{[&]{ writer(); }}; 39 | std::thread generate1{ generate, 1 }; 40 | std::thread generate2{ generate, 2 }; 41 | 42 | generate1.join(); 43 | generate2.join(); 44 | 45 | std::this_thread::sleep_for(std::chrono::seconds(3)); 46 | 47 | writer.stop(); 48 | writerThread.join(); 49 | } 50 | -------------------------------------------------------------------------------- /test/test_min.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std::chrono; 8 | 9 | template 10 | inline const T& fastMinRef(const T& lhs, const T& rhs) 11 | { 12 | const T* const tmp[] = { &lhs, &rhs }; 13 | return *tmp[lhs < rhs]; 14 | } 15 | 16 | template 17 | inline T fastMin(const T lhs, const T rhs) 18 | { 19 | const T tmp[] = { lhs, rhs }; 20 | return tmp[lhs < rhs]; 21 | } 22 | 23 | template 24 | inline T fastMin2(T&& lhs, T&& rhs) 25 | { 26 | constexpr T one = 1; 27 | constexpr T signbit = (one << 7) << (sizeof(T) - 1) * 8; 28 | auto diff = lhs - rhs; 29 | auto minusDiff = -diff; 30 | constexpr auto shiftBack = sizeof(T) * 8 - 1; 31 | 32 | return lhs * (diff >> shiftBack) + rhs * (minusDiff >> shiftBack); 33 | } 34 | 35 | inline float branchFloat(bool b) { return b ? 1.3f : 2.6f; } 36 | 37 | inline float indexFloat(bool b) 38 | { 39 | constexpr float vals[] = { 2.6f, 1.3f }; 40 | return vals[b]; 41 | } 42 | 43 | inline int branchInt(bool b){ return b ? 1 : 3; } 44 | 45 | inline int indexInt(bool b) 46 | { 47 | constexpr float vals[] = { 3, 1 }; 48 | return vals[b]; 49 | } 50 | 51 | template 52 | inline T fastArrayMin(const T (&vals)[2]) { return vals[vals[0] < vals[1]]; } 53 | 54 | template 55 | inline T fastArrayMin(const T (&vals)[n]) 56 | { 57 | 58 | } 59 | 60 | constexpr size_t dataSize = 1 << 24; 61 | int data[dataSize]; 62 | int data2[dataSize]; 63 | float result1[dataSize >> 1]; 64 | float result2[dataSize >> 1]; 65 | 66 | int resultInt1[dataSize >> 1]; 67 | int resultInt2[dataSize >> 1]; 68 | 69 | template 70 | void randomiseBlock(T (&data)[n]) 71 | { 72 | std::ranlux24_base rd; 73 | std::uniform_int_distribution dist; 74 | 75 | for(auto& i: data) i = dist(rd); 76 | } 77 | 78 | template 79 | inline L3::ScopedTimer<>::duration runCase(Fun& fun) 80 | { 81 | L3::ScopedTimer<>::duration elapsedTime{0}; 82 | L3::ScopedTimer<> timer(elapsedTime); 83 | fun(); 84 | return elapsedTime; 85 | } 86 | 87 | template 88 | class RunTimer 89 | { 90 | public: 91 | 92 | using Duration = duration; 93 | 94 | Duration aTime{0}; 95 | Duration bTime{0}; 96 | 97 | template 98 | void run(A&& a, B&& b, size_t repeats) 99 | { 100 | for(size_t i = 0; i < repeats; ++i) 101 | { 102 | aTime += runCase(a); 103 | bTime += runCase(b); 104 | } 105 | aTime /= repeats; 106 | bTime /= repeats; 107 | } 108 | }; 109 | 110 | template 111 | struct DataRunner 112 | { 113 | Data data; 114 | Result result; 115 | Fun fun; 116 | 117 | DataRunner(Fun&& f): 118 | fun(f) 119 | { 120 | randomiseBlock(data); 121 | } 122 | 123 | void operator()() 124 | { 125 | auto* r = result; 126 | for(const auto* i = std::begin(data); i != std::end(data);) 127 | { 128 | *r++ = fun(i); 129 | } 130 | } 131 | }; 132 | 133 | template 134 | inline DataRunner 135 | makeDataRunner(Fun&& fun) 136 | { 137 | return DataRunner(std::move(fun)); 138 | } 139 | 140 | auto branchRunner = makeDataRunner( 141 | [](const int*& i) 142 | { 143 | int l = *i++; 144 | return branchFloat(l < *i++); 145 | }); 146 | 147 | auto indexRunner = makeDataRunner( 148 | [](const int*& i) 149 | { 150 | int l = *i++; 151 | return indexFloat(l < *i++); 152 | }); 153 | 154 | auto branchRunnerInt = makeDataRunner( 155 | [](const int*& i) 156 | { 157 | int l = *i++; 158 | return branchInt(l < *i++); 159 | }); 160 | 161 | auto indexRunnerInt = makeDataRunner( 162 | [](const int*& i) 163 | { 164 | int l = *i++; 165 | return indexInt(l < *i++); 166 | }); 167 | 168 | auto fastArrayRunnerInt = makeDataRunner( 169 | [](const int*& i) 170 | { 171 | int result = fastArrayMin(reinterpret_cast(*i)); 172 | i += 2; 173 | return result; 174 | }); 175 | 176 | auto stdMinRunnerInt = makeDataRunner( 177 | [](const int*& i){ 178 | int tmp = *i++; 179 | return std::min(tmp, *i++); 180 | }); 181 | 182 | auto fastArrayRunner = makeDataRunner( 183 | [](const float*& i) 184 | { 185 | float result = fastArrayMin(reinterpret_cast(*i)); 186 | i += 2; 187 | return result; 188 | }); 189 | 190 | auto stdMinRunner = makeDataRunner( 191 | [](const float*& i){ 192 | float tmp = *i++; 193 | return std::min(tmp, *i++); 194 | }); 195 | 196 | int 197 | main() 198 | { 199 | std::atomic i; 200 | using clock = std::chrono::steady_clock; 201 | clock::time_point start(clock::now()); 202 | i.store(10); 203 | clock::time_point end(clock::now()); 204 | 205 | L3::ScopedTimer<>::duration tickSize = end - start; 206 | 207 | std::cout << "Minimum measurable time: " 208 | << duration_cast(tickSize).count() 209 | << "ns" 210 | << std::endl; 211 | 212 | L3::ScopedTimer<>::duration elapsedTime{0}; 213 | { 214 | L3::ScopedTimer<> timer(elapsedTime); 215 | randomiseBlock(data); 216 | randomiseBlock(data2); 217 | } 218 | std::cout << "Time: init: " 219 | << duration_cast(elapsedTime).count() 220 | << "us" 221 | << std::endl; 222 | 223 | RunTimer<> runner; 224 | int repeats = 100; 225 | runner.run(stdMinRunner, fastArrayRunner, repeats); 226 | std::cout << "std::min v fastArray" << std::endl; 227 | std::cout << "average branch: " << runner.aTime.count() << "ms" 228 | << std::endl; 229 | std::cout << "average index: " << runner.bTime.count() << "ms" 230 | << std::endl; 231 | 232 | runner.run(branchRunner, indexRunner, repeats); 233 | std::cout << "branch v index" << std::endl; 234 | std::cout << "average branch: " << runner.aTime.count() << "ms" 235 | << std::endl; 236 | std::cout << "average index: " << runner.bTime.count() << "ms" 237 | << std::endl; 238 | 239 | runner.run(branchRunnerInt, indexRunnerInt, repeats); 240 | std::cout << "branchInt v indexInt" << std::endl; 241 | std::cout << "average branch: " << runner.aTime.count() << "ms" 242 | << std::endl; 243 | std::cout << "average index: " << runner.bTime.count() << "ms" 244 | << std::endl; 245 | 246 | runner.run( 247 | []() 248 | { 249 | float* r = result2; 250 | for(const int* i = std::begin(data); i != std::end(data);) 251 | { 252 | int tmp = *i++; 253 | *r++ = branchFloat(tmp < *++i); 254 | } 255 | }, 256 | [](){ 257 | float* r = result1; 258 | for(const int* i = std::begin(data2); i != std::end(data2);) 259 | { 260 | int tmp = *i++; 261 | *r++ = indexFloat(tmp < *++i); 262 | } 263 | }, 264 | repeats); 265 | 266 | std::cout << "branchFloat v indexFloat" << std::endl; 267 | std::cout << "average branch: " << runner.aTime.count() << "ms" << std::endl; 268 | std::cout << "average index: " << runner.bTime.count() << "ms" << std::endl; 269 | 270 | runner.run( 271 | [](){ 272 | int* r = resultInt1; 273 | for(const int* i = std::begin(data2); i != std::end(data2);) 274 | { 275 | int tmp = *i++; 276 | *r++ = indexInt(tmp < *++i); 277 | } 278 | }, 279 | []() 280 | { 281 | int* r = resultInt2; 282 | for(const int* i = std::begin(data); i != std::end(data);) 283 | { 284 | int tmp = *i++; 285 | *r++ = branchInt(tmp < *++i); 286 | } 287 | }, 288 | repeats); 289 | 290 | std::cout << "indexInt v branchInt" << std::endl; 291 | std::cout << "average index: " << runner.aTime.count() << "ms" 292 | << std::endl; 293 | std::cout << "average branch: " << runner.bTime.count() << "ms" 294 | << std::endl; 295 | } 296 | -------------------------------------------------------------------------------- /test/test_putget.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | #ifndef L3_ITERATIONS 30 | # define L3_ITERATIONS 10000000 31 | #endif 32 | 33 | constexpr size_t iterations {L3_ITERATIONS}; 34 | 35 | #ifndef L3_QSIZE 36 | # define L3_QSIZE 20 37 | #endif 38 | 39 | template 40 | void dbg() 41 | { 42 | std::cout << "ring: "; 43 | std::ostream_iterator oi(std::cout, ", "); 44 | std::copy(D::ring.begin(), D::ring.end(), oi); 45 | std::cout << std::endl; 46 | 47 | std::cout << "cursor: " << D::cursor << std::endl; 48 | } 49 | 50 | struct ThrowSpin 51 | { 52 | void operator()() const 53 | { 54 | throw std::runtime_error("shouldn't spin"); 55 | } 56 | }; 57 | 58 | namespace testSpins 59 | { 60 | using D = L3::Disruptor>; 61 | 62 | using Get = D::Get, L3::Barrier, ThrowSpin>; 63 | using Put = D::Put, 64 | L3::CommitPolicy::Unique, 65 | ThrowSpin, 66 | ThrowSpin>; 67 | void dbg1() 68 | { 69 | dbg(); 70 | std::cout << "writeCursor: " << Put::cursor 71 | << ", readCursor: " << Get::cursor 72 | << std::endl; 73 | } 74 | 75 | bool test() 76 | { 77 | Put() = 42; 78 | Put() = 43; 79 | 80 | try 81 | { 82 | Put() = 44; 83 | 84 | // std::cout << "put spin fail" << std::endl; 85 | return false; 86 | } 87 | catch(const std::exception& ex) 88 | { 89 | // std::cout << ex.what() << std::endl; 90 | } 91 | auto i = Get().begin(); 92 | if(*i == 42 && *++i != 43) 93 | { 94 | return false; 95 | } 96 | try 97 | { 98 | auto i = Get(); 99 | // std::cout << "get spin fail" << std::endl; 100 | return false; 101 | } 102 | catch(const std::exception& ex) 103 | { 104 | // std::cout << ex.what() << std::endl; 105 | } 106 | return true; 107 | } 108 | } 109 | 110 | namespace testSpins1to1to1 111 | { 112 | using D = L3::Disruptor>; 113 | 114 | using Get1 = D::Get, L3::Barrier, ThrowSpin>; 115 | using Get2 = D::Get, L3::Barrier, ThrowSpin>; 116 | 117 | using Put = D::Put, 118 | L3::CommitPolicy::Unique, 119 | ThrowSpin, 120 | ThrowSpin>; 121 | void dbg() 122 | { 123 | // dbg(); 124 | std::cout << "writeCursor: " << Put::cursor 125 | << ", readCursor1: " << Get1::cursor 126 | << ", readCursor2: " << Get2::cursor 127 | << std::endl; 128 | } 129 | 130 | bool test() 131 | { 132 | Put() = 42; 133 | Put() = 43; 134 | // dbg(); 135 | try 136 | { 137 | Put() = 44; 138 | 139 | // std::cout << "put spin fail" << std::endl; 140 | return false; 141 | } 142 | catch(const std::exception& ex) 143 | { 144 | // std::cout << ex.what() << std::endl; 145 | } 146 | // dbg(); 147 | auto i = Get1().begin(); 148 | if(*i == 42 && *++i != 43) 149 | { 150 | return false; 151 | } 152 | try 153 | { 154 | auto i = Get1(); 155 | // std::cout << "get spin fail" << std::endl; 156 | return false; 157 | } 158 | catch(const std::exception& ex) 159 | { 160 | // std::cout << ex.what() << std::endl; 161 | } 162 | try 163 | { 164 | Put() = 44; 165 | 166 | // std::cout << "put spin fail" << std::endl; 167 | return false; 168 | } 169 | catch(const std::exception& ex) 170 | { 171 | // std::cout << ex.what() << std::endl; 172 | } 173 | i = Get2().begin(); 174 | if(*i == 42 && *++i != 43) 175 | { 176 | return false; 177 | } 178 | // dbg(); 179 | try 180 | { 181 | auto i = Get2(); 182 | // std::cout << "get spin fail" << std::endl; 183 | return false; 184 | } 185 | catch(const std::exception& ex) 186 | { 187 | // std::cout << ex.what() << std::endl; 188 | } 189 | Put() = 44; 190 | return true; 191 | } 192 | } 193 | 194 | namespace test1to1 195 | { 196 | using D = L3::Disruptor>; 197 | // 198 | // Using defaults. 199 | // 200 | using Get = D::Get<>; 201 | using Put = D::Put<>; 202 | 203 | void dbg1() 204 | { 205 | // dbg(); 206 | // std::cout << "writeCursor: " << Put::cursor 207 | // << ", readCursor: " << readCursor 208 | // << std::endl; 209 | } 210 | 211 | bool test1Thread() 212 | { 213 | Put() = 42; 214 | // dbg1(); 215 | for(auto& m: Get()) 216 | { 217 | // std::cout << "m: " << m << std::endl; 218 | // dbg1(); 219 | if(m != 42) 220 | { 221 | return false; 222 | } 223 | } 224 | // dbg1(); 225 | Put() = 101; 226 | // dbg1(); 227 | Put() = 202; 228 | // dbg1(); 229 | { 230 | Get g; 231 | // dbg1(); 232 | D::Iterator b(g.begin()); 233 | D::Iterator e(g.end()); 234 | 235 | D::Msg m1 = *b++; 236 | D::Msg m2 = *b++; 237 | 238 | // std::cout << "m1: " << m1 << ", m2: " << m2 << std::endl; 239 | // std::cout << "b: " << b << ", e: " << e << std::endl; 240 | 241 | return m1 == 101 && m2 == 202 && b == e; 242 | } 243 | } 244 | 245 | bool 246 | test2Threads() 247 | { 248 | std::thread prod( 249 | [](){ for(D::Msg i = 1; i < iterations; ++i) Put() = i; }); 250 | 251 | D::Msg previous = 0; 252 | for(D::Msg i = 1; i < iterations;) 253 | { 254 | for(auto msg: Get()) 255 | { 256 | i++; 257 | if(msg != previous + 1) 258 | { 259 | std::cout << "previous: " << previous 260 | << ", msg: " << msg 261 | << std::endl; 262 | // dbg(); 263 | return false; 264 | } 265 | previous = msg; 266 | } 267 | } 268 | prod.join(); 269 | 270 | return true; 271 | } 272 | 273 | bool test() 274 | { 275 | bool result = true; 276 | result &= test1Thread(); 277 | result &= test2Threads(); 278 | return result; 279 | } 280 | } 281 | 282 | namespace test2to1 283 | { 284 | using D = L3::Disruptor>; 285 | 286 | using Get = D::Get<>; 287 | 288 | using Put = D::Put, L3::CommitPolicy::Shared>; 289 | 290 | bool test3Threads() 291 | { 292 | std::atomic go { false }; 293 | std::thread producer1( 294 | [&](){ 295 | while(!go); 296 | for(D::Msg i = 3; i < iterations; i += 2) 297 | { 298 | Put() = i; 299 | } 300 | }); 301 | 302 | std::thread producer2( 303 | [&](){ 304 | while(!go); 305 | for(D::Msg i = 2; i < iterations; i += 2) 306 | { 307 | Put() = i; 308 | } 309 | }); 310 | 311 | go = true; 312 | D::Msg oldOdd = 1; 313 | D::Msg oldEven = 0; 314 | for(size_t i = 2; i < iterations;) 315 | { 316 | for(auto msg: Get()) 317 | { 318 | // dbg(); 319 | 320 | ++i; 321 | D::Msg& old = msg & 0x1L ? oldOdd : oldEven; 322 | if(msg != old + 2) 323 | { 324 | // std::cout << "old: " << old << ", new: " << msg 325 | // << std::endl; 326 | return false; 327 | } 328 | old = msg; 329 | } 330 | } 331 | std::cout << "Cons done" << std::endl; 332 | producer1.join(); 333 | producer2.join(); 334 | return true; 335 | } 336 | bool test() 337 | { 338 | return test3Threads(); 339 | } 340 | } 341 | 342 | template 343 | bool consume(size_t instance, bool& status) 344 | { 345 | typename D::Msg previous = 0; 346 | for(typename D::Msg i = 1; i < iterations;) 347 | { 348 | for(auto msg: Get()) 349 | { 350 | // std::cout << "NOT FAIL: instance: " << instance 351 | // << ", previous: " << previous 352 | // << ", msg: " << msg 353 | // << ", i: " << i 354 | // << std::endl; 355 | 356 | if(msg != previous + 1) 357 | { 358 | std::cout << "FAIL: previous: " << previous 359 | << ", msg: " << msg 360 | << std::endl; 361 | return status = false; 362 | } 363 | previous = msg; 364 | i++; 365 | } 366 | } 367 | return status = true; 368 | } 369 | 370 | namespace test1to2 371 | { 372 | using D = L3::Disruptor>; 373 | 374 | using Get1 = D::Get>; 375 | using Get2 = D::Get>; 376 | using Put = D::Put, L3::CommitPolicy::Unique>; 377 | 378 | bool 379 | test() 380 | { 381 | std::thread prod( 382 | [](){ for(D::Msg i = 1; i < iterations; ++i) Put() = i; }); 383 | 384 | bool stat1; 385 | std::thread cons1([&]{consume(1, stat1);}); 386 | bool stat2; 387 | consume(2, stat2); 388 | 389 | prod.join(); 390 | cons1.join(); 391 | 392 | return stat1 && stat2; 393 | } 394 | } 395 | 396 | 397 | namespace test1to2to1 398 | { 399 | using D = L3::Disruptor>; 400 | 401 | using Get1 = D::Get, L3::Barrier>; 402 | using Get2 = D::Get, L3::Barrier>; 403 | using Get3 = D::Get, L3::Barrier>; 404 | 405 | using Put = D::Put, L3::CommitPolicy::Unique>; 406 | 407 | void dbg() 408 | { 409 | // dbg(); 410 | 411 | std::cout << "readCursor1: " << Get1::cursor << std::endl; 412 | std::cout << "readCursor2: " << Get2::cursor << std::endl; 413 | std::cout << "readCursor3: " << Get3::cursor << std::endl; 414 | } 415 | 416 | bool 417 | test1() 418 | { 419 | // dbg(); 420 | Put() = 42; 421 | // dbg(); 422 | // Put() = 44; 423 | // dbg(); 424 | 425 | D::Msg m1 = *Get1().begin(); 426 | // dbg(); 427 | D::Msg m2 = *Get2().begin(); 428 | // dbg(); 429 | D::Msg m3 = *Get3().begin(); 430 | // dbg(); 431 | 432 | Put() = 43; 433 | m1 = *Get1().begin(); 434 | m2 = *Get2().begin(); 435 | m3 = *Get3().begin(); 436 | 437 | // dbg(); 438 | Put() = 44; 439 | m1 = *Get1().begin(); 440 | m2 = *Get2().begin(); 441 | m3 = *Get3().begin(); 442 | // dbg(); 443 | 444 | std::cout << "m1: " << m1 445 | << ", m2: " << m2 446 | << ", m3: " << m3 447 | << std::endl; 448 | 449 | return m1 == 44 && m2 == 44 && m3 ==44; 450 | } 451 | 452 | bool 453 | test2() 454 | { 455 | std::thread prod( 456 | [](){ for(D::Msg i = 1; i < iterations; ++i) Put() = i; }); 457 | 458 | bool status1; 459 | bool status2; 460 | bool status3; 461 | std::thread consumer1([&]{consume(1, status1);}); 462 | std::thread consumer2([&]{consume(2, status2);}); 463 | consume(3, status3); 464 | prod.join(); 465 | consumer1.join(); 466 | consumer2.join(); 467 | 468 | return true; 469 | } 470 | } 471 | 472 | int 473 | main() 474 | { 475 | bool status = true; 476 | // status &= testSpins::test(); 477 | // std::cerr << "testSpins::test: " << status << std::endl; 478 | 479 | // status &= testSpins1to1to1::test(); 480 | // std::cerr << "testSpins1to1to1::test: " << status << std::endl; 481 | 482 | status &= test1to1::test(); 483 | std::cerr << "test1to1::test: " << status << std::endl; 484 | 485 | status &= test2to1::test(); 486 | std::cerr << "test2to1::test: " << status << std::endl; 487 | 488 | status &= test1to2::test(); 489 | std::cerr << "test1to2::test: " << status << std::endl; 490 | 491 | status &= test1to2to1::test1(); 492 | std::cerr << "test1to2to1::test1: " << status << std::endl; 493 | 494 | status &= test1to2to1::test2(); 495 | std::cerr << "test1to2to1::test2: " << status << std::endl; 496 | 497 | return status ? 0 : 1; 498 | } 499 | 500 | -------------------------------------------------------------------------------- /test/test_putget.py: -------------------------------------------------------------------------------- 1 | """ 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | """ 24 | from multiprocessing import Process, Queue 25 | 26 | q = Queue() 27 | 28 | iterations = 10000000 29 | 30 | def produce(q): 31 | for i in range(iterations): 32 | q.put(i) 33 | 34 | if __name__ == "__main__": 35 | t = Process(target=produce, args=(q,)) 36 | t.start() 37 | 38 | previous = -1 39 | for i in range(iterations): 40 | m = q.get() 41 | if m != previous + 1: 42 | print "Fail at:", previous, m 43 | break 44 | previous = m 45 | 46 | print "done" 47 | -------------------------------------------------------------------------------- /test/test_selector.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 Norman Wilson - Volcano Consultancy Ltd 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | using Msg = size_t; 34 | constexpr size_t log2size = 18; 35 | 36 | using D1 = L3::Disruptor>; 37 | using D2 = L3::Disruptor>; 38 | 39 | template 40 | struct L3_CACHE_LINE MsgTestSequence 41 | { 42 | using Put = typename D::template Put<>; 43 | using Get = typename D::template Get<>; 44 | 45 | const Msg eos{0}; 46 | Msg previous; 47 | 48 | MsgTestSequence(): 49 | previous{first - increment} 50 | {} 51 | 52 | void put(Msg i) 53 | { 54 | Put() = i; 55 | } 56 | 57 | void produce() 58 | { 59 | for(Msg i = first; i < last; i += increment) 60 | { 61 | Put() = i; 62 | } 63 | Put() = eos; 64 | } 65 | 66 | void operator()(Msg& m) 67 | { 68 | if(m != eos && previous + increment != m) 69 | { 70 | std::cout << "D<" << D::Tag::tag << ">: FAIL: msg: " << m 71 | << ", previous: " << previous 72 | << ", &previous: " << &previous 73 | << ", ring: { " << D::ring << " }"; 74 | } 75 | previous = m; 76 | } 77 | 78 | bool done() const { return previous == eos; } 79 | }; 80 | 81 | constexpr Msg loops{10 * 1000 * 1000}; 82 | 83 | int 84 | main() 85 | { 86 | MsgTestSequence odd; 87 | MsgTestSequence even; 88 | 89 | std::thread p1([&]{ odd.produce(); }); 90 | std::thread p2([&]{ even.produce(); }); 91 | 92 | try 93 | { 94 | while(!(odd.done() && even.done())) 95 | { 96 | L3::select(1, even, odd); 97 | } 98 | } 99 | catch(const std::runtime_error& ex) 100 | {} 101 | 102 | for(auto t: {&p1, &p2}) t->join(); 103 | } 104 | -------------------------------------------------------------------------------- /tums/.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /tums/.gitrepo: -------------------------------------------------------------------------------- 1 | ; DO NOT EDIT (unless you know what you are doing) 2 | ; 3 | ; This subdirectory is a git "subrepo", and this file is maintained by the 4 | ; git-subrepo command. See https://github.com/git-commands/git-subrepo#readme 5 | ; 6 | [subrepo] 7 | remote = git@github.com:abwilson/tums.git 8 | branch = master 9 | commit = 0a80a71afdb403c624a5e9d8cf2ba54c0919d647 10 | parent = a788db9b1e19d92c0f168d835351b6d7d0c161e9 11 | method = merge 12 | cmdver = 0.4.0 13 | -------------------------------------------------------------------------------- /tums/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Norman Wilson - Volcano Consultancy Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tums/README.md: -------------------------------------------------------------------------------- 1 | # tums 2 | TUMS - The Ultimate Make System 3 | 4 | A way of writing non-recursive make systems. Work in progress. See L3 repo for usage. 5 | -------------------------------------------------------------------------------- /tums/features/clean.mk: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | clean: 3 | rm -rf $(bld_root)/* 4 | -------------------------------------------------------------------------------- /tums/features/cpp.mk: -------------------------------------------------------------------------------- 1 | CPPFLAGS = 2 | CXXFLAGS = --std=c++17 -MD -g -O3 3 | 4 | # cpp_read_deps = $(eval -include $(@:.o=.d)) 5 | 6 | cpp_files = $(wildcard $(src)/*.cpp) 7 | 8 | # cpp_read_deps = \ 9 | # $(shell test -f $(@:.o=.d) && make -BrRf make/show_deps.mk -f $(@:.o=.d) $@ 2> /dev/null) 10 | 11 | # $(info auto deps $@) \ 12 | # $(info $(shell test -f $(@:.o=.d) && make -Brf make/show_deps.mk -f $(@:.o=.d) $@ 2> /dev/null)) 13 | 14 | 15 | # cpp_read_deps = test/../src/AddGlue.h 16 | -------------------------------------------------------------------------------- /tums/features/dbg.mk: -------------------------------------------------------------------------------- 1 | dbg = $(if $(DBG),$(warning $1)) 2 | 3 | dbg_obj = $(if $(DBG),$(call dbg_var,$(filter $1.%,$(.VARIABLES)))) 4 | 5 | dbg_var = $(foreach i,$1,$(warning $i = $($i))) 6 | -------------------------------------------------------------------------------- /tums/features/exec.mk: -------------------------------------------------------------------------------- 1 | target = $($(stem).target) 2 | 3 | define exec_impl 4 | $(call dbg,Defining exec "$1".) 5 | $(eval $1.target ?= $(bld)/$1) 6 | execs += $(target) 7 | $(target): LDFLAGS = $($1.lddirs:%=-L%) $($1.libs:%=-l%) 8 | $(target): $($1.src:%.cpp=$(bld_root)/%.o) 9 | -include $(wildcard $($1.src:%.cpp=$(bld_root)/%.d)) /dev/null 10 | all: $(target) 11 | endef 12 | 13 | exec = $(eval $(exec_impl)) 14 | 15 | 16 | -------------------------------------------------------------------------------- /tums/features/lib.mk: -------------------------------------------------------------------------------- 1 | define lib_impl 2 | $(info Defining lib "$1") 3 | libs += $1 4 | $(eval $1.target ?= $(bld_root)/lib/lib$1.a) 5 | all: $(target) 6 | $(target): LDFLAGS = $($1.libs:%=-l%) 7 | $(target): $($1.src:$(src)/%.cpp=$(bld)/%.o) 8 | -include $($1.src:$(src)/%.cpp=$(bld)/%.d) 9 | endef 10 | 11 | lib = $(eval $(lib_impl)) 12 | 13 | -------------------------------------------------------------------------------- /tums/features/mkdir.mk: -------------------------------------------------------------------------------- 1 | .SECONDEXPANSION: 2 | mkdir = $$(@D)/. 3 | -------------------------------------------------------------------------------- /tums/features/subdirs.mk: -------------------------------------------------------------------------------- 1 | subdirs = $(eval include $(wildcard */rules.mk /dev/null)) 2 | -------------------------------------------------------------------------------- /tums/features/test.mk: -------------------------------------------------------------------------------- 1 | define test_impl 2 | $(call dbg,Defining test "$1".) 3 | $(call dbg_obj,$1) 4 | $(eval test: $(1:%=$(bld)/%.ok)) 5 | $(eval $1.src ?= $(wildcard $(src)/*Test.cpp)) 6 | $(eval $1.libs += gtest_main gtest gmock) 7 | $(exec_impl) 8 | $(eval tests += $(target)) 9 | endef 10 | 11 | test = $(eval $(test_impl)) 12 | 13 | -------------------------------------------------------------------------------- /tums/patterns/cpp.mk: -------------------------------------------------------------------------------- 1 | .SECONDEXPANSION: 2 | $(bld_root)/%.o: %.cpp $$(cpp_read_deps) | $(mkdir) 3 | $(COMPILE.cpp) $< -o $@ 4 | 5 | # $(warning deps: $^) 6 | -------------------------------------------------------------------------------- /tums/patterns/exec.mk: -------------------------------------------------------------------------------- 1 | $(execs): %: | $(mkdir) 2 | $(LINK.cpp) -o $@ $^ 3 | -------------------------------------------------------------------------------- /tums/patterns/lib.mk: -------------------------------------------------------------------------------- 1 | $(bld)/lib%.a: | $(mkdir) 2 | ar -r $@ $^ 3 | -------------------------------------------------------------------------------- /tums/patterns/mkdir.mk: -------------------------------------------------------------------------------- 1 | .PRECIOUS: %/. 2 | %/.: 3 | mkdir -p $(@D) 4 | -------------------------------------------------------------------------------- /tums/patterns/test.mk: -------------------------------------------------------------------------------- 1 | 2 | %.ok: % 3 | $< 4 | touch $@ 5 | -------------------------------------------------------------------------------- /tums/recurse.mk: -------------------------------------------------------------------------------- 1 | include */rules.mk 2 | -------------------------------------------------------------------------------- /tums/show_deps.mk: -------------------------------------------------------------------------------- 1 | %: 2 | @ echo $^ 3 | -------------------------------------------------------------------------------- /tums/top.mk: -------------------------------------------------------------------------------- 1 | $(warning hello) 2 | this_makefile = $(lastword $(MAKEFILE_LIST)) 3 | 4 | dirname = $(foreach d,$(dir $1),$(d:%/=%)) 5 | 6 | start_dir = $(shell pwd) 7 | 8 | $(warning start_dir = $(start_dir)) 9 | 10 | include $(this_makefile:%/top.mk=%/mkdir.mk) 11 | 12 | src_root = $(call dirname,$(call dirname,$(this_makefile))) 13 | bld_root = $(src_root)/bld 14 | 15 | top: all 16 | 17 | .DEFAULT: | $(bld_root)/. 18 | $(info target = $@) 19 | $(info $|) 20 | $(MAKE) -C $(bld_root) \ 21 | -I $(src_root)/make \ 22 | -f $(src_root)/make/rules.mk \ 23 | VPATH=$(src_root) \ 24 | $(start_dir)/$@ 25 | -------------------------------------------------------------------------------- /tums/tums.mk: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: 3 | 4 | .SECONDEXPANSION: 5 | 6 | src_root := . 7 | bld_root = bld 8 | 9 | $(warning src_root: $(realpath $(src_root))) 10 | $(warning bld_root: $(realpath $(bld_root))) 11 | $(if $(realpath $(bld_root)),,$(shell mkdir $(bld_root))) 12 | # $(warning subst: $(subst $(realpath $(src_root)),,$(realpath $(bld_root)))) 13 | 14 | $(if $(subst $(realpath $(src_root)),,$(realpath $(bld_root))),\ 15 | $(info paths ok),\ 16 | $(error paths bad: $(realpath $(src_root)) $(realpath $(bld_root)))) 17 | 18 | bin = $(bld_root)/bin 19 | 20 | stem = $1 21 | 22 | this_rules.mk = $(lastword $(filter %rules.mk,$(MAKEFILE_LIST))) 23 | # . = $(patsubst %/,%,$(dir $(this_rules.mk))) 24 | 25 | . = $(this_rules.mk:%/rules.mk=%) 26 | 27 | src = $(patsubst %/,%,$(dir $(this_rules.mk))) 28 | bld = $(bld_root)/$(src) 29 | 30 | include tums/features/*.mk 31 | 32 | deps = 33 | 34 | -include ./rules.mk 35 | 36 | stem = $* 37 | 38 | include tums/patterns/*.mk 39 | 40 | # -include $(deps) 41 | 42 | --------------------------------------------------------------------------------