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

🔨 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 |
......@@ -19,209 +19,253 @@
#define SIMPLE_PSKIPLIST_HPP
#include <cstdlib>
#include <cstdint>
#include <iostream>
#include <libpmemobj++/utils.hpp>
#include <libpmemobj++/make_persistent.hpp>
#include <libpmemobj++/container/array.hpp>
#include <libpmemobj++/make_persistent.hpp>
#include <libpmemobj++/p.hpp>
#include <libpmemobj++/persistent_ptr.hpp>
#include <libpmemobj++/transaction.hpp>
#include <libpmemobj++/utils.hpp>
#include "utils/Random.hpp"
namespace dbis::pskiplists {
namespace pmemobj_exp = pmem::obj::experimental;
using pmem::obj::array;
using pmem::obj::delete_persistent;
using pmem::obj::make_persistent;
using pmem::obj::p;
using pmem::obj::persistent_ptr;
using pmem::obj::transaction;
template <typename Object>
using pptr = persistent_ptr<Object>;
class Random {
uint32_t seed;
public:
explicit Random(uint32_t s) : seed(s & 0x7fffffffu) {
if (seed == 0 || seed == 2147483647L) {
seed = 1;
}
}
uint32_t Next() {
static const uint32_t M = 2147483647L; // 2^31-1
static const uint64_t A = 16807; // bits 14, 8, 7, 5, 2, 1, 0
uint64_t product = seed * A;
seed = static_cast<uint32_t>((product >> 31) + (product & M));
if(seed>M) {
seed -= M;
}
return seed;
}
uint32_t Uniform(int n) { return (Next() % n); }
bool OneIn(int n) { return (Next() % n) == 0; }
uint32_t Skewed(int max_log) {
return Uniform(1 << Uniform(max_log + 1));
}
};
/**
* A persistent memory implementation of a skip list with a single record per node.
*
* @tparam KeyType the data type of the key
* @tparam ValueType the data type of the values associated with the key
* @tparam MAX_LEVEL the maximum number of levels
*/
template<typename KeyType, typename ValueType, int MAX_LEVEL>
class simplePSkiplist {
struct SkipNode {
p<KeyType> key;
p<ValueType> value;
persistent_ptr<persistent_ptr<SkipNode>> forward;
p<int> nodeLevel;
static constexpr auto MAX_KEY = std::numeric_limits<KeyType>::max();
static constexpr auto MIN_KEY = std::numeric_limits<KeyType>::min();
SkipNode() {}
#ifndef UNIT_TESTS
private:
#else
public:
#endif
SkipNode(const KeyType key, const ValueType value) {
this->key = key;
this->value = value;
/**
* A structure for representing a node of a skip list.
*/
struct alignas(64) SkipNode {
p<KeyType> key;
p<ValueType> value;
p<size_t> nodeLevel;
array<pptr<SkipNode>, MAX_LEVEL> forward;
/**
* Constructor for creating a new empty node.
*/
SkipNode() : nodeLevel(0) {}
/**
* Constructor for creating a new empty node on a given level.
*/
SkipNode(size_t level) : nodeLevel(level) {}
/**
* Constructor for creating a new node with an initial key and value.
*/
explicit SkipNode(const KeyType &key, const ValueType &value, size_t level) :
key(key), value(value), nodeLevel(level) {}
/**
* Print details this node to standard output.
*/
void printNode() {
std::cout << "{" << key << "," << value << "} --> ";
}
};
persistent_ptr<SkipNode> head;
persistent_ptr<SkipNode> tail;
p<int> level;
}; /// end struct SkipNode
/* -------------------------------------------------------------------------------------------- */
p<size_t> nodeCount;
Random rnd;
p<size_t> level; ///< the current number of levels (height)
p<size_t> nodeCount; ///< the current number of nodes
dbis::Random* rnd; ///< volatile pointer to a random number generator
pptr<SkipNode> head; ///< pointer to the entry node of the skip list
void createNode(int level, persistent_ptr<SkipNode> &node) {
/**
* Create a new node within the skip list.
*
* @param node[out] the pointer/reference to the newly allocated node
*/
void newSkipNode(pptr<SkipNode> &node) {
auto pop = pmem::obj::pool_by_vptr(this);
transaction::run(pop, [&] {node = make_persistent<SkipNode>();
node->forward = make_persistent<persistent_ptr<SkipNode>>();
for(int i=0; i<level+1;i++) {
node->forward[i] = make_persistent<SkipNode>();
}
//node->forward = new SkipNode*[level+1];
node->nodeLevel = level;
});
transaction::run(pop, [&] {
node = make_persistent<SkipNode>(level);
});
}
void createNode(int level, persistent_ptr<SkipNode> &node, KeyType key, ValueType value) {
/**
* Create a new node within the skip list.
*
* @param level the level of the node within the skip list
* @param key the initial key
* @param value the initial value
* @param node[out] the pointer/reference to the newly allocated node
*/
void newSkipNode(const KeyType &key, const ValueType &value, size_t level, pptr<SkipNode> &node) {
auto pop = pmem::obj::pool_by_vptr(this);
transaction::run(pop, [&] {
node = make_persistent<SkipNode>(key, value);
if(level > 0) {
//node->forward = new SkipNode*[level +1];
node->forward = make_persistent<persistent_ptr<SkipNode>>();
for(int i=0; i<level+1; i++) {
node->forward[i] = make_persistent<SkipNode>();
}
}
node->nodeLevel = level;
});
node = make_persistent<SkipNode>(key, value, level);
});
}
void createList(KeyType tailKey) {
createNode(0, tail);
tail->key = tailKey;
this->level = 0;
createNode(MAX_LEVEL, head);
for(int i=0; i < MAX_LEVEL; ++i) {
head->forward[i] = tail;
/**
* Initialize a skip list with a new head and initial empty node.
*/
void initList() {
newSkipNode(MIN_KEY, ValueType{}, MAX_LEVEL, head);
auto &headForward = head->forward;
for (auto i = 0u; i < MAX_LEVEL; ++i) {
headForward[i] = nullptr;
}
nodeCount = 0;
}
int getRandomLevel() {
int level = static_cast<int>(rnd.Uniform(MAX_LEVEL));
if (level == 0) {
level = 1;
}
return level;
size_t getRandomLevel() const {
const auto rlevel = rnd->Uniform(MAX_LEVEL);
return (rlevel ? rlevel : 1);
}
public:
/* -------------------------------------------------------------------------------------------- */