├── CMakeLists.txt ├── README.md ├── bheap_aux.cpp ├── bsearch_array.cpp ├── heap_aux.cpp ├── heap_naive.cpp ├── linear_array.cpp ├── linked_list.cpp └── map.cpp /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(cache_friendly) 3 | set(CMAKE_CXX_STANDARD 17) 4 | 5 | add_executable(linked_list linked_list.cpp) 6 | add_executable(linear_array linear_array.cpp) 7 | add_executable(bsearch_array bsearch_array.cpp) 8 | add_executable(map map.cpp) 9 | add_executable(heap_aux heap_aux.cpp) 10 | add_executable(bheap_aux bheap_aux.cpp) 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cache-friendly-samples 2 | Code samples from the [code::dive 2019](https://codedive.pl) presentation "What do you mean by 'Cache Friendly'?" 3 | 4 | The slides are availablefrom [speakerdeck.com](https://speakerdeck.com/rollbear/code-dive-2019-what-do-you-mean-by-cache-friendly) 5 | 6 | Video recording is available here: 7 | ---------------------------------------------------------------------------| 8 | ![YouTube thumbnail](https://img.youtube.com/vi/Fzbotzi1gYs/mqdefault.jpg) | 9 | https://youtu.be/Fzbotzi1gYs | 10 | -------------------------------------------------------------------------------- /bheap_aux.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | typedef uint32_t (*timer_cb)(void*); 7 | 8 | template 9 | struct align_allocator 10 | { 11 | static_assert(N > 0); 12 | static_assert((N & (N - 1)) == 0, "size must be power of two"); 13 | template 14 | struct type { 15 | static_assert(N >= alignof(T)); 16 | 17 | using value_type = T; 18 | static constexpr std::align_val_t alignment{N}; 19 | T* allocate(size_t n) 20 | { 21 | return static_cast(operator new(n*sizeof(T), alignment)); 22 | } 23 | void deallocate(T* p, size_t) 24 | { 25 | operator delete(p, alignment); 26 | } 27 | }; 28 | }; 29 | 30 | class timeout_store 31 | { 32 | public: 33 | struct timer_data { 34 | uint32_t deadline; 35 | uint32_t action_index; 36 | }; 37 | void push(timer_data); 38 | const timer_data& top() const; 39 | void pop(); 40 | bool empty() const; 41 | private: 42 | static constexpr size_t block_size = 8; 43 | static constexpr size_t block_mask = block_size - 1; 44 | 45 | static size_t child_of(size_t idx); 46 | static size_t parent_of(size_t idx); 47 | static bool is_block_root(size_t idx); 48 | static size_t block_offset(size_t idx); 49 | static size_t block_base(size_t idx); 50 | static bool is_block_leaf(size_t idx); 51 | static size_t child_no(size_t idx); 52 | 53 | std::vector::type> bheap_store; 54 | }; 55 | 56 | inline size_t timeout_store::child_of(size_t idx) 57 | { 58 | if (!is_block_leaf(idx)) return idx + block_offset(idx); 59 | auto base = block_base(idx) + 1; 60 | return base * block_size + child_no(idx) * block_size * 2 + 1; 61 | } 62 | 63 | inline size_t timeout_store::parent_of(size_t idx) 64 | { 65 | auto const node_root = block_base(idx); 66 | if (!is_block_root(idx)) return node_root + block_offset(idx) / 2; 67 | auto parent_base = block_base(node_root / block_size - 1); 68 | auto child = ((idx - block_size) / block_size - parent_base) / 2; 69 | return parent_base + block_size / 2 + child; 70 | } 71 | 72 | inline bool timeout_store::is_block_root(size_t idx) 73 | { 74 | return block_offset(idx) == 1; 75 | } 76 | 77 | inline size_t timeout_store::block_offset(size_t idx) 78 | { 79 | return idx & block_mask; 80 | } 81 | 82 | inline size_t timeout_store::block_base(size_t idx) 83 | { 84 | return idx & ~block_mask; 85 | } 86 | 87 | inline bool timeout_store::is_block_leaf(size_t idx) 88 | { 89 | return (idx & (block_size >> 1)) != 0U; 90 | } 91 | 92 | inline size_t timeout_store::child_no(size_t idx) 93 | { 94 | return idx & (block_mask >> 1); 95 | } 96 | 97 | inline void timeout_store::push(timer_data d) 98 | { 99 | if ((bheap_store.size() & block_mask) == 0) { 100 | bheap_store.emplace_back(); 101 | } 102 | auto hole_idx = bheap_store.size(); 103 | bheap_store.emplace_back(); 104 | while (hole_idx != 1) { 105 | auto parent_idx = parent_of(hole_idx); 106 | auto& parent_data = bheap_store[parent_idx]; 107 | if (parent_data.deadline < d.deadline) break; 108 | bheap_store[hole_idx] = parent_data; 109 | hole_idx = parent_idx; 110 | } 111 | bheap_store[hole_idx] = d; 112 | } 113 | 114 | inline const timeout_store::timer_data& timeout_store::top() const 115 | { 116 | return bheap_store[1]; 117 | } 118 | 119 | inline void timeout_store::pop() 120 | { 121 | size_t idx = 1; 122 | const auto last_idx = bheap_store.size() - 1; 123 | for (;;) { 124 | auto left_child = child_of(idx); 125 | if (left_child > last_idx) break; 126 | auto const sibling_offset = is_block_leaf(idx) ? block_size : 1; 127 | auto right_child = left_child + sibling_offset; 128 | bool go_right = right_child < last_idx && bheap_store[right_child].deadline < bheap_store[left_child].deadline; 129 | auto next_child = left_child + go_right*sibling_offset; 130 | bheap_store[idx] = bheap_store[next_child]; 131 | idx = next_child; 132 | } 133 | if (idx != last_idx) { 134 | auto last = bheap_store.back(); 135 | while (idx != 1) { 136 | auto parent_idx = parent_of(idx); 137 | if (bheap_store[parent_idx].deadline < last.deadline) break; 138 | bheap_store[idx] = bheap_store[parent_idx]; 139 | idx = parent_idx; 140 | } 141 | bheap_store[idx] = last; 142 | } 143 | bheap_store.resize(last_idx - ((last_idx & block_mask) == 1)); 144 | } 145 | 146 | inline bool timeout_store::empty() const 147 | { 148 | return bheap_store.empty(); 149 | } 150 | 151 | struct timer_action { 152 | timer_cb callback; 153 | void* userp; 154 | }; 155 | 156 | struct action_store 157 | { 158 | union timer_store 159 | { 160 | uint32_t next_free; 161 | timer_action cb; 162 | }; 163 | uint32_t push(timer_cb cb, void* userp) 164 | { 165 | if (first_free == data.size()) { 166 | timer_store st; 167 | st.cb.callback = cb; 168 | st.cb.userp = userp; 169 | 170 | data.push_back(st); 171 | return std::exchange(first_free, first_free+1); 172 | } 173 | else 174 | { 175 | auto idx = std::exchange(first_free, data[first_free].next_free); 176 | data[idx].cb.callback = cb; 177 | data[idx].cb.userp = userp; 178 | return idx; 179 | } 180 | } 181 | void remove(uint32_t idx) 182 | { 183 | data[idx].next_free = first_free; 184 | first_free = idx; 185 | } 186 | void cancel(uint32_t idx) 187 | { 188 | data[idx].cb.callback = nullptr; 189 | } 190 | const timer_action& operator[](uint32_t idx) const 191 | { 192 | return data[idx].cb; 193 | } 194 | uint32_t first_free = 0; 195 | std::vector data; 196 | }; 197 | 198 | static timeout_store timeouts; 199 | static action_store actions; 200 | 201 | using timer = uint32_t; 202 | 203 | timer schedule_timer(uint32_t deadline, timer_cb cb, void* userp) 204 | { 205 | auto action_index = actions.push(cb, userp); 206 | timeouts.push({deadline, action_index}); 207 | return action_index; 208 | } 209 | 210 | void cancel_timer(timer t) 211 | { 212 | actions.cancel(t); 213 | } 214 | 215 | bool shoot_first() 216 | { 217 | while (!timeouts.empty()) { 218 | auto& t = timeouts.top(); 219 | auto& action = actions[t.action_index]; 220 | if (action.callback) break; 221 | actions.remove(t.action_index); 222 | timeouts.pop(); 223 | } 224 | if (timeouts.empty()) return false; 225 | auto& t = timeouts.top(); 226 | auto& action = actions[t.action_index]; 227 | action.callback(action.userp); 228 | actions.remove(t.action_index); 229 | timeouts.pop(); 230 | return true; 231 | } 232 | 233 | int main() 234 | { 235 | std::random_device rd; 236 | std::mt19937 gen(rd()); 237 | std::uniform_int_distribution dist; 238 | 239 | for (int k = 0; k < 1000; ++k) { 240 | timer prev{}; 241 | for (int i = 0; i < 20'000; ++i) { 242 | auto deadline = dist(gen); 243 | timer t = schedule_timer(deadline, 244 | [](void*) {return 0U;}, 245 | reinterpret_cast(deadline)); 246 | if (i & 1) cancel_timer(prev); 247 | prev = t; 248 | } 249 | int i = 0; 250 | while (shoot_first()) 251 | ++i; 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /bsearch_array.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | typedef uint32_t (*timer_cb)(void*); 8 | struct timer_data { 9 | uint32_t deadline; 10 | uint32_t id; 11 | void* userp; 12 | timer_cb callback; 13 | }; 14 | 15 | struct timer 16 | { 17 | uint32_t deadline; 18 | uint32_t id; 19 | }; 20 | 21 | static std::vector timeouts; 22 | static uint32_t next_id = 0; 23 | 24 | static bool is_after(const timer_data& lh, const timer_data& rh) 25 | { 26 | return lh.deadline < rh.deadline; 27 | } 28 | 29 | 30 | timer schedule_timer(uint32_t deadline, timer_cb cb, void* userp) 31 | { 32 | timer_data element{deadline, next_id, userp, cb}; 33 | auto i = std::lower_bound(timeouts.begin(), timeouts.end(), 34 | element, is_after); 35 | timeouts.insert(i, element); 36 | return {deadline, next_id++}; 37 | } 38 | 39 | void cancel_timer(timer t) 40 | { 41 | timer_data element{t.deadline, t.id, nullptr, nullptr}; 42 | auto [lo, hi] = std::equal_range(timeouts.begin(), timeouts.end(), 43 | element, is_after); 44 | auto i = std::find_if(lo, hi, 45 | [t](const auto& e) { return e.id == t.id; }); 46 | if (i != hi) { 47 | std::move(i + 1, timeouts.end(), i); 48 | timeouts.pop_back(); 49 | } 50 | } 51 | 52 | bool shoot_first() 53 | { 54 | if (timeouts.empty()) return false; 55 | timeouts.front().callback(timeouts.front().userp); 56 | timeouts.erase(timeouts.begin()); 57 | return true; 58 | } 59 | 60 | int main() 61 | { 62 | std::random_device rd; 63 | std::mt19937 gen(rd()); 64 | std::uniform_int_distribution dist; 65 | 66 | for (int k = 0; k < 10; ++k) { 67 | timer prev{}; 68 | for (int i = 0; i < 20'000; ++i) { 69 | timer t = schedule_timer(dist(gen), [](void*){return 0U;}, nullptr); 70 | if (i & 1) cancel_timer(prev); 71 | prev = t; 72 | } 73 | while (shoot_first()) 74 | ; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /heap_aux.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | typedef uint32_t (*timer_cb)(void*); 6 | struct timer_data { 7 | uint32_t deadline; 8 | uint32_t action_index; 9 | }; 10 | 11 | struct timer_action { 12 | timer_cb callback; 13 | void* userp; 14 | }; 15 | 16 | struct is_after { 17 | bool operator()(const timer_data& lh, const timer_data& rh) const 18 | { 19 | return lh.deadline < rh.deadline; 20 | } 21 | }; 22 | 23 | 24 | static 25 | std::priority_queue, is_after> timeouts; 26 | 27 | struct action_store 28 | { 29 | union timer_store 30 | { 31 | uint32_t next_free; 32 | timer_action cb; 33 | }; 34 | uint32_t push(timer_cb cb, void* userp) 35 | { 36 | if (first_free == data.size()) { 37 | timer_store st; 38 | st.cb.callback = cb; 39 | st.cb.userp = userp; 40 | 41 | data.push_back(st); 42 | return std::exchange(first_free, first_free+1); 43 | } 44 | else 45 | { 46 | auto idx = std::exchange(first_free, data[first_free].next_free); 47 | data[idx].cb.callback = cb; 48 | data[idx].cb.userp = userp; 49 | return idx; 50 | } 51 | } 52 | void remove(uint32_t idx) 53 | { 54 | data[idx].next_free = first_free; 55 | first_free = idx; 56 | } 57 | void cancel(uint32_t idx) 58 | { 59 | data[idx].cb.callback = nullptr; 60 | } 61 | const timer_action& operator[](uint32_t idx) const 62 | { 63 | return data[idx].cb; 64 | } 65 | uint32_t first_free = 0; 66 | std::vector data; 67 | }; 68 | 69 | static action_store actions; 70 | 71 | using timer = uint32_t; 72 | 73 | timer schedule_timer(uint32_t deadline, timer_cb cb, void* userp) 74 | { 75 | auto action_index = actions.push(cb, userp); 76 | timeouts.push(timer_data{deadline, action_index}); 77 | return action_index; 78 | } 79 | 80 | void cancel_timer(timer t) 81 | { 82 | actions.cancel(t); 83 | } 84 | 85 | bool shoot_first() 86 | { 87 | while (!timeouts.empty()) { 88 | auto& t = timeouts.top(); 89 | auto& action = actions[t.action_index]; 90 | if (action.callback) break; 91 | actions.remove(t.action_index); 92 | timeouts.pop(); 93 | } 94 | if (timeouts.empty()) return false; 95 | auto& t = timeouts.top(); 96 | auto& action = actions[t.action_index]; 97 | action.callback(action.userp); 98 | actions.remove(t.action_index); 99 | timeouts.pop(); 100 | return true; 101 | } 102 | 103 | int main() 104 | { 105 | std::random_device rd; 106 | std::mt19937 gen(rd()); 107 | std::uniform_int_distribution dist; 108 | 109 | for (int k = 0; k < 1000; ++k) { 110 | timer prev{}; 111 | for (int i = 0; i < 20'000; ++i) { 112 | timer t = schedule_timer(dist(gen), [](void*){return 0U;}, nullptr); 113 | if (i & 1) cancel_timer(prev); 114 | prev = t; 115 | } 116 | while (shoot_first()) 117 | ; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /heap_naive.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | extern "C" { 7 | typedef uint32_t (*timer_cb)(void*); 8 | using timer = uint32_t; 9 | struct timer_data { 10 | uint32_t deadline; 11 | uint32_t id; 12 | void* userp; 13 | timer_cb callback; 14 | }; 15 | timer next_id = 0; 16 | 17 | bool is_before(const timer_data& lh, const timer_data& rh) 18 | { 19 | return lh.deadline < rh.deadline; 20 | } 21 | 22 | static std::vector timeouts; 23 | 24 | 25 | timer schedule_timer(uint32_t deadline, timer_cb cb, void* userp) 26 | { 27 | timeouts.push_back(timer_data{deadline, next_id, userp, cb}); 28 | std::push_heap(timeouts.begin(), timeouts.end(), is_before); 29 | return next_id++; 30 | } 31 | 32 | void cancel_timer(timer t) 33 | { 34 | auto i = std::find_if(timeouts.begin(), timeouts.end(), 35 | [t](const auto& e) { return e.id == t; }); 36 | if (i != timeouts.end()) i->callback = nullptr; 37 | } 38 | 39 | bool shoot_first() 40 | { 41 | while (!timeouts.empty() && timeouts.front().callback == nullptr) { 42 | std::pop_heap(timeouts.begin(), timeouts.end(), is_before); 43 | timeouts.pop_back(); 44 | } 45 | if (timeouts.empty()) return false; 46 | timeouts.front().callback(timeouts.front().userp); 47 | std::pop_heap(timeouts.begin(), timeouts.end(), is_before); 48 | timeouts.pop_back(); 49 | return true; 50 | } 51 | } 52 | int main() 53 | { 54 | std::random_device rd; 55 | std::mt19937 gen(rd()); 56 | std::uniform_int_distribution dist; 57 | 58 | for (int k = 0; k < 10; ++k) { 59 | timer prev{}; 60 | for (int i = 0; i < 20'000; ++i) { 61 | timer t = schedule_timer(dist(gen), [](void*){return 0U;}, nullptr); 62 | if (i & 1) cancel_timer(prev); 63 | prev = t; 64 | } 65 | while (shoot_first()) 66 | ; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /linear_array.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | typedef uint32_t (*timer_cb)(void*); 7 | struct timer_data { 8 | uint32_t deadline; 9 | uint32_t id; 10 | void* userp; 11 | timer_cb callback; 12 | }; 13 | 14 | typedef uint32_t timer; 15 | 16 | static std::vector timeouts; 17 | static uint32_t next_id = 0; 18 | 19 | static bool is_after(uint32_t lh, uint32_t rh) 20 | { 21 | return lh < rh; 22 | } 23 | 24 | timer schedule_timer(uint32_t deadline, timer_cb cb, void* userp) 25 | { 26 | auto idx = timeouts.size(); 27 | timeouts.push_back({}); 28 | while (idx > 0 && is_after(timeouts[idx-1].deadline, deadline)) { 29 | timeouts[idx] = std::move(timeouts[idx-1]); 30 | --idx; 31 | } 32 | timeouts[idx] = timer_data{deadline, next_id++, userp, cb }; 33 | return next_id; 34 | } 35 | 36 | void cancel_timer(timer t) 37 | { 38 | auto i = std::find_if(timeouts.begin(), timeouts.end(), 39 | [t](const auto& e) { return e.id == t; }); 40 | timeouts.erase(i); 41 | } 42 | 43 | bool shoot_first() 44 | { 45 | if (timeouts.empty()) return false; 46 | timeouts.front().callback(timeouts.front().userp); 47 | timeouts.erase(timeouts.begin()); 48 | return true; 49 | } 50 | 51 | int main() 52 | { 53 | std::random_device rd; 54 | std::mt19937 gen(rd()); 55 | std::uniform_int_distribution dist; 56 | 57 | for (int k = 0; k < 10; ++k) { 58 | timer prev{}; 59 | for (int i = 0; i < 20'000; ++i) { 60 | timer t = schedule_timer(dist(gen), [](void*){return 0U;}, nullptr); 61 | if (i & 1) cancel_timer(prev); 62 | prev = t; 63 | } 64 | while (shoot_first()) 65 | ; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /linked_list.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | 5 | typedef uint32_t (*timer_cb)(void*); 6 | struct timer { 7 | uint32_t deadline; 8 | void* userp; 9 | timer_cb callback; 10 | struct timer* next; 11 | struct timer* prev; 12 | }; 13 | 14 | static timer timeouts = { 0, NULL, NULL, &timeouts, &timeouts }; 15 | 16 | static 17 | timer* add_behind(timer* anchor, uint32_t deadline, timer_cb cb, void* userp) 18 | { 19 | timer* t = (timer*)malloc(sizeof(timer)); 20 | t->deadline = deadline; 21 | t->userp = userp; 22 | t->callback = cb; 23 | t->prev = anchor; 24 | t->next = anchor->next; 25 | anchor->next->prev = t; 26 | anchor->next = t; 27 | return t; 28 | } 29 | 30 | static bool is_after(uint32_t lh, uint32_t rh) 31 | { 32 | return lh < rh; 33 | } 34 | 35 | timer* schedule_timer(uint32_t deadline, timer_cb cb, void* userp) 36 | { 37 | timer* iter = timeouts.prev; 38 | while (iter != &timeouts && 39 | is_after(iter->deadline, deadline)) 40 | iter = iter->prev; 41 | return add_behind(iter, deadline, cb, userp); 42 | } 43 | 44 | void cancel_timer(timer* t) 45 | { 46 | t->next->prev = t->prev; 47 | t->prev->next = t->next; 48 | free(t); 49 | } 50 | 51 | bool shoot_first() 52 | { 53 | if (timeouts.next == &timeouts) return false; 54 | timer* t = timeouts.next; 55 | t->callback(t->userp); 56 | cancel_timer(t); 57 | return true; 58 | } 59 | 60 | int main() 61 | { 62 | std::random_device rd; 63 | std::mt19937 gen(rd()); 64 | std::uniform_int_distribution dist; 65 | 66 | for (int k = 0; k < 10; ++k) { 67 | timer* prev = nullptr; 68 | for (int i = 0; i < 20'000; ++i) { 69 | timer* t = schedule_timer(dist(gen), [](void*){return 0U;}, nullptr); 70 | if (i & 1) cancel_timer(prev); 71 | prev = t; 72 | } 73 | while (shoot_first()) 74 | ; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /map.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | typedef uint32_t (*timer_cb)(void*); 6 | struct timer_data { 7 | void* userp; 8 | timer_cb callback; 9 | }; 10 | 11 | struct is_after { 12 | bool operator()(uint32_t lh, uint32_t rh) const 13 | { 14 | return lh < rh; 15 | } 16 | }; 17 | 18 | using timer_map = std::multimap; 19 | using timer = timer_map::iterator; 20 | 21 | static timer_map timeouts; 22 | 23 | 24 | timer schedule_timer(uint32_t deadline, timer_cb cb, void* userp) 25 | { 26 | return timeouts.insert(std::make_pair(deadline, timer_data{userp, cb})); 27 | } 28 | 29 | void cancel_timer(timer t) 30 | { 31 | timeouts.erase(t); 32 | } 33 | 34 | bool shoot_first() 35 | { 36 | if (timeouts.empty()) return false; 37 | auto i = timeouts.begin(); 38 | i->second.callback(i->second.userp); 39 | timeouts.erase(i); 40 | return true; 41 | } 42 | 43 | int main() 44 | { 45 | std::random_device rd; 46 | std::mt19937 gen(rd()); 47 | std::uniform_int_distribution dist; 48 | 49 | for (int k = 0; k < 10; ++k) { 50 | timer prev{}; 51 | for (int i = 0; i < 20'000; ++i) { 52 | timer t = schedule_timer(dist(gen), [](void*){return 0U;}, nullptr); 53 | if (i & 1) cancel_timer(prev); 54 | prev = t; 55 | } 56 | while (shoot_first()) 57 | ; 58 | } 59 | } 60 | --------------------------------------------------------------------------------