├── .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 | } --------------------------------------------------------------------------------