├── .gitignore
├── CMakeLists.txt
├── LICENSE
├── README.md
├── fptree.cpp
├── fptree.h
├── fptree_wrapper.cpp
├── fptree_wrapper.hpp
└── inspector.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | main.cpp
2 | makefile
3 |
4 | # Prerequisites
5 | *.d
6 |
7 | # Compiled Object files
8 | *.slo
9 | *.lo
10 | *.o
11 | *.obj
12 |
13 | # Precompiled Headers
14 | *.gch
15 | *.pch
16 |
17 | # Compiled Dynamic libraries
18 | *.so
19 | *.dylib
20 | *.dll
21 |
22 | # Fortran module files
23 | *.mod
24 | *.smod
25 |
26 | # Compiled Static libraries
27 | *.lai
28 | *.la
29 | *.a
30 | *.lib
31 |
32 | # Executables
33 | *.exe
34 | *.out
35 | *.app
36 |
37 | bitset
38 | build
39 | oneTBB
40 | Release
41 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.14)
2 | include(FetchContent)
3 | project(fptree)
4 |
5 | set(CMAKE_CXX_STANDARD 17)
6 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
7 | set(CMAKE_BUILD_TYPE Release)
8 |
9 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src)
10 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src)
11 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src)
12 |
13 |
14 | if(${CMAKE_BUILD_TYPE} STREQUAL "Release")
15 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -std=c++17 -lpmem -lpmemobj -ljemalloc -mavx512f -mavx512vl -mavx512bw -mavx512dq -mavx512cd -mrtm -ltbb -pthread")
16 | else()
17 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O1 -g2 -std=c++17 -lpmem -lpmemobj -mavx512f -mavx512vl -mavx512bw -mavx512dq -mavx512cd -mrtm -ltbb -pthread")
18 | endif()
19 |
20 |
21 | set(PMEM_BACKEND "PMEM" CACHE STRING "Persistent memory backend type")
22 |
23 | if(${PMEM_BACKEND} STREQUAL "PMEM")
24 | add_definitions(-DPMEM)
25 | message(STATUS "Persistence support: PMEM")
26 | elseif(${PMEM_BACKEND} STREQUAL "DRAM")
27 | message(STATUS "Persistence support: off")
28 | else()
29 | message(FATAL_ERROR "Unsupported persistent memory backend: ${PMEM_BACKEND}")
30 | endif()
31 |
32 | option(BUILD_INSPECTOR "Build inspector to check correctness of single/multi-thread operation" ON)
33 |
34 | option(TEST_MODE "Test mode will test leaf nodes and inner node smaller to help debug" ON)
35 |
36 | option(NDEBUG "Disable assert statements" ON)
37 |
38 |
39 | if(${TEST_MODE})
40 | add_definitions(-DTEST_MODE)
41 | message(STATUS "TEST_MODE: defined")
42 | else()
43 | message(STATUS "TEST_MODE: not defined")
44 | endif()
45 |
46 |
47 | if(${BUILD_INSPECTOR})
48 | add_definitions(-DBUILD_INSPECTOR)
49 | message(STATUS "BUILD_INSPECTOR: defined")
50 | add_executable(
51 | inspector
52 | fptree.cpp
53 | fptree.h
54 | inspector.cpp
55 | )
56 | else()
57 | message(STATUS "BUILD_INSPECTOR: not defined")
58 | endif()
59 |
60 | ##################### PiBench #########################
61 | FetchContent_Declare(
62 | pibench
63 | GIT_REPOSITORY https://github.com/sfu-dis/pibench.git
64 | GIT_TAG master
65 | )
66 | if (NOT pibench_POPULATED)
67 | FetchContent_Populate(pibench)
68 | include_directories(${pibench_SOURCE_DIR}/include)
69 | endif ()
70 |
71 |
72 | add_library(fptree_pibench_wrapper SHARED fptree_wrapper.cpp
73 | fptree.cpp)
74 |
75 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Data-Intensive Systems Lab at Simon Fraser University
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FPTree
2 | An open-source [FPTree](https://wwwdb.inf.tu-dresden.de/misc/papers/2016/Oukid_FPTree.pdf) implementation
3 |
4 | ```
5 | I. Oukid, J. Lasperas, A. Nica, T. Willhalm, and W. Lehner. FPTree: A Hybrid SCM-DRAM Persistent and Concurrent B-Tree for Storage Class Memory.
6 | In Proceedings of the 2016 International Conference on Management of Data, SIGMOD’16, pages 371–386. ACM, 2016
7 | ```
8 |
9 |
10 | ## Important information before build !
11 | FPTree use Intel Threading Building Blocks (oneTBB) for concurrency control.
12 | The default retry threshold for oneTBB is only 10 for read write mutex.
13 | 1. To achieve better scalability, we are using customized TBB library for FPTree
14 | (which is also the approach taken by the original author).
Here are the steps to generate libtbb.so:
15 | * Clone oneTBB from github (https://github.com/oneapi-src/oneTBB.git)
**to this repo** (i.e., /path/to/your/fptree/oneTBB).
16 | * Modify the read/write retry from **10 to 256** in `oneTBB/src/tbb/rtm_mutex.cpp` and `oneTBB/src/tbb/rtm_rw_mutex.cpp`
17 | * `cd oneTBB && mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j`
18 | * Check that libtbb.so exists in *oneTBB/build/gnu_11.1_cxx11_64_release*
19 | * Modify **CMakeLists.txt** located in FPTree folder to use custom TBB
20 | * delete -ltbb flag in CMAKE_CXX_FLAGS which link to your default TBB built
21 | * add and modify them in the proper place in CMakeLists.txt
22 | ```
23 | link_directories(oneTBB/build/gnu_11.1_cxx11_64_release) # gnu version and cxx version could vary
24 | include_directories(oneTBB/include)
25 | ```
26 | * After the line that adds fptree pibench wrapper library, do target link below.
27 | ```
28 | target_link_libraries(fptree_pibench_wrapper libtbb.so)
29 | ```
30 | * Change header files in fptree.h to include those from custom tbb like this:
31 | ```
32 | // #include
33 | // #include
34 | #include "oneapi/tbb/spin_mutex.h"
35 | #include "oneapi/tbb/spin_rw_mutex.h"
36 | ```
37 | 2. Modify `#define PMEMOBJ_POOL_SIZE` in fptree.h if BACKEND = PMEM (defined in CMakeLists.txt)
38 | 3. Modify `#define MAX_INNER_SIZE 128` and `#define MAX_LEAF_SIZE 64` in fptree.h if you want. These are tunable variable.
39 | 4. To use HTM, you will need to turn on TSX on your machine. If you execute `lscpu` and see `Vulnerability Tsx async abort: Vulnerable`, then TSX is turned on. Otherwise, here is an example of how to turn on TSX on archlinux.
40 | * Make sure everything's up-to-date and consistent. Use pacman to do an update.
41 | * Add this line to /etc/default/grub:
42 | ```GRUB_CMDLINE_LINUX_DEFAULT="tsx=on tsx_async_abort=off loglevel=3 quiet"```
43 | * Run these commands:
44 | ```
45 | grub-mkconfig -o /boot/grub/grub.cfg
46 | systemctl reboot
47 | ```
48 | ## Build (check out the next section for running pibench with the fptree wrapper)
49 |
50 | ### Build PMEM Version
51 |
52 | ```bash
53 | mkdir build && cd build
54 | cmake -DPMEM_BACKEND=PMEM ..
55 | ```
56 |
57 | ### Build DRAM Version
58 |
59 | ```bash
60 | mkdir build && cd build
61 | cmake -DPMEM_BACKEND=DRAM ..
62 | ```
63 |
64 | All executables are in `build/src` folder
65 |
66 | #### Inspector executable
67 | ```bash
68 | mkdir build && cd build
69 | cmake -DPMEM_BACKEND=${BACKEND} -DBUILD_INSPECTOR=1 ..
70 | ```
71 |
72 | Run `inspector` to check the correctness of single/multi-threaded insert, delete operations for both leaf nodes and inner nodes
73 |
74 | If you want to check performance of this implementation, please see PiBench instruction below
75 |
76 | #### Other build options
77 | `-DBUILD_INSPECTOR=1` to build inspector executable which can check the correctness of single/multi-threaded operations (insert, delete..)
78 |
79 | `-DTEST_MODE=1` to set the size of leaf nodes & inner nodes. (TEST MODE: MAX_INNER_SIZE=3 MAX_LEAF_SIZE=4 for debug usage)
80 |
81 | ## Benchmark on PiBench
82 |
83 | We officially support FPTree wrapper for pibench:
84 |
85 | Checkout PiBench here: https://github.com/sfu-dis/pibench
86 |
87 | ### Build/Create FPTree shared lib
88 |
89 | ```bash
90 | mkdir Release && cd Release
91 | cmake -DPMEM_BACKEND= -DTEST_MODE=0 -DBUILD_INSPECTOR=0 ..
92 | ```
93 |
94 | ### Troubleshooting
95 | (1) If you see the error below when you try to run PiBench with this wrapper:
96 | ```
97 | Error in dlopen(): /lib/x86_64-linux-gnu/libjemalloc.so.2: cannot allocate memory in static TLS block
98 | ```
99 | You can try adding `LD_PRELOAD` before the PiBench executable:
100 | ```
101 | LD_PRELOAD=/path/to/your/libjemalloc.so ./PiBench ...
102 | ```
103 | (2) `libpmemobj.so` error.
104 | ```
105 | Error in dlopen(): /path/to/libfptree_pibench_wrapper.so: undefined symbol: _pobj_cache_invalidate
106 | ```
107 | Add `/path/to/libpmemobj.so` to `LD_PRELOAD`.
108 |
--------------------------------------------------------------------------------
/fptree.cpp:
--------------------------------------------------------------------------------
1 | // Copyright (c) Simon Fraser University. All rights reserved.
2 | // Licensed under the MIT license.
3 | //
4 | // Authors:
5 | // George He
6 | // Duo Lu
7 | // Tianzheng Wang
8 |
9 | #include "fptree.h"
10 |
11 | #ifdef PMEM
12 | inline bool file_pool_exists(const std::string& name)
13 | {
14 | return ( access( name.c_str(), F_OK ) != -1 );
15 | }
16 | #endif
17 |
18 | BaseNode::BaseNode()
19 | {
20 | this->isInnerNode = false;
21 | }
22 |
23 | InnerNode::InnerNode()
24 | {
25 | this->isInnerNode = true;
26 | this->nKey = 0;
27 | }
28 |
29 | InnerNode::InnerNode(uint64_t key, BaseNode* left, BaseNode* right)
30 | {
31 | this->isInnerNode = true;
32 | this->keys[0] = key;
33 | this->p_children[0] = left;
34 | this->p_children[1] = right;
35 | this->nKey = 1;
36 | }
37 |
38 | void InnerNode::init(uint64_t key, BaseNode* left, BaseNode* right)
39 | {
40 | this->isInnerNode = true;
41 | this->keys[0] = key;
42 | this->p_children[0] = left;
43 | this->p_children[1] = right;
44 | this->nKey = 1;
45 | }
46 |
47 | InnerNode::InnerNode(const InnerNode& inner)
48 | {
49 | memcpy(this, &inner, sizeof(struct InnerNode));
50 | }
51 |
52 | InnerNode::~InnerNode()
53 | {
54 | for (size_t i = 0; i < this->nKey; i++) { delete this->p_children[i]; }
55 | }
56 |
57 | #ifndef PMEM
58 | LeafNode::LeafNode()
59 | {
60 | this->isInnerNode = false;
61 | this->bitmap.clear();
62 | this->p_next = nullptr;
63 | this->lock.store(0, std::memory_order_acquire);
64 | }
65 |
66 | LeafNode::LeafNode(const LeafNode& leaf)
67 | {
68 | memcpy(this, &leaf, sizeof(struct LeafNode));
69 | }
70 |
71 | LeafNode& LeafNode::operator=(const LeafNode& leaf)
72 | {
73 | memcpy(this, &leaf, sizeof(struct LeafNode));
74 | return *this;
75 | }
76 | #endif
77 |
78 | void InnerNode::removeKey(uint64_t index, bool remove_right_child = true)
79 | {
80 | assert(this->nKey > index && "Remove key index out of range!");
81 | this->nKey--;
82 | std::memmove(this->keys + index, this->keys + index + 1, (this->nKey-index)*sizeof(uint64_t));
83 | if (remove_right_child)
84 | index ++;
85 | std::memmove(this->p_children + index, this->p_children + index + 1, (this->nKey - index + 1)*sizeof(BaseNode*));
86 | }
87 |
88 | void InnerNode::addKey(uint64_t index, uint64_t key, BaseNode* child, bool add_child_right = true)
89 | {
90 | assert(this->nKey >= index && "Insert key index out of range!");
91 | std::memmove(this->keys+index+1, this->keys+index, (this->nKey-index)*sizeof(uint64_t)); // move keys
92 | this->keys[index] = key;
93 | if (add_child_right)
94 | index ++;
95 | std::memmove(this->p_children+index+1, this->p_children+index, (this->nKey-index+1)*sizeof(BaseNode*));
96 | this->p_children[index] = child;
97 | this->nKey++;
98 | }
99 |
100 | inline uint64_t InnerNode::findChildIndex(uint64_t key)
101 | {
102 | auto lower = std::lower_bound(this->keys, this->keys + this->nKey, key);
103 | uint64_t idx = lower - this->keys;
104 | if (idx < this->nKey && *lower == key)
105 | idx++;
106 | return idx;
107 | }
108 |
109 | inline void LeafNode::addKV(struct KV kv)
110 | {
111 | uint64_t idx = this->bitmap.first_zero();
112 | assert(idx < MAX_LEAF_SIZE && "Insert kv out of bound!");
113 | this->fingerprints[idx] = getOneByteHash(kv.key);
114 | this->kv_pairs[idx] = kv;
115 | this->bitmap.set(idx);
116 | }
117 |
118 | inline uint64_t LeafNode::findKVIndex(uint64_t key)
119 | {
120 | size_t key_hash = getOneByteHash(key);
121 | for (uint64_t i = 0; i < MAX_LEAF_SIZE; i++)
122 | {
123 | if (this->bitmap.test(i) == 1 &&
124 | this->fingerprints[i] == key_hash &&
125 | this->kv_pairs[i].key == key)
126 | {
127 | return i;
128 | }
129 | }
130 | return MAX_LEAF_SIZE;
131 | }
132 |
133 | uint64_t LeafNode::minKey()
134 | {
135 | uint64_t min_key = -1, i = 0;
136 | for (; i < MAX_LEAF_SIZE; i++)
137 | {
138 | if (this->bitmap.test(i) && this->kv_pairs[i].key < min_key)
139 | min_key = this->kv_pairs[i].key;
140 | }
141 | assert(min_key != -1 && "minKey called for empty leaf!");
142 | return min_key;
143 | }
144 |
145 | void LeafNode::getStat(uint64_t key, LeafNodeStat& lstat)
146 | {
147 | lstat.count = 0;
148 | lstat.min_key = -1;
149 | lstat.kv_idx = MAX_LEAF_SIZE;
150 |
151 | uint64_t cur_key = -1;
152 | for (size_t counter = 0; counter < MAX_LEAF_SIZE; counter ++)
153 | {
154 | if (this->bitmap.test(counter)) // if find a valid entry
155 | {
156 | lstat.count ++;
157 | cur_key = this->kv_pairs[counter].key;
158 | if (cur_key == key) // if the entry is key
159 | lstat.kv_idx = counter;
160 | else if (cur_key < lstat.min_key)
161 | lstat.min_key = cur_key;
162 | }
163 | }
164 | }
165 |
166 | inline LeafNode* FPtree::maxLeaf(BaseNode* node)
167 | {
168 | while(node->isInnerNode)
169 | {
170 | node = reinterpret_cast (node)->p_children[reinterpret_cast (node)->nKey];
171 | }
172 | return reinterpret_cast (node);
173 | }
174 |
175 | #ifdef PMEM
176 | static TOID(struct Log) allocLogArray()
177 | {
178 | TOID(struct Log) array = POBJ_ROOT(pop, struct Log);
179 |
180 | POBJ_ALLOC(pop, &array, struct Log, sizeof(struct Log) * sizeLogArray,
181 | NULL, NULL);
182 |
183 | if (TOID_IS_NULL(array)) { fprintf(stderr, "POBJ_ALLOC\n"); return OID_NULL; }
184 |
185 | for (uint64_t i = 0; i < sizeLogArray; i++)
186 | {
187 | if (POBJ_ALLOC(pop, &D_RW(array)[i],
188 | struct Log, sizeof(struct Log),
189 | NULL, NULL))
190 | {
191 | fprintf(stderr, "pmemobj_alloc\n");
192 | }
193 | }
194 | return array.oid;
195 | }
196 |
197 | static TOID(struct Log) root_LogArray;
198 |
199 | void FPtree::recover()
200 | {
201 | root_LogArray = POBJ_ROOT(pop, struct Log);
202 | for (uint64_t i = 1; i < sizeLogArray / 2; i++)
203 | {
204 | recoverSplit(&D_RW(root_LogArray)[i]);
205 | }
206 | for (uint64_t i = sizeLogArray / 2; i < sizeLogArray; i++)
207 | {
208 | recoverDelete(&D_RW(root_LogArray)[i]);
209 | }
210 | }
211 |
212 | void FPtree::pmemInit(const char* path_ptr, long long pool_size)
213 | {
214 | if (file_pool_exists(path_ptr) == 0)
215 | {
216 | if ((pop = pmemobj_create(path_ptr, POBJ_LAYOUT_NAME(FPtree), pool_size, 0666)) == NULL)
217 | perror("failed to create pool\n");
218 | root_LogArray = allocLogArray();
219 | }
220 | else
221 | {
222 | if ((pop = pmemobj_open(path_ptr, POBJ_LAYOUT_NAME(FPtree))) == NULL)
223 | perror("failed to open pool\n");
224 | else
225 | {
226 | recover();
227 | bulkLoad(1);
228 | }
229 | }
230 | root_LogArray = POBJ_ROOT(pop, struct Log); // Avoid push root object to Queue, i = 1
231 | for (uint64_t i = 1; i < sizeLogArray / 2; i++) // push persistent array to splitLogQueue
232 | {
233 | D_RW(root_LogArray)[i].PCurrentLeaf = OID_NULL;
234 | D_RW(root_LogArray)[i].PLeaf = OID_NULL;
235 | splitLogQueue.push(&D_RW(root_LogArray)[i]);
236 | }
237 | for (uint64_t i = sizeLogArray / 2; i < sizeLogArray; i++) // second half of array use as delete log
238 | {
239 | D_RW(root_LogArray)[i].PCurrentLeaf = OID_NULL;
240 | D_RW(root_LogArray)[i].PLeaf = OID_NULL;
241 | deleteLogQueue.push(&D_RW(root_LogArray)[i]);
242 | }
243 | }
244 |
245 | #endif
246 |
247 | FPtree::FPtree()
248 | {
249 | root = nullptr;
250 | #ifndef PMEM
251 | bitmap_idx = MAX_LEAF_SIZE;
252 | #endif
253 | }
254 |
255 |
256 | FPtree::~FPtree()
257 | {
258 | #ifdef PMEM
259 | pmemobj_close(pop);
260 | #else
261 | if (root != nullptr)
262 | delete root;
263 | #endif
264 | }
265 |
266 |
267 | inline static uint8_t getOneByteHash(uint64_t key)
268 | {
269 | uint8_t oneByteHashKey = std::_Hash_bytes(&key, sizeof(key), 1) & 0xff;
270 | return oneByteHashKey;
271 | }
272 |
273 |
274 | #ifdef PMEM
275 | static void showList()
276 | {
277 | TOID(struct List) ListHead = POBJ_ROOT(pop, struct List);
278 | TOID(struct LeafNode) leafNode = D_RO(ListHead)->head;
279 |
280 | while (!TOID_IS_NULL(leafNode))
281 | {
282 | for (size_t i = 0; i < MAX_LEAF_SIZE; i++)
283 | {
284 | if (D_RO(leafNode)->bitmap.test(i))
285 | std::cout << "(" << D_RO(leafNode)->kv_pairs[i].key << " | " <<
286 | D_RO(leafNode)->kv_pairs[i].value << ")" << ", ";
287 | }
288 | std::cout << std::endl;
289 | leafNode = D_RO(leafNode)->p_next;
290 | }
291 | }
292 |
293 | static int constructLeafNode(PMEMobjpool *pop, void *ptr, void *arg)
294 | {
295 | struct LeafNode *node = (struct LeafNode *)ptr;
296 | struct argLeafNode *a = (struct argLeafNode *)arg;
297 |
298 | node->isInnerNode = a->isInnerNode;
299 | node->bitmap = a->bitmap;
300 | memcpy(node->fingerprints, a->fingerprints, sizeof(a->fingerprints));
301 | memcpy(node->kv_pairs, a->kv_pairs, sizeof(a->kv_pairs));
302 | node->p_next = TOID_NULL(struct LeafNode);
303 | node->lock = a->lock;
304 |
305 | pmemobj_persist(pop, node, a->size);
306 |
307 | return 0;
308 | }
309 | #endif
310 |
311 |
312 |
313 | void FPtree::printFPTree(std::string prefix, BaseNode* root)
314 | {
315 | if (root)
316 | {
317 | if (root->isInnerNode)
318 | {
319 | InnerNode* node = reinterpret_cast (root);
320 | printFPTree(" " + prefix, node->p_children[node->nKey]);
321 | for (int64_t i = node->nKey-1; i >= 0; i--)
322 | {
323 | std::cout << prefix << node->keys[i] << std::endl;
324 | printFPTree(" " + prefix, node->p_children[i]);
325 | }
326 | }
327 | else
328 | {
329 | LeafNode* node = reinterpret_cast (root);
330 | for (int64_t i = MAX_LEAF_SIZE-1; i >= 0; i--)
331 | {
332 | if (node->bitmap.test(i) == 1)
333 | std::cout << prefix << node->kv_pairs[i].key << "," << node->kv_pairs[i].value << std::endl;
334 | }
335 | }
336 | }
337 | }
338 |
339 | inline LeafNode* FPtree::findLeaf(uint64_t key)
340 | {
341 | if (!root)
342 | return nullptr;
343 | if (!root->isInnerNode)
344 | return reinterpret_cast (root);
345 | InnerNode* cursor = reinterpret_cast (root);
346 | while (cursor->isInnerNode)
347 | {
348 | cursor = reinterpret_cast (cursor->p_children[cursor->findChildIndex(key)]);
349 | }
350 | return reinterpret_cast (cursor);
351 | }
352 |
353 | inline LeafNode* FPtree::findLeafAndPushInnerNodes(uint64_t key)
354 | {
355 | if (!root)
356 | return nullptr;
357 | stack_innerNodes.clear();
358 | if (!root->isInnerNode)
359 | {
360 | stack_innerNodes.push(nullptr);
361 | return reinterpret_cast (root);
362 | }
363 | InnerNode* cursor = reinterpret_cast (root);
364 | while (cursor->isInnerNode)
365 | {
366 | stack_innerNodes.push(cursor);
367 | cursor = reinterpret_cast (cursor->p_children[cursor->findChildIndex(key)]);
368 | }
369 | return reinterpret_cast (cursor);
370 | }
371 |
372 |
373 | uint64_t FPtree::find(uint64_t key)
374 | {
375 | LeafNode* pLeafNode;
376 | volatile uint64_t idx;
377 | tbb::speculative_spin_rw_mutex::scoped_lock lock_find;
378 | while (true)
379 | {
380 | lock_find.acquire(speculative_lock, false);
381 | if ((pLeafNode = findLeaf(key)) == nullptr) { lock_find.release(); break; }
382 | if (pLeafNode->lock) { lock_find.release(); continue; }
383 | idx = pLeafNode->findKVIndex(key);
384 | lock_find.release();
385 | return (idx != MAX_LEAF_SIZE ? pLeafNode->kv_pairs[idx].value : 0 );
386 | }
387 | return 0;
388 | }
389 |
390 |
391 | void FPtree::splitLeafAndUpdateInnerParents(LeafNode* reachedLeafNode, Result decision, struct KV kv,
392 | bool updateFunc = false, uint64_t prevPos = MAX_LEAF_SIZE)
393 | {
394 | uint64_t splitKey;
395 |
396 | #ifdef PMEM
397 | TOID(struct LeafNode) insertNode = pmemobj_oid(reachedLeafNode);
398 | #else
399 | LeafNode* insertNode = reachedLeafNode;
400 | #endif
401 |
402 | if (decision == Result::Split)
403 | {
404 | splitKey = splitLeaf(reachedLeafNode); // split and link two leaves
405 | if (kv.key >= splitKey) // select one leaf to insert
406 | insertNode = reachedLeafNode->p_next;
407 | }
408 |
409 | #ifdef PMEM
410 | uint64_t slot = D_RW(insertNode)->bitmap.first_zero();
411 | assert(slot < MAX_LEAF_SIZE && "Slot idx out of bound");
412 | D_RW(insertNode)->kv_pairs[slot] = kv;
413 | D_RW(insertNode)->fingerprints[slot] = getOneByteHash(kv.key);
414 | pmemobj_persist(pop, &D_RO(insertNode)->kv_pairs[slot], sizeof(struct KV));
415 | pmemobj_persist(pop, &D_RO(insertNode)->fingerprints[slot], SIZE_ONE_BYTE_HASH);
416 |
417 | if (!updateFunc)
418 | {
419 | D_RW(insertNode)->bitmap.set(slot);
420 | }
421 | else
422 | {
423 | Bitset tmpBitmap = D_RW(insertNode)->bitmap;
424 | tmpBitmap.reset(prevPos); tmpBitmap.set(slot);
425 | D_RW(insertNode)->bitmap = tmpBitmap;
426 | }
427 | pmemobj_persist(pop, &D_RO(insertNode)->bitmap, sizeof(D_RO(insertNode)->bitmap));
428 | #else
429 | if (updateFunc)
430 | insertNode->kv_pairs[prevPos].value = kv.value;
431 | else
432 | insertNode->addKV(kv);
433 | #endif
434 |
435 | if (decision == Result::Split)
436 | {
437 | LeafNode* newLeafNode;
438 | #ifdef PMEM
439 | newLeafNode = (struct LeafNode *) pmemobj_direct((reachedLeafNode->p_next).oid);
440 | #else
441 | newLeafNode = reachedLeafNode->p_next;
442 | #endif
443 | tbb::speculative_spin_rw_mutex::scoped_lock lock_split;
444 | uint64_t mid = MAX_INNER_SIZE / 2, new_splitKey, insert_pos;
445 | InnerNode* cur, *parent, *newInnerNode;
446 | BaseNode* child;
447 | short i = 0, idx;
448 | /*---------------- Second Critical Section -----------------*/
449 | lock_split.acquire(speculative_lock);
450 | if (!root->isInnerNode) // splitting when tree has only root
451 | {
452 | cur = new InnerNode();
453 | cur->init(splitKey, reachedLeafNode, newLeafNode);
454 | root = cur;
455 | }
456 | else // need to retraverse & update parent
457 | {
458 | cur = reinterpret_cast (root);
459 | while(cur->isInnerNode)
460 | {
461 | inners[i] = cur;
462 | idx = std::lower_bound(cur->keys, cur->keys + cur->nKey, kv.key) - cur->keys;
463 | if (idx < cur->nKey && cur->keys[idx] == kv.key) // TODO: this should always be false
464 | idx ++;
465 | ppos[i++] = idx;
466 | cur = reinterpret_cast (cur->p_children[idx]);
467 | }
468 | parent = inners[--i];
469 | child = newLeafNode;
470 | while (true)
471 | {
472 | insert_pos = ppos[i--];
473 | if (parent->nKey < MAX_INNER_SIZE)
474 | {
475 | parent->addKey(insert_pos, splitKey, child);
476 | break;
477 | }
478 | else
479 | {
480 | newInnerNode = new InnerNode();
481 | parent->nKey = mid;
482 | if (insert_pos != mid)
483 | {
484 | new_splitKey = parent->keys[mid];
485 | std::copy(parent->keys + mid + 1, parent->keys + MAX_INNER_SIZE, newInnerNode->keys);
486 | std::copy(parent->p_children + mid + 1, parent->p_children + MAX_INNER_SIZE + 1, newInnerNode->p_children);
487 | newInnerNode->nKey = MAX_INNER_SIZE - mid - 1;
488 | if (insert_pos < mid)
489 | parent->addKey(insert_pos, splitKey, child);
490 | else
491 | newInnerNode->addKey(insert_pos - mid - 1, splitKey, child);
492 | }
493 | else
494 | {
495 | new_splitKey = splitKey;
496 | std::copy(parent->keys + mid, parent->keys + MAX_INNER_SIZE, newInnerNode->keys);
497 | std::copy(parent->p_children + mid, parent->p_children + MAX_INNER_SIZE + 1, newInnerNode->p_children);
498 | newInnerNode->p_children[0] = child;
499 | newInnerNode->nKey = MAX_INNER_SIZE - mid;
500 | }
501 | splitKey = new_splitKey;
502 | if (parent == root)
503 | {
504 | cur = new InnerNode(splitKey, parent, newInnerNode);
505 | root = cur;
506 | break;
507 | }
508 | parent = inners[i];
509 | child = newInnerNode;
510 | }
511 | }
512 | }
513 | newLeafNode->Unlock();
514 | lock_split.release();
515 | /*---------------- End of Second Critical Section -----------------*/
516 | }
517 | }
518 |
519 |
520 | void FPtree::updateParents(uint64_t splitKey, InnerNode* parent, BaseNode* child)
521 | {
522 | uint64_t mid = floor(MAX_INNER_SIZE / 2);
523 | uint64_t new_splitKey, insert_pos;
524 | while (true)
525 | {
526 | if (parent->nKey < MAX_INNER_SIZE)
527 | {
528 | insert_pos = parent->findChildIndex(splitKey);
529 | parent->addKey(insert_pos, splitKey, child);
530 | return;
531 | }
532 | else
533 | {
534 | InnerNode* newInnerNode = new InnerNode();
535 | insert_pos = std::lower_bound(parent->keys, parent->keys + MAX_INNER_SIZE, splitKey) - parent->keys;
536 |
537 | if (insert_pos < mid) { // insert into parent node
538 | new_splitKey = parent->keys[mid];
539 | parent->nKey = mid;
540 | std::memmove(newInnerNode->keys, parent->keys + mid + 1, (MAX_INNER_SIZE - mid - 1)*sizeof(uint64_t));
541 | std::memmove(newInnerNode->p_children, parent->p_children + mid + 1, (MAX_INNER_SIZE - mid)*sizeof(BaseNode*));
542 | newInnerNode->nKey = MAX_INNER_SIZE - mid - 1;
543 | parent->addKey(insert_pos, splitKey, child);
544 | }
545 | else if (insert_pos > mid) { // insert into new innernode
546 | new_splitKey = parent->keys[mid];
547 | parent->nKey = mid;
548 | std::memmove(newInnerNode->keys, parent->keys + mid + 1, (MAX_INNER_SIZE - mid - 1)*sizeof(uint64_t));
549 | std::memmove(newInnerNode->p_children, parent->p_children + mid + 1, (MAX_INNER_SIZE - mid)*sizeof(BaseNode*));
550 | newInnerNode->nKey = MAX_INNER_SIZE - mid - 1;
551 | newInnerNode->addKey(insert_pos - mid - 1, splitKey, child);
552 | }
553 | else { // only insert child to new innernode, splitkey does not change
554 | new_splitKey = splitKey;
555 | parent->nKey = mid;
556 | std::memmove(newInnerNode->keys, parent->keys + mid, (MAX_INNER_SIZE - mid)*sizeof(uint64_t));
557 | std::memmove(newInnerNode->p_children, parent->p_children + mid, (MAX_INNER_SIZE - mid + 1)*sizeof(BaseNode*));
558 | newInnerNode->p_children[0] = child;
559 | newInnerNode->nKey = MAX_INNER_SIZE - mid;
560 | }
561 |
562 | splitKey = new_splitKey;
563 |
564 | if (parent == root)
565 | {
566 | root = new InnerNode(splitKey, parent, newInnerNode);
567 | return;
568 | }
569 | parent = stack_innerNodes.pop();
570 | child = newInnerNode;
571 | }
572 | }
573 | }
574 |
575 |
576 | bool FPtree::update(struct KV kv)
577 | {
578 | tbb::speculative_spin_rw_mutex::scoped_lock lock_update;
579 | LeafNode* reachedLeafNode;
580 | volatile uint64_t prevPos;
581 | volatile Result decision = Result::Abort;
582 | while (decision == Result::Abort)
583 | {
584 | lock_update.acquire(speculative_lock, false);
585 | if ((reachedLeafNode = findLeaf(kv.key)) == nullptr) { lock_update.release(); return false; }
586 | if (!reachedLeafNode->Lock()) { lock_update.release(); continue; }
587 | prevPos = reachedLeafNode->findKVIndex(kv.key);
588 | if (prevPos == MAX_LEAF_SIZE) // key not found
589 | {
590 | reachedLeafNode->Unlock();
591 | lock_update.release();
592 | return false;
593 | }
594 | decision = reachedLeafNode->isFull() ? Result::Split : Result::Update;
595 | lock_update.release();
596 | }
597 |
598 | splitLeafAndUpdateInnerParents(reachedLeafNode, decision, kv, true, prevPos);
599 |
600 | reachedLeafNode->Unlock();
601 |
602 | return true;
603 | }
604 |
605 |
606 | bool FPtree::insert(struct KV kv)
607 | {
608 | tbb::speculative_spin_rw_mutex::scoped_lock lock_insert;
609 | if (!root) // if tree is empty
610 | {
611 | lock_insert.acquire(speculative_lock, true);
612 | if (!root)
613 | {
614 | #ifdef PMEM
615 | struct argLeafNode args(kv);
616 | TOID(struct List) ListHead = POBJ_ROOT(pop, struct List);
617 | TOID(struct LeafNode) *dst = &D_RW(ListHead)->head;
618 | POBJ_ALLOC(pop, dst, struct LeafNode, args.size, constructLeafNode, &args);
619 | D_RW(ListHead)->head = *dst;
620 | pmemobj_persist(pop, &D_RO(ListHead)->head, sizeof(D_RO(ListHead)->head));
621 | root = (struct BaseNode *) pmemobj_direct((*dst).oid);
622 | #else
623 | root = new LeafNode();
624 | reinterpret_cast(root)->lock = 1;
625 | reinterpret_cast (root)->addKV(kv);
626 | reinterpret_cast(root)->lock = 0;
627 | #endif
628 | lock_insert.release();
629 | return true;
630 | }
631 | lock_insert.release();
632 | }
633 |
634 | Result decision = Result::Abort;
635 | InnerNode* cursor;
636 | LeafNode* reachedLeafNode;
637 | uint64_t nKey;
638 | int idx;
639 | /*---------------- First Critical Section -----------------*/
640 | {
641 | TBB_BEGIN:
642 | lock_insert.acquire(speculative_lock, false);
643 | reachedLeafNode = findLeaf(kv.key);
644 | if (!reachedLeafNode->Lock())
645 | {
646 | lock_insert.release();
647 | goto TBB_BEGIN;
648 | }
649 | idx = reachedLeafNode->findKVIndex(kv.key);
650 | if (idx != MAX_LEAF_SIZE)
651 | reachedLeafNode->Unlock();
652 | else
653 | decision = reachedLeafNode->isFull() ? Result::Split : Result::Insert;
654 | lock_insert.release();
655 | }
656 | /*---------------- End of First Critical Section -----------------*/
657 |
658 | if (decision == Result::Abort) // kv already exists
659 | return false;
660 |
661 | splitLeafAndUpdateInnerParents(reachedLeafNode, decision, kv);
662 |
663 | reachedLeafNode->Unlock();
664 |
665 | return true;
666 | }
667 |
668 |
669 |
670 | uint64_t FPtree::splitLeaf(LeafNode* leaf)
671 | {
672 | uint64_t splitKey = findSplitKey(leaf);
673 | #ifdef PMEM
674 | TOID(struct LeafNode) *dst = &(leaf->p_next);
675 | TOID(struct LeafNode) nextLeafNode = leaf->p_next;
676 |
677 | // Get uLog from splitLogQueue
678 | Log* log;
679 | if (!splitLogQueue.pop(log)) { assert("Split log queue pop error!"); }
680 |
681 | //set uLog.PCurrentLeaf to persistent address of Leaf
682 | log->PCurrentLeaf = pmemobj_oid(leaf);
683 | pmemobj_persist(pop, &(log->PCurrentLeaf), SIZE_PMEM_POINTER);
684 |
685 | // Copy the content of Leaf into NewLeaf
686 | struct argLeafNode args(leaf);
687 |
688 | log->PLeaf = *dst;
689 |
690 | POBJ_ALLOC(pop, dst, struct LeafNode, args.size, constructLeafNode, &args);
691 |
692 | for (size_t i = 0; i < MAX_LEAF_SIZE; i++)
693 | {
694 | if (D_RO(*dst)->kv_pairs[i].key < splitKey)
695 | D_RW(*dst)->bitmap.reset(i);
696 | }
697 | // Persist(NewLeaf.Bitmap)
698 | pmemobj_persist(pop, &D_RO(*dst)->bitmap, sizeof(D_RO(*dst)->bitmap));
699 |
700 | // Leaf.Bitmap = inverse(NewLeaf.Bitmap)
701 | leaf->bitmap = D_RO(*dst)->bitmap;
702 | if constexpr (MAX_LEAF_SIZE != 1) leaf->bitmap.flip();
703 |
704 | // Persist(Leaf.Bitmap)
705 | pmemobj_persist(pop, &leaf->bitmap, sizeof(leaf->bitmap));
706 |
707 | // Persist(Leaf.Next)
708 | D_RW(*dst)->p_next = nextLeafNode;
709 | pmemobj_persist(pop, &D_RO(*dst)->p_next, sizeof(D_RO(*dst)->p_next));
710 |
711 | // reset uLog
712 | log->PCurrentLeaf = OID_NULL;
713 | log->PLeaf = OID_NULL;
714 | pmemobj_persist(pop, &(log->PCurrentLeaf), SIZE_PMEM_POINTER);
715 | pmemobj_persist(pop, &(log->PLeaf), SIZE_PMEM_POINTER);
716 | splitLogQueue.push(log);
717 | #else
718 | LeafNode* newLeafNode = new LeafNode(*leaf);
719 |
720 | for (size_t i = 0; i < MAX_LEAF_SIZE; i++)
721 | {
722 | if (newLeafNode->kv_pairs[i].key < splitKey)
723 | newLeafNode->bitmap.reset(i);
724 | }
725 |
726 | leaf->bitmap = newLeafNode->bitmap;
727 | leaf->bitmap.flip();
728 | leaf->p_next = newLeafNode;
729 | #endif
730 |
731 | return splitKey;
732 | }
733 |
734 |
735 | uint64_t FPtree::findSplitKey(LeafNode* leaf)
736 | {
737 | KV tempArr[MAX_LEAF_SIZE];
738 | memcpy(tempArr, leaf->kv_pairs, sizeof(leaf->kv_pairs));
739 | // TODO: find median in one pass instead of sorting
740 | std::sort(std::begin(tempArr), std::end(tempArr), [] (const KV& kv1, const KV& kv2)
741 | {
742 | return kv1.key < kv2.key;
743 | });
744 |
745 | uint64_t mid = floor(MAX_LEAF_SIZE / 2);
746 | uint64_t splitKey = tempArr[mid].key;
747 |
748 | return splitKey;
749 | }
750 |
751 |
752 | #ifdef PMEM
753 | void FPtree::recoverSplit(Log* uLog)
754 | {
755 | if (TOID_IS_NULL(uLog->PCurrentLeaf))
756 | {
757 | return;
758 | }
759 |
760 | if (TOID_IS_NULL(uLog->PLeaf))
761 | {
762 | uLog->PCurrentLeaf = OID_NULL;
763 | uLog->PLeaf = OID_NULL;
764 | return;
765 | }
766 | else
767 | {
768 | LeafNode* leaf = (struct LeafNode *) pmemobj_direct((uLog->PCurrentLeaf).oid);
769 | uint64_t splitKey = findSplitKey(leaf);
770 | if (leaf->isFull()) // Crashed before inverse the current leaf
771 | {
772 | for (size_t i = 0; i < MAX_LEAF_SIZE; i++)
773 | {
774 | if (D_RO(uLog->PLeaf)->kv_pairs[i].key < splitKey)
775 | D_RW(uLog->PLeaf)->bitmap.reset(i);
776 | }
777 | // Persist(NewLeaf.Bitmap)
778 | pmemobj_persist(pop, &D_RO(uLog->PLeaf)->bitmap, sizeof(D_RO(uLog->PLeaf)->bitmap));
779 |
780 | // Leaf.Bitmap = inverse(NewLeaf.Bitmap)
781 | D_RW(uLog->PCurrentLeaf)->bitmap = D_RO(uLog->PLeaf)->bitmap;
782 | if constexpr (MAX_LEAF_SIZE != 1) D_RW(uLog->PCurrentLeaf)->bitmap.flip();
783 |
784 | // Persist(Leaf.Bitmap)
785 | pmemobj_persist(pop, &D_RO(uLog->PCurrentLeaf)->bitmap, sizeof(D_RO(uLog->PCurrentLeaf)->bitmap));
786 |
787 | // Persist(Leaf.Next)
788 | D_RW(uLog->PCurrentLeaf)->p_next = uLog->PLeaf;
789 | pmemobj_persist(pop, &D_RO(uLog->PLeaf)->p_next, sizeof(D_RO(uLog->PLeaf)->p_next));
790 |
791 | // reset uLog
792 | uLog->PCurrentLeaf = OID_NULL;
793 | uLog->PLeaf = OID_NULL;
794 | return;
795 | }
796 | else // Crashed after inverse the current leaf
797 | {
798 | // Leaf.Bitmap = inverse(NewLeaf.Bitmap)
799 | if constexpr (MAX_LEAF_SIZE != 1) D_RW(uLog->PCurrentLeaf)->bitmap.flip();
800 |
801 | // Persist(Leaf.Bitmap)
802 | pmemobj_persist(pop, &D_RO(uLog->PCurrentLeaf)->bitmap, sizeof(D_RO(uLog->PCurrentLeaf)->bitmap));
803 |
804 | // Persist(Leaf.Next)
805 | D_RW(uLog->PCurrentLeaf)->p_next = uLog->PLeaf;
806 | pmemobj_persist(pop, &D_RO(uLog->PLeaf)->p_next, sizeof(D_RO(uLog->PLeaf)->p_next));
807 |
808 | // reset uLog
809 | uLog->PCurrentLeaf = OID_NULL;
810 | uLog->PLeaf = OID_NULL;
811 | return;
812 | }
813 | }
814 | }
815 | #endif
816 |
817 | void FPtree::removeLeafAndMergeInnerNodes(short i, short indexNode_level)
818 | {
819 | InnerNode* temp, *left, *right, *parent = inners[i];
820 | uint64_t left_idx, new_key = 0, child_idx = ppos[i];
821 |
822 | if (child_idx == 0)
823 | {
824 | new_key = parent->keys[0];
825 | parent->removeKey(child_idx, false);
826 | if (indexNode_level >= 0 && inners[indexNode_level] != parent)
827 | inners[indexNode_level]->keys[ppos[indexNode_level] - 1] = new_key;
828 | }
829 | else
830 | parent->removeKey(child_idx - 1, true);
831 |
832 | while (!parent->nKey) // parent has no key, merge with sibling
833 | {
834 | if (parent == root) // entire tree stores 1 kv, convert the only leafnode into root
835 | {
836 | temp = reinterpret_cast (root);
837 | root = parent->p_children[0];
838 | delete temp;
839 | break;
840 | }
841 | parent = inners[--i];
842 | child_idx = ppos[i];
843 | left_idx = child_idx;
844 | if (!(child_idx != 0 && tryBorrowKey(parent, child_idx, child_idx-1)) &&
845 | !(child_idx != parent->nKey && tryBorrowKey(parent, child_idx, child_idx+1))) // if cannot borrow from any siblings
846 | {
847 | if (left_idx != 0)
848 | left_idx --;
849 | left = reinterpret_cast (parent->p_children[left_idx]);
850 | right = reinterpret_cast (parent->p_children[left_idx + 1]);
851 |
852 | if (left->nKey == 0)
853 | {
854 | right->addKey(0, parent->keys[left_idx], left->p_children[0], false);
855 | delete left;
856 | parent->removeKey(left_idx, false);
857 | }
858 | else
859 | {
860 | left->addKey(left->nKey, parent->keys[left_idx], right->p_children[0]);
861 | delete right;
862 | parent->removeKey(left_idx);
863 | }
864 | }
865 | else
866 | break;
867 | }
868 | }
869 |
870 | bool FPtree::deleteKey(uint64_t key)
871 | {
872 | LeafNode* leaf, *sibling;
873 | InnerNode *parent, *cur;
874 | tbb::speculative_spin_rw_mutex::scoped_lock lock_delete;
875 | Result decision = Result::Abort;
876 | LeafNodeStat lstat;
877 | short i, idx, indexNode_level, sib_level;
878 | while (decision == Result::Abort)
879 | {
880 | i = 0; indexNode_level = -1, sib_level = -1;
881 | sibling = nullptr;
882 | /*---------------- Critical Section -----------------*/
883 | lock_delete.acquire(speculative_lock, true);
884 |
885 | if (!root) { lock_delete.release(); return false;} // empty tree
886 | cur = reinterpret_cast (root);
887 | while (cur->isInnerNode)
888 | {
889 | inners[i] = cur;
890 | idx = std::lower_bound(cur->keys, cur->keys + cur->nKey, key) - cur->keys;
891 | if (idx < cur->nKey && cur->keys[idx] == key) // just found index node
892 | {
893 | indexNode_level = i;
894 | idx ++;
895 | }
896 | if (idx != 0)
897 | sib_level = i;
898 | ppos[i++] = idx;
899 | cur = reinterpret_cast (cur->p_children[idx]);
900 | }
901 | parent = inners[--i];
902 | leaf = reinterpret_cast (cur);
903 |
904 | if (!leaf->Lock()) { lock_delete.release(); continue; }
905 | leaf->getStat(key, lstat);
906 | if (lstat.kv_idx == MAX_LEAF_SIZE) // key not found
907 | {
908 | decision = Result::NotFound;
909 | leaf->Unlock();
910 | }
911 | else if (lstat.count > 1) // leaf contains key and other keys
912 | {
913 | if (indexNode_level >= 0) // key appears in an inner node, need to replace
914 | inners[indexNode_level]->keys[ppos[indexNode_level] - 1] = lstat.min_key;
915 | decision = Result::Remove;
916 | }
917 | else // leaf contains key only
918 | {
919 | if (parent) // try lock left sibling if exist, then remove leaf from parent and update inner nodes
920 | {
921 | if (sib_level >= 0) // left sibling exists
922 | {
923 | cur = reinterpret_cast (inners[sib_level]->p_children[ppos[sib_level] - 1]);
924 | while (cur->isInnerNode)
925 | cur = reinterpret_cast (cur->p_children[cur->nKey]);
926 | sibling = reinterpret_cast (cur);
927 | if (!sibling->Lock())
928 | {
929 | lock_delete.release(); leaf->Unlock(); continue;
930 | }
931 | }
932 | removeLeafAndMergeInnerNodes(i, indexNode_level);
933 | }
934 | decision = Result::Delete;
935 | }
936 | lock_delete.release();
937 | /*---------------- Critical Section -----------------*/
938 | }
939 | if (decision == Result::Remove)
940 | {
941 | leaf->bitmap.reset(lstat.kv_idx);
942 | #ifdef PMEM
943 | TOID(struct LeafNode) lf = pmemobj_oid(leaf);
944 | pmemobj_persist(pop, &D_RO(lf)->bitmap, sizeof(D_RO(lf)->bitmap));
945 | #endif
946 | leaf->Unlock();
947 | }
948 | else if (decision == Result::Delete)
949 | {
950 | #ifdef PMEM
951 | TOID(struct LeafNode) lf = pmemobj_oid(leaf);
952 |
953 | // Get uLog from deleteLogQueue
954 | Log* log;
955 | if (!deleteLogQueue.pop(log)) { assert("Delete log queue pop error!"); }
956 |
957 | //set uLog.PCurrentLeaf to persistent address of Leaf
958 | log->PCurrentLeaf = lf;
959 | pmemobj_persist(pop, &(log->PCurrentLeaf), SIZE_PMEM_POINTER);
960 |
961 | if (sibling) // set and persist sibling's p_next, then unlock sibling node
962 | {
963 | TOID(struct LeafNode) sib = pmemobj_oid(sibling);
964 |
965 | log->PLeaf = sib;
966 | pmemobj_persist(pop, &(log->PLeaf), SIZE_PMEM_POINTER);
967 |
968 | D_RW(sib)->p_next = D_RO(lf)->p_next;
969 | pmemobj_persist(pop, &D_RO(sib)->p_next, sizeof(D_RO(sib)->p_next));
970 | sibling->Unlock();
971 | }
972 | else if (parent) // the node to delete is left most node, set and persist list head instead
973 | {
974 | TOID(struct List) ListHead = POBJ_ROOT(pop, struct List);
975 | D_RW(ListHead)->head = D_RO(lf)->p_next;
976 | pmemobj_persist(pop, &D_RO(ListHead)->head, sizeof(D_RO(ListHead)->head));
977 | }
978 | else
979 | {
980 | TOID(struct List) ListHead = POBJ_ROOT(pop, struct List);
981 | D_RW(ListHead)->head = OID_NULL;
982 | pmemobj_persist(pop, &D_RO(ListHead)->head, sizeof(D_RO(ListHead)->head));
983 | root = nullptr;
984 | }
985 | POBJ_FREE(&lf);
986 |
987 | // reset uLog
988 | log->PCurrentLeaf = OID_NULL;
989 | log->PLeaf = OID_NULL;
990 | pmemobj_persist(pop, &(log->PCurrentLeaf), SIZE_PMEM_POINTER);
991 | pmemobj_persist(pop, &(log->PLeaf), SIZE_PMEM_POINTER);
992 | deleteLogQueue.push(log);
993 | #else
994 | if (sibling)
995 | {
996 | sibling->p_next = leaf->p_next;
997 | sibling->Unlock();
998 | }
999 | else if (!parent)
1000 | root = nullptr;
1001 | delete leaf;
1002 | #endif
1003 | }
1004 | return decision != Result::NotFound;
1005 | }
1006 |
1007 | #ifdef PMEM
1008 | void FPtree::recoverDelete(Log* uLog)
1009 | {
1010 | TOID(struct List) ListHead = POBJ_ROOT(pop, struct List);
1011 | TOID(struct LeafNode) PHead = D_RW(ListHead)->head;
1012 |
1013 | if( (!TOID_IS_NULL(uLog->PCurrentLeaf)) && (!TOID_IS_NULL(PHead)) )
1014 | {
1015 | D_RW(uLog->PLeaf)->p_next = D_RO(uLog->PCurrentLeaf)->p_next;
1016 | pmemobj_persist(pop, &D_RO(uLog->PLeaf)->p_next, SIZE_PMEM_POINTER);
1017 | D_RW(uLog->PLeaf)->Unlock();
1018 | POBJ_FREE(&(uLog->PCurrentLeaf));
1019 | }
1020 | else
1021 | {
1022 | if ( (!TOID_IS_NULL(uLog->PCurrentLeaf)) &&
1023 | ((struct LeafNode *) pmemobj_direct((uLog->PCurrentLeaf).oid) ==
1024 | (struct LeafNode *) pmemobj_direct(PHead.oid))
1025 | )
1026 | {
1027 | PHead = D_RO(uLog->PCurrentLeaf)->p_next;
1028 | pmemobj_persist(pop, &PHead, SIZE_PMEM_POINTER);
1029 | POBJ_FREE(&(uLog->PCurrentLeaf));
1030 | }
1031 | else
1032 | {
1033 | if ( (!TOID_IS_NULL(uLog->PCurrentLeaf)) &&
1034 | ((struct LeafNode *) pmemobj_direct((D_RO(uLog->PCurrentLeaf)->p_next).oid) ==
1035 | (struct LeafNode *) pmemobj_direct(PHead.oid))
1036 | )
1037 | POBJ_FREE(&(uLog->PCurrentLeaf));
1038 | else { /* reset uLog */ }
1039 | }
1040 | }
1041 |
1042 | // reset uLog
1043 | uLog->PCurrentLeaf = OID_NULL;
1044 | uLog->PLeaf = OID_NULL;
1045 | return;
1046 | }
1047 | #endif
1048 |
1049 | bool FPtree::tryBorrowKey(InnerNode* parent, uint64_t receiver_idx, uint64_t sender_idx)
1050 | {
1051 | InnerNode* sender = reinterpret_cast (parent->p_children[sender_idx]);
1052 | if (sender->nKey <= 1) // sibling has only 1 key, cannot borrow
1053 | return false;
1054 | InnerNode* receiver = reinterpret_cast (parent->p_children[receiver_idx]);
1055 | if (receiver_idx < sender_idx) // borrow from right sibling
1056 | {
1057 | receiver->addKey(0, parent->keys[receiver_idx], sender->p_children[0]);
1058 | parent->keys[receiver_idx] = sender->keys[0];
1059 | sender->removeKey(0, false);
1060 | }
1061 | else // borrow from left sibling
1062 | {
1063 | receiver->addKey(0, receiver->keys[0], sender->p_children[sender->nKey], false);
1064 | parent->keys[sender_idx] = sender->keys[sender->nKey-1];
1065 | sender->removeKey(sender->nKey-1);
1066 | }
1067 | return true;
1068 | }
1069 |
1070 | inline uint64_t FPtree::minKey(BaseNode* node)
1071 | {
1072 | while (node->isInnerNode)
1073 | node = reinterpret_cast (node)->p_children[0];
1074 | return reinterpret_cast (node)->minKey();
1075 | }
1076 |
1077 | LeafNode* FPtree::minLeaf(BaseNode* node)
1078 | {
1079 | while(node->isInnerNode)
1080 | node = reinterpret_cast (node)->p_children[0];
1081 | return reinterpret_cast (node);
1082 | }
1083 |
1084 |
1085 | void FPtree::sortKV()
1086 | {
1087 | uint64_t j = 0;
1088 | for (uint64_t i = 0; i < MAX_LEAF_SIZE; i++)
1089 | if (this->current_leaf->bitmap.test(i))
1090 | this->volatile_current_kv[j++] = this->current_leaf->kv_pairs[i];
1091 |
1092 | this->size_volatile_kv = j;
1093 |
1094 | std::sort(std::begin(this->volatile_current_kv), std::begin(this->volatile_current_kv) + this->size_volatile_kv,
1095 | [] (const KV& kv1, const KV& kv2){
1096 | return kv1.key < kv2.key;
1097 | });
1098 | }
1099 |
1100 |
1101 | void FPtree::scanInitialize(uint64_t key)
1102 | {
1103 | if (!root)
1104 | return;
1105 |
1106 | this->current_leaf = root->isInnerNode? findLeaf(key) : reinterpret_cast (root);
1107 | while (this->current_leaf != nullptr)
1108 | {
1109 | this->sortKV();
1110 | for (uint64_t i = 0; i < this->size_volatile_kv; i++)
1111 | {
1112 | if (this->volatile_current_kv[i].key >= key)
1113 | {
1114 | this->bitmap_idx = i;
1115 | return;
1116 | }
1117 | }
1118 | #ifdef PMEM
1119 | this->current_leaf = (struct LeafNode *) pmemobj_direct((this->current_leaf->p_next).oid);
1120 | #else
1121 | this->current_leaf = this->current_leaf->p_next;
1122 | #endif
1123 | }
1124 | }
1125 |
1126 |
1127 | KV FPtree::scanNext()
1128 | {
1129 | assert(this->current_leaf != nullptr && "Current scan node was deleted!");
1130 | struct KV kv = this->volatile_current_kv[this->bitmap_idx++];
1131 | if (this->bitmap_idx == this->size_volatile_kv)
1132 | {
1133 | #ifdef PMEM
1134 | this->current_leaf = (struct LeafNode *) pmemobj_direct((this->current_leaf->p_next).oid);
1135 | #else
1136 | this->current_leaf = this->current_leaf->p_next;
1137 | #endif
1138 | if (this->current_leaf != nullptr)
1139 | {
1140 | this->sortKV();
1141 | this->bitmap_idx = 0;
1142 | }
1143 | }
1144 | return kv;
1145 | }
1146 |
1147 |
1148 | bool FPtree::scanComplete()
1149 | {
1150 | return this->current_leaf == nullptr;
1151 | }
1152 |
1153 |
1154 | uint64_t FPtree::rangeScan(uint64_t key, uint64_t scan_size, char* result)
1155 | {
1156 | LeafNode* leaf, * next_leaf;
1157 | std::vector records;
1158 | records.reserve(scan_size);
1159 | uint64_t i;
1160 | tbb::speculative_spin_rw_mutex::scoped_lock lock_scan;
1161 | while (true)
1162 | {
1163 | lock_scan.acquire(speculative_lock, false);
1164 | if ((leaf = findLeaf(key)) == nullptr) { lock_scan.release(); return 0; }
1165 | if (!leaf->Lock()) { lock_scan.release(); continue; }
1166 | for (i = 0; i < MAX_LEAF_SIZE; i++)
1167 | if (leaf->bitmap.test(i) && leaf->kv_pairs[i].key >= key)
1168 | records.push_back(leaf->kv_pairs[i]);
1169 | while (records.size() < scan_size)
1170 | {
1171 | #ifdef PMEM
1172 | if (TOID_IS_NULL(leaf->p_next))
1173 | break;
1174 | next_leaf = (struct LeafNode *) pmemobj_direct((leaf->p_next).oid);
1175 | #else
1176 | if ((next_leaf = leaf->p_next) == nullptr)
1177 | break;
1178 | #endif
1179 | while (!next_leaf->Lock())
1180 | std::this_thread::sleep_for(std::chrono::nanoseconds(1));
1181 | leaf->Unlock();
1182 | leaf = next_leaf;
1183 | for (i = 0; i < MAX_LEAF_SIZE; i++)
1184 | if (leaf->bitmap.test(i))
1185 | records.push_back(leaf->kv_pairs[i]);
1186 | }
1187 | lock_scan.release();
1188 | break;
1189 | }
1190 | if (leaf && leaf->lock == 1)
1191 | leaf->Unlock();
1192 | std::sort(records.begin(), records.end(), [] (const KV& kv1, const KV& kv2) {
1193 | return kv1.key < kv2.key;
1194 | });
1195 | // result = new char[sizeof(KV) * records.size()];
1196 | i = records.size() > scan_size ? scan_size : records.size();
1197 | memcpy(result, records.data(), sizeof(KV) * i);
1198 | return i;
1199 | }
1200 |
1201 |
1202 |
1203 | #ifdef PMEM
1204 | bool FPtree::bulkLoad(float load_factor = 1)
1205 | {
1206 | TOID(struct List) ListHead = POBJ_ROOT(pop, struct List);
1207 | TOID(struct LeafNode) cursor = D_RW(ListHead)->head;
1208 |
1209 | if (TOID_IS_NULL(cursor)) { this->root = nullptr; return true; }
1210 |
1211 | if (TOID_IS_NULL(D_RO(cursor)->p_next))
1212 | { root = (struct BaseNode *) pmemobj_direct(cursor.oid); return true;}
1213 |
1214 | std::vector min_keys;
1215 | std::vector child_nodes;
1216 | uint64_t total_leaves = 0;
1217 | LeafNode* temp_leafnode;
1218 | while(!TOID_IS_NULL(cursor)) // record min keys and leaf nodes
1219 | {
1220 | temp_leafnode = (struct LeafNode *) pmemobj_direct(cursor.oid);
1221 | child_nodes.push_back(temp_leafnode);
1222 | min_keys.push_back(temp_leafnode->minKey());
1223 | cursor = D_RW(cursor)->p_next;
1224 | }
1225 | total_leaves = min_keys.size();
1226 | min_keys.erase(min_keys.begin());
1227 |
1228 | InnerNode* new_root = new InnerNode();
1229 | uint64_t idx = 0;
1230 | uint64_t root_size = total_leaves <= MAX_INNER_SIZE ?
1231 | total_leaves : MAX_INNER_SIZE + 1;
1232 | for (; idx < root_size; idx++) // recovery the root node
1233 | {
1234 | if (idx < root_size - 1)
1235 | new_root->keys[idx] = min_keys[idx];
1236 | new_root->p_children[idx] = child_nodes[idx];
1237 | }
1238 | new_root->nKey = root_size - 1;
1239 | this->root = reinterpret_cast (new_root);
1240 |
1241 | if (total_leaves > MAX_INNER_SIZE)
1242 | {
1243 | idx--;
1244 | right_most_innnerNode = reinterpret_cast(this->root);
1245 | for (; idx < min_keys.size(); idx++) // Index entries for leaf pages always entered into right-most index page
1246 | {
1247 | findLeafAndPushInnerNodes(min_keys[idx]);
1248 | right_most_innnerNode = stack_innerNodes.pop();
1249 | updateParents(min_keys[idx], right_most_innnerNode, child_nodes[idx+1]);
1250 | }
1251 | }
1252 | return true;
1253 | }
1254 | #endif
1255 |
1256 |
1257 | /*
1258 | Use case
1259 | uint64_t tick = rdtsc();
1260 | Put program between
1261 | std::cout << rdtsc() - tick << std::endl;
1262 | */
1263 | uint64_t rdtsc(){
1264 | unsigned int lo,hi;
1265 | __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
1266 | return ((uint64_t)hi << 32) | lo;
1267 | }
1268 |
1269 |
1270 | #ifdef PMEM
1271 | #if BUILD_INSPECTOR == 0
1272 | int main(int argc, char *argv[])
1273 | {
1274 | const char* path = "./test_pool";
1275 | srand( (unsigned) time(NULL) * getpid());
1276 | FPtree fptree;
1277 | fptree.pmemInit(path, PMEMOBJ_POOL_SIZE);
1278 |
1279 | #ifdef PMEM
1280 | const char* command = argv[1];
1281 | if (command != NULL && strcmp(command, "show") == 0)
1282 | {
1283 | showList();
1284 | return 0;
1285 | }
1286 | #endif
1287 |
1288 | int64_t key;
1289 | uint64_t value;
1290 | while (true)
1291 | {
1292 | fptree.printFPTree("├──", fptree.getRoot());
1293 | std::cout << "\nEnter the key to insert, delete or update (-1): ";
1294 | std::cin >> key;
1295 | std::cout << std::endl;
1296 | KV kv = KV(key, key);
1297 | if (key == 0)
1298 | break;
1299 | else if (key == -1)
1300 | {
1301 | std::cout << "\nEnter the key to update: ";
1302 | std::cin >> key;
1303 | std::cout << "\nEnter the value to update: ";
1304 | std::cin >> value;
1305 | fptree.update(KV(key, value));
1306 | }
1307 | else if (fptree.find(kv.key))
1308 | fptree.deleteKey(kv.key);
1309 | else
1310 | {
1311 | // fptree.insert(kv);
1312 | if (!fptree.getRoot())
1313 | for (size_t i = 0; i < 100; i++)
1314 | fptree.insert(KV(rand() % 100 + 2, 1));
1315 | else
1316 | fptree.insert(kv);
1317 | }
1318 | // fptree.printFPTree("├──", fptree.getRoot());
1319 | #ifdef PMEM
1320 | std::cout << std::endl;
1321 | std::cout << "show list: " << std::endl;
1322 | showList();
1323 | #endif
1324 | }
1325 |
1326 | std::cout << "\nEnter the key to initialize scan: ";
1327 | std::cin >> key;
1328 | std::cout << std::endl;
1329 | fptree.scanInitialize(key);
1330 | while(!fptree.scanComplete())
1331 | {
1332 | KV kv = fptree.scanNext();
1333 | std::cout << kv.key << "," << kv.value << " ";
1334 | }
1335 | std::cout << std::endl;
1336 | }
1337 | #endif
1338 | #endif
1339 |
--------------------------------------------------------------------------------
/fptree.h:
--------------------------------------------------------------------------------
1 | // Copyright (c) Simon Fraser University. All rights reserved.
2 | // Licensed under the MIT license.
3 | //
4 | // Authors:
5 | // George He
6 | // Duo Lu
7 | // Tianzheng Wang
8 |
9 | #pragma once
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | //#include "oneapi/tbb/spin_mutex.h"
22 | //#include "oneapi/tbb/spin_rw_mutex.h"
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include
37 | #include
38 | #include
39 | #include
40 | #include
41 | #include
42 | #include
43 | #include
44 |
45 | #ifdef TEST_MODE
46 | #define MAX_INNER_SIZE 3
47 | #define MAX_LEAF_SIZE 4
48 | #define SIZE_ONE_BYTE_HASH 1
49 | #define SIZE_PMEM_POINTER 16
50 | #else
51 | #define MAX_INNER_SIZE 128
52 | #define MAX_LEAF_SIZE 64
53 | #define SIZE_ONE_BYTE_HASH 1
54 | #define SIZE_PMEM_POINTER 16
55 | #endif
56 |
57 | #if MAX_LEAF_SIZE > 64
58 | #error "Number of kv pairs in LeafNode must be <= 64."
59 | #endif
60 |
61 | static const uint64_t offset = std::numeric_limits::max() >> (64 - MAX_LEAF_SIZE);
62 |
63 | enum Result { Insert, Update, Split, Abort, Delete, Remove, NotFound };
64 |
65 | #ifdef PMEM
66 | #include
67 |
68 | #define PMEMOBJ_POOL_SIZE ((size_t)(1024 * 1024 * 11) * 1000) /* 11 GB */
69 |
70 | POBJ_LAYOUT_BEGIN(FPtree);
71 | POBJ_LAYOUT_ROOT(FPtree, struct List);
72 | POBJ_LAYOUT_TOID(FPtree, struct LeafNode);
73 | POBJ_LAYOUT_END(FPtree);
74 |
75 | POBJ_LAYOUT_BEGIN(Array);
76 | POBJ_LAYOUT_TOID(Array, struct Log);
77 | POBJ_LAYOUT_END(Array);
78 |
79 | inline PMEMobjpool *pop;
80 | #endif
81 |
82 | static uint8_t getOneByteHash(uint64_t key);
83 |
84 | struct KV
85 | {
86 | uint64_t key;
87 | uint64_t value;
88 |
89 | KV() {}
90 | KV(uint64_t key, uint64_t value) { this->key = key; this->value = value; }
91 | };
92 |
93 | struct LeafNodeStat
94 | {
95 | uint64_t kv_idx; // bitmap index of key
96 | uint64_t count; // number of kv in leaf
97 | uint64_t min_key; // min key excluding key
98 | };
99 |
100 | // This Bitset class implements bitmap of size <= 64
101 | // The bitmap iterates from right to left - starting from least significant bit
102 | // off contains 0 on some significant bits when bitmap size < 64
103 | class Bitset
104 | {
105 | public:
106 | uint64_t bits;
107 |
108 | Bitset()
109 | {
110 | bits = 0;
111 | }
112 |
113 | ~Bitset() {}
114 |
115 | Bitset(const Bitset& bts)
116 | {
117 | bits = bts.bits;
118 | }
119 |
120 | Bitset& operator=(const Bitset& bts)
121 | {
122 | bits = bts.bits;
123 | return *this;
124 | }
125 |
126 | inline void set(const size_t pos)
127 | {
128 | bits |= ((uint64_t)1 << pos);
129 | }
130 |
131 | inline void reset(const size_t pos)
132 | {
133 | bits &= ~((uint64_t)1 << pos);
134 | }
135 |
136 | inline void clear()
137 | {
138 | bits = 0;
139 | }
140 |
141 | inline bool test(const size_t pos) const
142 | {
143 | return bits & ((uint64_t)1 << pos);
144 | }
145 |
146 | inline void flip()
147 | {
148 | bits ^= offset;
149 | }
150 |
151 | inline bool is_full()
152 | {
153 | return bits == offset;
154 | }
155 |
156 | inline size_t count()
157 | {
158 | return __builtin_popcountl(bits);
159 | }
160 |
161 | inline size_t first_set()
162 | {
163 | size_t idx = __builtin_ffsl(bits);
164 | return idx ? idx - 1 : MAX_LEAF_SIZE;
165 | }
166 |
167 | inline size_t first_zero()
168 | {
169 | size_t idx = __builtin_ffsl(bits ^ offset);
170 | return idx ? idx - 1 : MAX_LEAF_SIZE;
171 | }
172 |
173 | void print_bits()
174 | {
175 | for (uint64_t i = 0; i < 64; i++) { std::cout << ((bits >> i) & 1); }
176 | std::cout << std::endl;
177 | }
178 | };
179 |
180 |
181 | /*******************************************************
182 | Define node struture
183 | ********************************************************/
184 |
185 |
186 | struct BaseNode
187 | {
188 | bool isInnerNode;
189 |
190 | friend class FPtree;
191 |
192 | public:
193 | BaseNode();
194 | } __attribute__((aligned(64)));
195 |
196 |
197 |
198 | struct InnerNode : BaseNode
199 | {
200 | uint64_t nKey;
201 | uint64_t keys[MAX_INNER_SIZE];
202 | BaseNode* p_children[MAX_INNER_SIZE + 1];
203 |
204 | friend class FPtree;
205 |
206 | public:
207 | InnerNode();
208 | InnerNode(uint64_t key, BaseNode* left, BaseNode* right);
209 | InnerNode(const InnerNode& inner);
210 | ~InnerNode();
211 |
212 | // for using mempool only where constructor is not called
213 | void init(uint64_t key, BaseNode* left, BaseNode* right);
214 |
215 | // return index of child in p_children when searching key in this innernode
216 | uint64_t findChildIndex(uint64_t key);
217 |
218 | // remove key at index, default remove right child (or left child if false)
219 | void removeKey(uint64_t index, bool remove_right_child);
220 |
221 | // add key at index pos, default add child to the right
222 | void addKey(uint64_t index, uint64_t key, BaseNode* child, bool add_child_right);
223 | } __attribute__((aligned(64)));
224 |
225 |
226 | struct LeafNode : BaseNode
227 | {
228 | __attribute__((aligned(64))) uint8_t fingerprints[MAX_LEAF_SIZE];
229 | Bitset bitmap;
230 |
231 | KV kv_pairs[MAX_LEAF_SIZE];
232 |
233 | #ifdef PMEM
234 | TOID(struct LeafNode) p_next;
235 | #else
236 | LeafNode* p_next;
237 | #endif
238 |
239 | std::atomic lock;
240 |
241 | friend class FPtree;
242 |
243 | public:
244 | #ifndef PMEM
245 | LeafNode();
246 | LeafNode(const LeafNode& leaf);
247 | LeafNode& operator=(const LeafNode& leaf);
248 | #endif
249 |
250 | inline bool isFull() { return this->bitmap.is_full(); }
251 |
252 | void addKV(struct KV kv);
253 |
254 | // find index of kv that has kv.key = key, return MAX_LEAF_SIZE if key not found
255 | uint64_t findKVIndex(uint64_t key);
256 |
257 | // return min key in leaf
258 | uint64_t minKey();
259 |
260 | bool Lock()
261 | {
262 | uint64_t expected = 0;
263 | return std::atomic_compare_exchange_strong(&lock, &expected, 1);
264 | }
265 | void Unlock()
266 | {
267 | this->lock = 0;
268 | }
269 |
270 | void getStat(uint64_t key, LeafNodeStat& lstat);
271 | } __attribute__((aligned(64)));
272 |
273 |
274 | #ifdef PMEM
275 | struct argLeafNode
276 | {
277 | size_t size;
278 | bool isInnerNode;
279 | Bitset bitmap;
280 | __attribute__((aligned(64))) uint8_t fingerprints[MAX_LEAF_SIZE];
281 | KV kv_pairs[MAX_LEAF_SIZE];
282 | uint64_t lock;
283 |
284 | argLeafNode(LeafNode* leaf)
285 | {
286 | isInnerNode = false;
287 | size = sizeof(struct LeafNode);
288 | memcpy(fingerprints, leaf->fingerprints, sizeof(leaf->fingerprints));
289 | memcpy(kv_pairs, leaf->kv_pairs, sizeof(leaf->kv_pairs));
290 | bitmap = leaf->bitmap;
291 | lock = 1;
292 | }
293 |
294 | argLeafNode(struct KV kv)
295 | {
296 | isInnerNode = false;
297 | size = sizeof(struct LeafNode);
298 | kv_pairs[0] = kv;
299 | fingerprints[0] = getOneByteHash(kv.key);
300 | bitmap.set(0);
301 | lock = 0;
302 | }
303 | };
304 |
305 | static int constructLeafNode(PMEMobjpool *pop, void *ptr, void *arg);
306 |
307 | struct List
308 | {
309 | TOID(struct LeafNode) head;
310 | };
311 |
312 | /*
313 | uLog
314 | */
315 | struct Log
316 | {
317 | TOID(struct LeafNode) PCurrentLeaf;
318 | TOID(struct LeafNode) PLeaf;
319 | };
320 |
321 | static const uint64_t sizeLogArray = 128;
322 |
323 | static boost::lockfree::queue splitLogQueue = boost::lockfree::queue(sizeLogArray);
324 | static boost::lockfree::queue deleteLogQueue = boost::lockfree::queue(sizeLogArray);
325 | #endif
326 |
327 |
328 | struct Stack //TODO: Get rid of Stack
329 | {
330 | public:
331 | static const uint64_t kMaxNodes = 32;
332 | InnerNode* innerNodes[kMaxNodes];
333 | uint64_t num_nodes;
334 |
335 | Stack() : num_nodes(0) {}
336 | ~Stack() { num_nodes = 0; }
337 |
338 | inline void push(InnerNode* node)
339 | {
340 | assert(num_nodes < kMaxNodes && "Error: exceed max stack size");
341 | this->innerNodes[num_nodes++] = node;
342 | }
343 |
344 | InnerNode* pop() { return num_nodes == 0 ? nullptr : this->innerNodes[--num_nodes]; }
345 |
346 | inline bool isEmpty() { return num_nodes == 0; }
347 |
348 | inline InnerNode* top() { return num_nodes == 0 ? nullptr : this->innerNodes[num_nodes - 1]; }
349 |
350 | inline void clear() { num_nodes = 0; }
351 | };
352 |
353 | static thread_local Stack stack_innerNodes;
354 | static thread_local InnerNode* inners[32];
355 | static thread_local short ppos[32];
356 |
357 | struct FPtree
358 | {
359 | BaseNode *root;
360 | tbb::speculative_spin_rw_mutex speculative_lock;
361 |
362 | InnerNode* right_most_innnerNode; // for bulkload
363 |
364 | public:
365 | FPtree();
366 | ~FPtree();
367 |
368 | BaseNode* getRoot () { return this->root; }
369 |
370 | void printFPTree(std::string prefix, BaseNode *root);
371 |
372 | // return flse if kv.key not found, otherwise set kv.value to value associated with kv.key
373 | uint64_t find(uint64_t key);
374 |
375 | // return false if kv.key not found, otherwise update value associated with key
376 | bool update(struct KV kv);
377 |
378 | // return false if key already exists, otherwise insert kv
379 | bool insert(struct KV kv);
380 |
381 | // delete key from tree
382 | bool deleteKey(uint64_t key);
383 |
384 | // TODO: fix/optimize iterator based scan methods
385 | // initialize scan by finding the first kv with kv.key >= key
386 | void scanInitialize(uint64_t key);
387 |
388 | KV scanNext();
389 |
390 | bool scanComplete();
391 |
392 | uint64_t rangeScan(uint64_t key, uint64_t scan_size, char* result);
393 |
394 | #ifdef PMEM
395 | bool bulkLoad(float load_factor);
396 |
397 | void recoverSplit(Log* uLog);
398 |
399 | void recoverDelete(Log* uLog);
400 |
401 | void recover();
402 |
403 | void pmemInit(const char* path_ptr, long long pool_size);
404 |
405 | void showList();
406 | #endif
407 |
408 | private:
409 | // return leaf that may contain key, does not push inner nodes
410 | LeafNode* findLeaf(uint64_t key);
411 |
412 | // return leaf that may contain key, push all innernodes on traversal path into stack
413 | LeafNode* findLeafAndPushInnerNodes(uint64_t key);
414 |
415 | uint64_t findSplitKey(LeafNode* leaf);
416 |
417 | uint64_t splitLeaf(LeafNode* leaf);
418 |
419 | void updateParents(uint64_t splitKey, InnerNode* parent, BaseNode* leaf);
420 |
421 | void splitLeafAndUpdateInnerParents(LeafNode* reachedLeafNode, Result decision, struct KV kv,
422 | bool updateFunc, uint64_t prevPos);
423 |
424 | // merge parent with sibling, may incur further merges. Remove key from indexNode after
425 | void removeLeafAndMergeInnerNodes(short i, short indexNode_level);
426 |
427 | // try transfer a key from sender to receiver, sender and receiver should be immediate siblings
428 | // If receiver & sender are inner nodes, will assume the only child in receiver is at index 0
429 | // return false if cannot borrow key from sender
430 | bool tryBorrowKey(InnerNode* parent, uint64_t receiver_idx, uint64_t sender_idx);
431 |
432 | uint64_t minKey(BaseNode* node);
433 |
434 | LeafNode* minLeaf(BaseNode* node);
435 |
436 | LeafNode* maxLeaf(BaseNode* node);
437 |
438 | void sortKV();
439 |
440 | uint64_t size_volatile_kv;
441 | KV volatile_current_kv[MAX_LEAF_SIZE];
442 |
443 | LeafNode* current_leaf;
444 | uint64_t bitmap_idx;
445 |
446 | friend class Inspector;
447 | };
448 |
--------------------------------------------------------------------------------
/fptree_wrapper.cpp:
--------------------------------------------------------------------------------
1 | #include "fptree_wrapper.hpp"
2 |
3 | extern "C" tree_api* create_tree(const tree_options_t& opt)
4 | {
5 |
6 | #ifdef PMEM
7 | auto path_ptr = new std::string(opt.pool_path);
8 |
9 | if (*path_ptr == "")
10 | path_ptr->assign("./pool");
11 |
12 | const char* path = (*path_ptr).c_str();
13 |
14 | struct stat s;
15 | if (stat(path, &s) == 0 && (s.st_mode & S_IFDIR))
16 | path_ptr->assign("./pool");
17 |
18 | printf("hpp path: %s", path);
19 |
20 | long long pool_size = (opt.pool_size == 0) ? PMEMOBJ_POOL_SIZE : pool_size;
21 |
22 | return new fptree_wrapper(path, pool_size);
23 | #else
24 | return new fptree_wrapper();
25 | #endif
26 | }
27 |
--------------------------------------------------------------------------------
/fptree_wrapper.hpp:
--------------------------------------------------------------------------------
1 | #ifndef __FPTREE_WRAPPER_HPP__
2 | #define __FPTREE_WRAPPER_HPP__
3 |
4 | #include "tree_api.hpp"
5 | #include "fptree.h"
6 |
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | // #define DEBUG_MSG
13 |
14 | class fptree_wrapper : public tree_api
15 | {
16 | public:
17 | #ifdef PMEM
18 | fptree_wrapper(const char* path_ptr, long long pool_size);
19 | #else
20 | fptree_wrapper();
21 | #endif
22 | virtual ~fptree_wrapper();
23 |
24 | virtual bool find(const char* key, size_t key_sz, char* value_out) override;
25 | virtual bool insert(const char* key, size_t key_sz, const char* value, size_t value_sz) override;
26 | virtual bool update(const char* key, size_t key_sz, const char* value, size_t value_sz) override;
27 | virtual bool remove(const char* key, size_t key_sz) override;
28 | virtual int scan(const char* key, size_t key_sz, int scan_sz, char*& values_out) override;
29 |
30 | private:
31 | FPtree tree_;
32 | };
33 |
34 |
35 | #ifdef PMEM
36 | fptree_wrapper::fptree_wrapper(const char* path_ptr, long long pool_size)
37 | {
38 | tree_.pmemInit(path_ptr, pool_size);
39 | }
40 | #else
41 | fptree_wrapper::fptree_wrapper()
42 | {
43 | }
44 | #endif
45 |
46 |
47 |
48 | fptree_wrapper::~fptree_wrapper()
49 | {
50 | }
51 |
52 | bool fptree_wrapper::find(const char* key, size_t key_sz, char* value_out)
53 | {
54 | // For now only support 8 bytes key and value (uint64_t)
55 | uint64_t value = tree_.find(*reinterpret_cast(const_cast(key)));
56 | if (value == 0)
57 | {
58 | #ifdef DEBUG_MSG
59 | printf("Search key not found!\n");
60 | #endif
61 | return false;
62 | }
63 | memcpy(value_out, &value, sizeof(value));
64 | return true;
65 | }
66 |
67 |
68 | bool fptree_wrapper::insert(const char* key, size_t key_sz, const char* value, size_t value_sz)
69 | {
70 | KV kv = KV(*reinterpret_cast(const_cast(key)), *reinterpret_cast(const_cast(value)));
71 | if (!tree_.insert(kv))
72 | {
73 | #ifdef DEBUG_MSG
74 | printf("Insert failed\n");
75 | #endif
76 | return false;
77 | }
78 | return true;
79 | }
80 |
81 | bool fptree_wrapper::update(const char* key, size_t key_sz, const char* value, size_t value_sz)
82 | {
83 | KV kv = KV(*reinterpret_cast(const_cast(key)), *reinterpret_cast(const_cast(value)));
84 | if (!tree_.update(kv))
85 | {
86 | #ifdef DEBUG_MSG
87 | printf("Update failed\n");
88 | #endif
89 | return false;
90 | }
91 | return true;
92 | }
93 |
94 | bool fptree_wrapper::remove(const char* key, size_t key_sz)
95 | {
96 | if (!tree_.deleteKey(*reinterpret_cast(const_cast(key))))
97 | {
98 | #ifdef DEBUG_MSG
99 | printf("Remove failed\n");
100 | #endif
101 | return false;
102 | }
103 | return true;
104 | }
105 |
106 | int fptree_wrapper::scan(const char* key, size_t key_sz, int scan_sz, char*& values_out)
107 | {
108 | constexpr size_t ONE_MB = 1ULL << 20;
109 | static thread_local char results[ONE_MB];
110 | int scanned = tree_.rangeScan(*reinterpret_cast(const_cast(key)), (uint64_t)scan_sz, results);
111 | #ifdef DEBUG_MSG
112 | printf("%d records scanned\n", scanned);
113 | #endif
114 | return scanned;
115 | }
116 |
117 | #endif
118 |
--------------------------------------------------------------------------------
/inspector.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | #include "fptree.h"
13 |
14 |
15 | #define NUM_RECORDS 10000000 // Number of records to start with
16 |
17 | #define NUM_WORKER_THREAD 16 // Number of worker threads for insert, delete
18 |
19 | #define NUM_INSPECTOR_THREAD 48 // Number of threads that walks tree in parallel
20 |
21 | #define CHECK_INNER 0 // Whether verifies correctness of innernode
22 |
23 | #define CHECK_INSERT 1 // Check tree integrity after loading NUM_RECORDS records
24 |
25 | #define DELETE 1 // Whether delete half of keys after loading
26 | #define CHECK_DELETE 1 // Check tree integrity after delete half
27 |
28 | #define BULK_LOAD 0 // Create another tree using the test_pool, check integrity
29 |
30 | static thread_local std::unordered_map count_;
31 |
32 | struct Queue
33 | {
34 | public:
35 |
36 | std::thread queue_[NUM_INSPECTOR_THREAD];
37 | uint64_t head, tail;
38 | bool empty;
39 |
40 | Queue() : head(0), tail(0), empty(true) {}
41 | ~Queue() {}
42 |
43 | inline bool push(std::thread t)
44 | {
45 | if (head == tail && !empty) // if full
46 | return false;
47 | queue_[tail] = std::move(t);
48 | tail = (tail + 1) % NUM_INSPECTOR_THREAD;
49 | empty = false;
50 | return true;
51 | }
52 |
53 | inline void pop() {
54 | if (empty)
55 | return;
56 | else
57 | {
58 | queue_[head].join();
59 | head = (head + 1) % NUM_INSPECTOR_THREAD;
60 | if (head == tail)
61 | empty = true;
62 | }
63 | }
64 |
65 | inline uint64_t size()
66 | {
67 | if (head < tail)
68 | return tail - head;
69 | else if (head > tail)
70 | return NUM_INSPECTOR_THREAD - head + tail;
71 | else
72 | return empty? 0 : NUM_INSPECTOR_THREAD;
73 | }
74 |
75 | inline bool isEmpty() { return empty; }
76 |
77 | inline bool full() { return head == tail && !empty; }
78 |
79 | inline void clear() { head = 0; tail = 0; empty = true; }
80 | };
81 |
82 | class Inspector {
83 | public:
84 | Inspector();
85 | ~Inspector();
86 |
87 | void ClearStats();
88 | bool SanityCheck(FPtree& tree, std::vector& keys, std::vector& values);
89 | void KVPresenceCheck(FPtree& tree, std::vector& keys, std::vector& values);
90 | void InnerNodeOrderCheck(InnerNode* node, std::vector& keys);
91 | void SubtreeOrderCheck(BaseNode* node, uint64_t min, uint64_t max, std::vector& keys, bool stop);
92 |
93 | uint64_t kv_missing_count_;
94 | uint64_t kv_duplicate_count_;
95 | uint64_t inner_order_violation_count_;
96 | uint64_t inner_boundary_violation_count_;
97 | uint64_t inner_duplicate_count_;
98 | uint64_t inner_invalid_count_;
99 | };
100 |
101 | Inspector::Inspector()
102 | {
103 | ClearStats();
104 | }
105 |
106 | Inspector::~Inspector(){}
107 |
108 | bool Inspector::SanityCheck(FPtree& tree, std::vector& keys, std::vector& values)
109 | {
110 | ClearStats();
111 |
112 | std::cout << "\nLeafNode check\n";
113 | KVPresenceCheck(tree, keys, values);
114 |
115 | #if CHECK_INNER == 1
116 | std::cout << "\nInnerNode check\n";
117 | if (tree.root->isInnerNode){
118 |
119 | std::vector vec(keys);
120 | std::sort(vec.begin(), vec.end());
121 | InnerNodeOrderCheck(reinterpret_cast(tree.root), vec);
122 | }
123 | #else
124 | printf("Skip innernode check.\n");
125 | #endif
126 |
127 | return !(kv_missing_count_ || kv_duplicate_count_ || inner_order_violation_count_ ||
128 | inner_boundary_violation_count_ || inner_duplicate_count_ || inner_invalid_count_);
129 | }
130 |
131 | void Inspector::KVPresenceCheck(FPtree& tree, std::vector& keys, std::vector& values)
132 | {
133 | // missing kv check in normal read approach
134 | uint64_t val, prev_kv_missing_count = kv_missing_count_, prev_kv_duplicate_count_ = kv_duplicate_count_;
135 | for (uint64_t i = 0; i < keys.size(); i++)
136 | {
137 | val = tree.find(keys[i]);
138 | if (val != values[i])
139 | {
140 | std::cout << "Missing Key: " << keys[i] << " Value: " << values[i] << std::endl;
141 | kv_missing_count_ ++;
142 | }
143 | }
144 | // duplicate check in scan approach
145 | LeafNode* cur = tree.minLeaf(tree.root);
146 | std::vector vec;
147 | vec.reserve(keys.size());
148 | uint64_t i;
149 | while(cur != nullptr)
150 | {
151 | for (i = 0; i < MAX_LEAF_SIZE; i++)
152 | if (cur->bitmap.test(i))
153 | vec.push_back(cur->kv_pairs[i].key);
154 | #ifdef PMEM
155 | cur = (struct LeafNode *) pmemobj_direct((cur->p_next).oid);
156 | #else
157 | cur = cur->p_next;
158 | #endif
159 | }
160 | std::sort(vec.begin(), vec.end());
161 | for (std::vector::iterator it = vec.begin() + 1 ; it != vec.end(); ++it)
162 | if (*it == *(it-1))
163 | {
164 | std::cout << "Duplicate key: " << *it << std::endl;
165 | kv_duplicate_count_ ++;
166 | }
167 | // deduction
168 | uint64_t scan_size = vec.size() - (kv_duplicate_count_ - prev_kv_duplicate_count_);
169 | uint64_t read_size = keys.size() - (kv_missing_count_ - prev_kv_missing_count);
170 | std::cout << "Records checked through traversing: " << read_size << std::endl;
171 | std::cout << "Records checked through scanning: " << scan_size << std::endl;
172 | if (scan_size > read_size)
173 | std::cout << "Some keys can be accessed through scanning leaf list but not through normal traversal: \
174 | tree likely contains abandoned leafnode(s) and/or additional records\n";
175 | else if (scan_size < read_size)
176 | std::cout << "Some keys can be accessed through normal traversal but not through scanning leaf lists: \
177 | leaf list is likely broken\n";
178 | //TODO: add element check in both vectors
179 | }
180 |
181 |
182 |
183 | void Inspector::InnerNodeOrderCheck(InnerNode* node, std::vector& keys)
184 | {
185 | Queue q;
186 | SubtreeOrderCheck(node, 0, std::numeric_limits::max(), keys, true);
187 | uint64_t child_idx = 0, min, max;
188 | std::cout << "Root has " << node->nKey + 1 << " children\n";
189 | while (child_idx <= node->nKey)
190 | {
191 | min = 0;
192 | if (child_idx)
193 | min = node->keys[child_idx - 1];
194 | max = std::numeric_limits::max();
195 | if (child_idx != node->nKey)
196 | max = node->keys[child_idx];
197 | if (q.full())
198 | q.pop();
199 | q.push(std::thread(&Inspector::SubtreeOrderCheck, this, node->p_children[child_idx++], min, max, std::ref(keys), false));
200 | }
201 | while(!q.isEmpty())
202 | q.pop();
203 | }
204 |
205 | void Inspector::SubtreeOrderCheck(BaseNode* node, uint64_t min, uint64_t max, std::vector& keys, bool stop = false)
206 | {
207 | uint64_t val, cur_min = std::numeric_limits::max(), cur_max = 0;
208 | if (node->isInnerNode)
209 | {
210 | count_.clear();
211 | InnerNode * inner = reinterpret_cast (node);
212 | assert(inner->nKey > 0 && "Reached empty innernode!\n");
213 | for (uint64_t i = 0; i < inner->nKey; i++)
214 | {
215 | val = inner->keys[i];
216 | count_[val]++;
217 | if (std::find(keys.begin(), keys.end(), val) == keys.end())
218 | {
219 | std::cout << "Innernode invalid key: " << val << std::endl;
220 | inner_invalid_count_++;
221 | }
222 | if (i && val < inner->keys[i-1])
223 | {
224 | std::cout << "Innernode order violation: " << inner->keys[i] << " " << inner->keys[i-1] << std::endl;
225 | inner_order_violation_count_++;
226 | }
227 | }
228 | for (auto &elt : count_)
229 | {
230 | if (elt.second > 1)
231 | {
232 | std::cout << "Innernode duplicate key: " << elt.first << " found " << elt.second << " times\n";
233 | inner_duplicate_count_++;
234 | }
235 | if (elt.first < cur_min)
236 | cur_min = elt.first;
237 | if (elt.first > cur_max)
238 | cur_max = elt.first;
239 | }
240 | if (cur_min < min || cur_max >= max)
241 | {
242 | std::cout << "Innernode boundary violation: " << cur_min << " " << cur_max
243 | << " Min: " << min << " " << " Max: " << max << std::endl;
244 | inner_boundary_violation_count_++;
245 | }
246 | if (!stop)
247 | {
248 | for (uint64_t i = 0; i <= inner->nKey; i++)
249 | {
250 | if (i == 0)
251 | SubtreeOrderCheck(inner->p_children[i], min, inner->keys[0], keys);
252 | else if (i == inner->nKey)
253 | SubtreeOrderCheck(inner->p_children[i], inner->keys[inner->nKey-1], max, keys);
254 | else
255 | SubtreeOrderCheck(inner->p_children[i], inner->keys[i-1], inner->keys[i], keys);
256 | }
257 | }
258 | }
259 | }
260 |
261 | void Inspector::ClearStats()
262 | {
263 | kv_missing_count_ = 0;
264 | kv_duplicate_count_ = 0;
265 | inner_order_violation_count_ = 0;
266 | inner_boundary_violation_count_ = 0;
267 | inner_duplicate_count_ = 0;
268 | inner_invalid_count_ = 0;
269 | }
270 |
271 | void shuffle(std::vector& keys, std::vector& values) {
272 | uint64_t i, j, times = keys.size()/2;
273 | for (uint64_t k = 0; k < times; k++)
274 | {
275 | i = rand() % keys.size();
276 | j = rand() % keys.size();
277 | std::iter_swap(keys.begin() + i, keys.begin() + j);
278 | std::iter_swap(values.begin() + i, values.begin() + j);
279 | }
280 | }
281 |
282 | void thread_load(FPtree & tree, std::vector & keys, std::vector & values, uint64_t id) {
283 | uint64_t workload = NUM_RECORDS / NUM_WORKER_THREAD, stop;
284 | if (id == NUM_WORKER_THREAD - 1) // last thread, load all keys left
285 | stop = NUM_RECORDS;
286 | else // just normal workload
287 | stop = (id + 1) * workload;
288 | for (uint64_t i = id * workload; i < stop; i++)
289 | if (!tree.insert(KV(keys[i], values[i])))
290 | {
291 | printf("Insert failed! Key: %llu Value: %llu\n", keys[i], values[i]);
292 | exit(1);
293 | }
294 | }
295 |
296 | void thread_delete(FPtree & tree, std::vector & keys, uint64_t id) {
297 | uint64_t half = keys.size() / 2;
298 | uint64_t workload = (NUM_RECORDS - half) / NUM_WORKER_THREAD, stop;
299 | if (id == NUM_WORKER_THREAD - 1) // last thread, delete all keys left
300 | stop = NUM_RECORDS;
301 | else // just normal workload
302 | stop = (id + 1) * workload + half;
303 | for (uint64_t i = id * workload + half; i < stop; i++)
304 | if (!tree.deleteKey(keys[i]))
305 | printf("Delete failed! Key: %llu \n", keys[i]);
306 | }
307 |
308 | int main()
309 | {
310 | printf("Number of Records: %llu\n", NUM_RECORDS);
311 | printf("Number of worker thread: %d\n", NUM_WORKER_THREAD);
312 | printf("Number of inspector thread: %d\n", NUM_INSPECTOR_THREAD);
313 |
314 |
315 | srand (0); //(time(NULL));
316 | std::independent_bits_engine rbe;
317 | std::vector keys(NUM_RECORDS);
318 | std::generate(begin(keys), end(keys), std::ref(rbe));
319 | std::vector values(NUM_RECORDS);
320 | std::generate(begin(values), end(values), std::ref(rbe));
321 | std::cout << "Key generation complete, start loading...\n";
322 |
323 | const char* path = "./test_pool";
324 | FPtree fptree;
325 | fptree.pmemInit(path, PMEMOBJ_POOL_SIZE);
326 |
327 | Inspector ins;
328 | std::vector workers(NUM_WORKER_THREAD);
329 |
330 | auto start = std::chrono::steady_clock::now();
331 |
332 | for (uint64_t i = 0; i < NUM_WORKER_THREAD; i++)
333 | workers[i] = std::thread(thread_load, std::ref(fptree), std::ref(keys), std::ref(values), i);
334 | for (uint64_t i = 0; i < NUM_WORKER_THREAD; i++)
335 | workers[i].join();
336 | // for (uint64_t i = 0; i < NUM_RECORDS; i++)
337 | // fptree.insert(KV(keys[i], values[i]));
338 | std::cout << "Loading complete (" << std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count() << " sec). start testing...\n";
339 |
340 |
341 | #if CHECK_INSERT == 1
342 | printf("Starting sanity check for insert...\n");
343 | if (ins.SanityCheck(fptree, keys, values))
344 | std::cout << "Sanity check for insertion passed!\n";
345 | else
346 | {
347 | std::cout << "Sanity check for insertion failed!\n";
348 | //fptree.printFPTree("├──", fptree.getRoot());
349 | // #ifdef PMEM
350 | // showList();
351 | // #endif
352 | return -1;
353 | }
354 | #else
355 | printf("Skip insertion check.\n");
356 | #endif
357 |
358 |
359 | #if DELETE == 1
360 | printf("Deleting half of keys randomly.\n");
361 | shuffle(keys, values);
362 | start = std::chrono::steady_clock::now();
363 | uint64_t half = keys.size() / 2;
364 | workers.clear();
365 | for (uint64_t i = 0; i < NUM_WORKER_THREAD; i++)
366 | workers[i] = std::thread(thread_delete, std::ref(fptree), std::ref(keys), i);
367 | for (uint64_t i = 0; i < NUM_WORKER_THREAD; i++)
368 | workers[i].join();
369 | std::cout << "Deletion complete (" << std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count() << " sec)\n";
370 | keys.erase(keys.begin() + half, keys.end());
371 | values.erase(values.begin() + half, values.end());
372 |
373 | #if CHECK_DELETE == 1
374 | printf("Starting sanity check for delete...\n");
375 | if (ins.SanityCheck(fptree, keys, values))
376 | std::cout << "Sanity check for deletion passed!\n";
377 | else
378 | return -1;
379 | #else
380 | printf("Skip deletion check.\n");
381 | #endif
382 | #endif
383 |
384 | #if BULK_LOAD
385 | printf("Bulk load current index!\n");
386 | FPtree bulk_load_tree;
387 | if (ins.SanityCheck(bulk_load_tree, keys, values))
388 | std::cout << "Sanity check for bulk load passed!\n";
389 | else
390 | return -1;
391 | #else
392 | #endif
393 |
394 | // #if UPDATE == 1
395 | // printf("Updating all values...\n");
396 |
397 | // #endif
398 |
399 | return 0;
400 | }
--------------------------------------------------------------------------------