Commit 6712ce72 authored by Philipp Götze's avatar Philipp Götze

🔨 Reworked the persistent skip lists and created/enhanced tests and benchmarks

parent f173a8c4
......@@ -12,3 +12,6 @@ do_bench(trees/tree_merge)
#do_bench(ptable/insert ptable)
do_bench(ptable/scan ptable)
do_bench(ptable/point ptable)
# PSKIPLISTS
do_bench(pskiplists/pskip_get)
do_bench(pskiplists/pskip_insert)
/*
* Copyright (C) 2017-2020 DBIS Group - TU Ilmenau, All Rights Reserved.
*
* This file is part of our NVM-based Data Structures repository.
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DBIS_SKIP_COMMON_HPP
#define DBIS_SKIP_COMMON_HPP
#include <unistd.h>
#include "config.h"
#define UNIT_TESTS
#include "woPSkiplist.hpp"
using namespace dbis::pskiplists;
/*========== Types and constants =================================================================*/
constexpr auto CACHE_LINE = 64;
constexpr auto DCPMM_LINE = 4 * CACHE_LINE;
constexpr auto KiB = 1024;
constexpr auto MiB = KiB * KiB;
constexpr auto GiB = MiB * KiB;
constexpr auto L3 = 14080 * KiB;
constexpr auto TARGET_NODE_SIZE = 16 * DCPMM_LINE;
constexpr auto ArraySize = 4 * L3 / DCPMM_LINE; /// or ... / TARGET_NODE_SIZE
constexpr auto NODE_PTR_SIZE = sizeof(pptr<int>);
const std::string path = dbis::gPmemPath + "pskiplist_bench.data";
constexpr auto LAYOUT = "PSkipList";
constexpr auto POOL_SIZE = 2ull * GiB;
constexpr auto LEVEL = 5;
using KeyType = unsigned long long;
using ValueType = std::tuple<int, int, double>;
template <size_t KEYS>
constexpr size_t getNodeKeys() {
constexpr auto wordBits = sizeof(unsigned long) * 8;
constexpr auto BM = (KEYS + wordBits - 1) / wordBits * 8; ///< bitmap size round to full words
constexpr auto FORW = LEVEL * NODE_PTR_SIZE;
constexpr auto CL = ((BM + FORW + 2 * sizeof(KeyType) + sizeof(size_t) + 63) / 64) * 64;
constexpr auto SIZE = CL + KEYS * (sizeof(KeyType) + sizeof(ValueType));
if constexpr (SIZE <= TARGET_NODE_SIZE)
return KEYS;
else
return getNodeKeys<KEYS - 1>();
}
constexpr size_t getNodeKeys() {
constexpr auto KEYS = ((TARGET_NODE_SIZE - 64) / (sizeof(KeyType) + sizeof(ValueType)));
return getNodeKeys<KEYS>();
}
constexpr auto NODEKEYS = getNodeKeys();
constexpr auto TARGET_KEY = NODEKEYS;
using ListType = woPSkiplist<KeyType, ValueType, NODEKEYS, LEVEL>;
#endif /// DBIS_SKIP_COMMON_HPP
/*
* Copyright (C) 2017-2020 DBIS Group - TU Ilmenau, All Rights Reserved.
*
* This file is part of our NVM-based Data Structures repository.
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <chrono>
#include <libpmempool.h>
#include <libpmemobj++/pool.hpp>
using pmem::obj::pool;
#include "common.hpp"
#include "benchmark/benchmark.h"
/*========== Get benchmark on skip list ==========================================================*/
static void BM_PSkiplistGet(benchmark::State &state) {
std::cout << "Keys per Node: " << NODEKEYS << " - " << sizeof(ListType::SkipNode) << "\n";
using NodeArray = pmem::obj::array<pptr<ListType::SkipNode>, ArraySize>;
struct root {
pptr<ListType> list;
pptr<NodeArray> nodeArray;
};
pool<root> pop;
pobj_alloc_class_desc alloc_class;
if (access(path.c_str(), F_OK) != 0) {
pop = pool<root>::create(path, LAYOUT, POOL_SIZE);
alloc_class =
pop.ctl_set<struct pobj_alloc_class_desc>("heap.alloc_class.new.desc", ListType::AllocClass);
transaction::run(pop, [&] { pop.root()->list = make_persistent<ListType>(alloc_class); });
} else {
LOG("Warning: " << path << " already exists");
pmempool_rm(path.c_str(), 0);
pop = pool<root>::create(path, LAYOUT, POOL_SIZE);
alloc_class =
pop.ctl_set<struct pobj_alloc_class_desc>("heap.alloc_class.new.desc", ListType::AllocClass);
transaction::run(pop, [&] { pop.root()->list = make_persistent<ListType>(alloc_class); });
}
auto &list = pop.root()->list;
auto &listRef = *list;
for (auto i = 0u; i < NODEKEYS; ++i) {
auto tup = ValueType(i + 1, (i + 1) * 100, (i + 1) * 1.0);
listRef.insert(i + 1, tup);
}
list.flush(pop);
auto &nodeArray = pop.root()->nodeArray;
auto &node = listRef.head->forward[0];
node.flush(pop);
transaction::run(pop, [&] {
nodeArray = make_persistent<NodeArray>();
for (int i = 0; i < ArraySize; ++i) {
auto &a = *nodeArray;
a[i] = make_persistent<ListType::SkipNode>(allocation_flag::class_id(alloc_class.class_id),
*node);
}
});
pop.drain();
std::srand(std::time(nullptr));
/// BENCHMARKING
for (auto _ : state) {
const auto nodePos = std::rand() % ArraySize;
const auto randNode = (*nodeArray)[nodePos].get();
const auto &nodeRef = *randNode;
ValueType value;
auto start = std::chrono::high_resolution_clock::now();
benchmark::DoNotOptimize(nodeRef);
listRef.searchInNode(randNode, TARGET_KEY, value);
benchmark::DoNotOptimize(nodeRef);
auto end = std::chrono::high_resolution_clock::now();
auto elapsed_seconds = std::chrono::duration_cast<std::chrono::duration<double>>(end - start);
state.SetIterationTime(elapsed_seconds.count());
}
/// Clean up
// listRef.printList(0);
pop.close();
pmempool_rm(path.c_str(), 0);
}
BENCHMARK(BM_PSkiplistGet)->UseManualTime();
BENCHMARK_MAIN();
/*
* Copyright (C) 2017-2020 DBIS Group - TU Ilmenau, All Rights Reserved.
*
* This file is part of our NVM-based Data Structures repository.
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <chrono>
#include <libpmempool.h>
#include <libpmemobj++/pool.hpp>
using pmem::obj::pool;
#include "common.hpp"
#include "benchmark/benchmark.h"
#ifdef ENABLE_PCM
#include <cpucounters.h>
#endif
void prepare(const pptr<ListType> &list);
uint64_t pmmwrites = 0;
uint64_t pmmreads = 0;
uint64_t modBytes = 0; ///< modified Bytes
/*========== Insert benchmark on skip list =======================================================*/
static void BM_PSkiplistInsert(benchmark::State &state) {
#ifdef ENABLE_PCM
PCM *pcm_ = PCM::getInstance();
auto s = pcm_->program();
if (s != PCM::Success) {
std::cerr << "Error creating PCM instance: " << s << std::endl;
if (s == PCM::PMUBusy)
pcm_->resetPMU();
else
exit(0);
}
#endif
std::cout << "Keys per Node: " << NODEKEYS << " - " << sizeof(ListType::SkipNode) << "\n";
using NodeArray = pmem::obj::array<pptr<ListType::SkipNode>, ArraySize>;
struct root {
pptr<ListType> list;
pptr<NodeArray> nodeArray;
};
pool<root> pop;
pobj_alloc_class_desc alloc_class;
if (access(path.c_str(), F_OK) != 0) {
pop = pool<root>::create(path, LAYOUT, POOL_SIZE);
alloc_class =
pop.ctl_set<struct pobj_alloc_class_desc>("heap.alloc_class.new.desc", ListType::AllocClass);
transaction::run(pop, [&] { pop.root()->list = make_persistent<ListType>(alloc_class); });
} else {
LOG("Warning: " << path << " already exists");
pmempool_rm(path.c_str(), 0);
pop = pool<root>::create(path, LAYOUT, POOL_SIZE);
alloc_class =
pop.ctl_set<struct pobj_alloc_class_desc>("heap.alloc_class.new.desc", ListType::AllocClass);
transaction::run(pop, [&] { pop.root()->list = make_persistent<ListType>(alloc_class); });
}
auto &list = pop.root()->list;
prepare(list);
auto &listRef = *list;
list.flush(pop);
auto &nodeArray = pop.root()->nodeArray;
auto &node = listRef.head->forward[0];
node.flush(pop);
transaction::run(pop, [&] {
nodeArray = make_persistent<NodeArray>();
for (int i = 0; i < ArraySize; ++i) {
auto &a = *nodeArray;
a[i] = make_persistent<ListType::SkipNode>(allocation_flag::class_id(alloc_class.class_id),
*node);
}
});
pop.drain();
const auto reqVal = ValueType(TARGET_KEY, TARGET_KEY * 100, TARGET_KEY * 1.0);
#ifdef ENABLE_PCM
SocketCounterState before_sstate;
SocketCounterState after_sstate;
#endif
/// Lambda function for measured part (needed twice)
auto benchmark = [&pop, &listRef, &reqVal] (ListType::SkipNode* node) {
auto &nodeRef = *node;
benchmark::DoNotOptimize(nodeRef);
listRef.insertInNode(node, TARGET_KEY, reqVal);
pop.flush(node, TARGET_NODE_SIZE);
pop.drain();
benchmark::DoNotOptimize(nodeRef);
benchmark::ClobberMemory();
};
/// BENCHMARK Writes
if (pmmwrites == 0) {
auto randNode = (*nodeArray)[0].get();
//dbis::PersistEmulation::getBytesWritten();
#ifdef ENABLE_PCM
before_sstate = getSocketCounterState(0);
#endif
benchmark(randNode);
#ifdef ENABLE_PCM
after_sstate = getSocketCounterState(0);
pmmreads = getBytesReadFromPMM(before_sstate, after_sstate);
pmmwrites = getBytesWrittenToPMM(before_sstate, after_sstate);
#endif
//modBytes = dbis::PersistEmulation::getBytesWritten();
*randNode = *node; ///< reset the modified node
pop.persist(randNode, TARGET_NODE_SIZE);
}
/// BENCHMARK Timing
/// avoid output to stdout during benchmark
std::cout.setstate(std::ios_base::failbit);
std::srand(std::time(nullptr));
for (auto _ : state) {
const auto nodePos = std::rand() % ArraySize;
auto randNode = (*nodeArray)[nodePos].get();
auto &nodeRef = *randNode;
auto start = std::chrono::high_resolution_clock::now();
benchmark(randNode);
auto end = std::chrono::high_resolution_clock::now();
nodeRef = *node; ///< reset the modified node
pop.persist(randNode, TARGET_NODE_SIZE);
auto elapsed_seconds = std::chrono::duration_cast<std::chrono::duration<double>>(end - start);
state.SetIterationTime(elapsed_seconds.count());
}
/// Evaluation & cleanup
// listRef.printList(0);
std::cout.clear();
std::cout << "Iterations:" << state.iterations() << '\n';
std::cout << "PMM Reads:" << pmmreads << '\n';
std::cout << "Writes:" << modBytes << '\n';
std::cout << "PMM Writes:" << pmmwrites << '\n';
std::cout << "Elements:" << NODEKEYS << '\n';
pop.close();
pmempool_rm(path.c_str(), 0);
}
BENCHMARK(BM_PSkiplistInsert)->UseManualTime();
BENCHMARK_MAIN();
/* preparing inserts */
void prepare(const pptr<ListType> &list) {
auto &listRef = *list;
auto insertLoop = [&listRef](const auto start, const auto end) {
for (auto j = start; j < end + 1; ++j) {
auto tup = ValueType(j, j * 100, j * 1.0);
listRef.insert(j, tup);
}
};
switch (TARGET_KEY) {
case 1 /*first*/:
insertLoop(2, NODEKEYS);
break;
case NODEKEYS /*last*/:
insertLoop(1, NODEKEYS - 1);
break;
case (NODEKEYS + 1) / 2 /*middle*/: {
insertLoop(1, TARGET_KEY - 1);
insertLoop(TARGET_KEY + 1, NODEKEYS);
}
}
}
PMem-based Skip Lists
=====================
:sparkles: TODOs
----------------
- [ ] More Variations similar to PBPTrees
- [ ] Provide deletes and underflow procedure
- [ ] Extent benchmarks and test cases
:books: Documentation
---------------------
Currently there two types of skip lists_:
| | |
|-|-|
| simplePSkiplist | a persistent skip list with a single entry per node |
| woPSkiplist | a persistent skip list with an arbitrary number of entries per nodes |
This diff is collapsed.
This diff is collapsed.
/*
* Copyright (C) 2017-2020 DBIS Group - TU Ilmenau, All Rights Reserved.
*
* This file is part of our NVM-based Data Structures repository.
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DBIS_RANDOM_HPP
#define DBIS_RANDOM_HPP
#include <cstdint> ///< uint32_t/uint64_t
#include <cstdlib> ///< size_t
namespace dbis {
class Random {
uint32_t seed;
public:
explicit Random(uint32_t s) : seed(s & 0x7fffffffu) {
if (seed == 0 || seed == 2147483647L) {
seed = 1;
}
}
inline uint32_t Next() {
static constexpr uint32_t M = 2147483647L;
static constexpr uint64_t A = 16807;
const uint64_t product = seed * A;
seed = static_cast<uint32_t>((product >> 31) + (product & M));
if (seed > M) seed -= M;
return seed;
}
inline uint32_t Uniform(size_t n) {
return (Next() % n);
}
inline bool OneIn(size_t n) {
return (Next() % n) == 0;
}
inline uint32_t Skewed(size_t max_log) {
return Uniform(1 << Uniform(max_log + 1));
}
}; /// class Random
} /// namespace dbis
#endif /// DBIS_RANDOM_HPP
......@@ -27,6 +27,7 @@ do_test(PTableInfoTest ptable)
do_test(PTupleTest ptable)
do_test(PTableTest ptable)
# woPSkiplist
do_test(simplePSkiplistTest)
do_test(woPSkiplistTest)
# PTries
do_test(BaseTrieTest)
......
/*
* Copyright (C) 2017-2020 DBIS Group - TU Ilmenau, All Rights Reserved.
*
* This file is part of our NVM-based Data Structures repository.
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <unistd.h>
#include "catch.hpp"
#include "config.h"
#define UNIT_TESTS 1
#include "simplePSkiplist.hpp"
using namespace dbis::pskiplists;
using pmem::obj::make_persistent;
using pmem::obj::persistent_ptr;
using pmem::obj::pool;
using pmem::obj::transaction;
TEST_CASE("Insert and lookup key", "[simplePSkiplist]") {
using simplePSkipType = simplePSkiplist<int, int, 5>;
struct root {
persistent_ptr<simplePSkipType> skiplist;
};
pool<root> pop;
const std::string path = dbis::gPmemPath + "simplePSkiplistTest";
if (access(path.c_str(), F_OK) != 0) {
pop = pool<root>::create(path, "simplePSkiplist", ((size_t)(1024 * 1024 * 16)));
} else {
pop = pool<root>::open(path, "simplePSkiplist");
pop.root()->skiplist->recover();
}
auto q = pop.root();
auto &rootRef = *q;
if (!rootRef.skiplist)
transaction::run(pop, [&] { rootRef.skiplist = make_persistent<simplePSkipType>(); });
/* -------------------------------------------------------------------------------------------- */
SECTION("Looking up a key") {
auto &sl = *rootRef.skiplist;
auto nextNode = sl.head->forward[0];
for (auto i = 5; i > 0; --i) {
pptr<simplePSkipType::SkipNode> newNode;
sl.newSkipNode(i, i, 0, newNode);
sl.head->forward[0] = newNode;
newNode->forward[0] = nextNode;
nextNode = newNode;
++sl.nodeCount.get_rw();
}
int val;
REQUIRE(sl.search(3, val));
REQUIRE(sl.search(5, val));
REQUIRE(!sl.search(0, val));
}
/* -------------------------------------------------------------------------------------------- */
SECTION("Inserting keys") {
auto &sl = *rootRef.skiplist;
for (auto i = 0u; i < 8; ++i) {
sl.printList();
sl.insert(i, i*i);
}
int val;
for (auto i = 0u; i < 8; ++i) {
REQUIRE(sl.search(i, val));
REQUIRE(val == i * i);
}
REQUIRE(sl.nodeCount == 8);
}
/* -------------------------------------------------------------------------------------------- */
SECTION("Recover the skip list") {
auto &sl = *rootRef.skiplist;
for (auto i = 0u; i < 10; ++i) {
sl.insert(i, i*i);
}
sl.~simplePSkipType();
sl.recover();
sl.insert(10, 100);
int value = 0;
REQUIRE(!sl.search(11, value));
REQUIRE(value == 0);
REQUIRE(sl.search(10, value));
REQUIRE(value == 100);
REQUIRE(sl.search(5, value));
REQUIRE(value == 25);
}
/* -------------------------------------------------------------------------------------------- */
SECTION("Print the skip list") {
auto &sl = *rootRef.skiplist;
for (auto i = 0u; i < 10; ++i) {
sl.insert(i, i*i);
}
sl.printList();
}
/* Clean up ----------------------------------------------------------------------------------- */
pop.close();
std::remove(path.c_str());
}
......@@ -19,6 +19,7 @@
#include "catch.hpp"
#include "config.h"
#define UNIT_TESTS 1
#include "woPSkiplist.hpp"
using namespace dbis::pskiplists;
......@@ -28,33 +29,132 @@ using pmem::obj::persistent_ptr;
using pmem::obj::pool;
using pmem::obj::transaction;
TEST_CASE("Insert and lookup key") {
using woPSkip = woPSkiplist<int, int, 8, 8>;
TEST_CASE("Insert and lookup key", "[woPSkiplist]") {
using woPSkipType5 = woPSkiplist<int, int, 5, 5>;
using woPSkipType8 = woPSkiplist<int, int, 8, 5>;
struct root {
persistent_ptr<woPSkip> skiplist;
persistent_ptr<woPSkipType5> skiplist5;
persistent_ptr<woPSkipType8> skiplist8;
};
pool<root> pop;
const std::string path = dbis::gPmemPath + "woPSkiplistTest";
//std::remove(path.c_str());
if (access(path.c_str(), F_OK) != 0)
if (access(path.c_str(), F_OK) != 0) {
pop = pool<root>::create(path, "woPSkiplist", ((size_t)(1024 * 1024 * 16)));
else
} else {
pop = pool<root>::open(path, "woPSkiplist");
pop.root()->skiplist5->recover();
pop.root()->skiplist8->recover();
}
auto q = pop.root();
auto &rootRef = *q;
const auto alloc_class = pop.ctl_set<struct pobj_alloc_class_desc>("heap.alloc_class.new.desc",
woPSkipType5::AllocClass);
if (!rootRef.skiplist5)
transaction::run(pop, [&] { rootRef.skiplist5 = make_persistent<woPSkipType5>(alloc_class); });
if (!rootRef.skiplist8)
transaction::run(pop, [&] { rootRef.skiplist8 = make_persistent<woPSkipType8>(alloc_class); });
/* -------------------------------------------------------------------------------------------- */
SECTION("Looking up a key") {
auto &sl = *rootRef.skiplist5;
auto &node = *sl.head