diff --git a/.gitignore b/.gitignore index 2070ab83..97da5a02 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,12 @@ /pqVsIt /ex-counter /unit-rcu +/unit-dboindex +/unit-tint +/unit-tart +/bench-tart +/bench-masstree +/tart_bench /unit-tarray /unit-tbox /unit-tgeneric @@ -47,7 +53,7 @@ /unit-sampling /unit-tflexarray /tpcc_bench -/ycsb_bench +/*ycsb_bench /micro_bench /wiki_bench /voter_bench @@ -60,3 +66,6 @@ **/.idea/workspace.xml **/.idea/tasks.xml **/cmake-build-* +/tart-bank +/art_micro_bench +/unit-artindex diff --git a/ART/Key.h b/ART/Key.h new file mode 100644 index 00000000..9c6fddd3 --- /dev/null +++ b/ART/Key.h @@ -0,0 +1,122 @@ +#pragma once +#ifndef ART_KEY_H +#define ART_KEY_H + +#include +#include +#include +#include + +using KeyLen = uint32_t; + +class Key { + static constexpr uint32_t stackLen = 128; + uint32_t len = 0; + + +public: + uint8_t stackKey[stackLen]; + uint8_t *data; + + Key() {} + + ~Key(); + + Key(const Key &key) = delete; + + Key(Key &&key); + + void set(const char bytes[], const std::size_t length); + + void operator=(const char key[]); + + bool operator==(const Key &k) const { + if (k.getKeyLen() != getKeyLen()) { + return false; + } + return std::memcmp(&k[0], data, getKeyLen()) == 0; + } + + uint8_t &operator[](std::size_t i); + + const uint8_t &operator[](std::size_t i) const; + + KeyLen getKeyLen() const; + + void setKeyLen(KeyLen len); + +}; + + +inline uint8_t &Key::operator[](std::size_t i) { + assert(i < len); + return data[i]; +} + +inline const uint8_t &Key::operator[](std::size_t i) const { + assert(i < len); + return data[i]; +} + +inline KeyLen Key::getKeyLen() const { return len; } + +inline Key::~Key() { + if (len > stackLen) { + delete[] data; + data = nullptr; + } +} + +inline Key::Key(Key &&key) { + len = key.len; + if (len > stackLen) { + data = key.data; + key.data = nullptr; + } else { + memcpy(stackKey, key.stackKey, key.len); + data = stackKey; + } +} + +inline void Key::set(const char bytes[], const std::size_t length) { + if (len > stackLen) { + delete[] data; + } + if (length <= stackLen) { + memcpy(stackKey, bytes, length); + data = stackKey; + } else { + data = new uint8_t[length]; + memcpy(data, bytes, length); + } + len = length; +} + +inline void Key::operator=(const char key[]) { + if (len > stackLen) { + delete[] data; + } + len = strlen(key); + if (len <= stackLen) { + memcpy(stackKey, key, len); + data = stackKey; + } else { + data = new uint8_t[len]; + memcpy(data, key, len); + } +} + +inline void Key::setKeyLen(KeyLen newLen) { + if (len == newLen) return; + if (len > stackLen) { + delete[] data; + } + len = newLen; + if (len > stackLen) { + data = new uint8_t[len]; + } else { + data = stackKey; + } +} + +#endif // ART_KEY_H diff --git a/ART/N.cpp b/ART/N.cpp new file mode 100644 index 00000000..cc1b8614 --- /dev/null +++ b/ART/N.cpp @@ -0,0 +1,429 @@ +#include +#include + +#include "N.h" +#include "N4.cpp" +#include "N16.cpp" +#include "N48.cpp" +#include "N256.cpp" +#include "Transaction.hh" + +namespace ART_OLC { + + void N::setType(NTypes type) { + typeVersionLockObsolete.fetch_add(convertTypeToVersion(type)); + } + + uint64_t N::convertTypeToVersion(NTypes type) { + return (static_cast(type) << 62); + } + + NTypes N::getType() const { + return static_cast(typeVersionLockObsolete.load(std::memory_order_relaxed) >> 62); + } + + void N::writeLockOrRestart(bool &needRestart) { + + uint64_t version; + version = readLockOrRestart(needRestart); + if (needRestart) return; + + upgradeToWriteLockOrRestart(version, needRestart); + if (needRestart) return; + } + + void N::upgradeToWriteLockOrRestart(uint64_t &version, bool &needRestart) { + if (typeVersionLockObsolete.compare_exchange_strong(version, version + 0b10)) { + version = version + 0b10; + } else { + needRestart = true; + } + } + + void N::writeUnlock() { + typeVersionLockObsolete.fetch_add(0b10); + } + + N *N::getAnyChild(const N *node) { + switch (node->getType()) { + case NTypes::N4: { + auto n = static_cast(node); + return n->getAnyChild(); + } + case NTypes::N16: { + auto n = static_cast(node); + return n->getAnyChild(); + } + case NTypes::N48: { + auto n = static_cast(node); + return n->getAnyChild(); + } + case NTypes::N256: { + auto n = static_cast(node); + return n->getAnyChild(); + } + } + assert(false); + __builtin_unreachable(); + } + + bool N::change(N *node, uint8_t key, N *val) { + switch (node->getType()) { + case NTypes::N4: { + auto n = static_cast(node); + return n->change(key, val); + } + case NTypes::N16: { + auto n = static_cast(node); + return n->change(key, val); + } + case NTypes::N48: { + auto n = static_cast(node); + return n->change(key, val); + } + case NTypes::N256: { + auto n = static_cast(node); + return n->change(key, val); + } + } + assert(false); + __builtin_unreachable(); + } + + template + N* N::insertGrow(curN *n, uint64_t v, N *parentNode, uint64_t parentVersion, uint8_t keyParent, uint8_t key, N *val, bool &needRestart) { + if (!n->isFull()) { + if (parentNode != nullptr) { + parentNode->readUnlockOrRestart(parentVersion, needRestart); + if (needRestart) return nullptr; + } + n->upgradeToWriteLockOrRestart(v, needRestart); + if (needRestart) return nullptr; + n->insert(key, val); + n->writeUnlock(); + return nullptr; + } + + parentNode->upgradeToWriteLockOrRestart(parentVersion, needRestart); + if (needRestart) return nullptr; + + n->upgradeToWriteLockOrRestart(v, needRestart); + if (needRestart) { + parentNode->writeUnlock(); + return nullptr; + } + + auto nBig = new biggerN(n->getPrefix(), n->getPrefixLength()); + n->copyTo(nBig); + nBig->insert(key, val); + + N::change(parentNode, keyParent, nBig); + + n->writeUnlockObsolete(); + // threadInfo.getEpoche().markNodeForDeletion(n, threadInfo); + parentNode->writeUnlock(); + return nBig; + } + + N* N::insertAndUnlock(N *node, uint64_t v, N *parentNode, uint64_t parentVersion, uint8_t keyParent, uint8_t key, N *val, bool &needRestart) { + switch (node->getType()) { + case NTypes::N4: { + auto n = static_cast(node); + return insertGrow(n, v, parentNode, parentVersion, keyParent, key, val, needRestart); + } + case NTypes::N16: { + auto n = static_cast(node); + return insertGrow(n, v, parentNode, parentVersion, keyParent, key, val, needRestart); + } + case NTypes::N48: { + auto n = static_cast(node); + return insertGrow(n, v, parentNode, parentVersion, keyParent, key, val, needRestart); + } + case NTypes::N256: { + auto n = static_cast(node); + return insertGrow(n, v, parentNode, parentVersion, keyParent, key, val, needRestart); + } + } + return nullptr; + } + + inline N *N::getChild(const uint8_t k, const N *node) { + switch (node->getType()) { + case NTypes::N4: { + auto n = static_cast(node); + return n->getChild(k); + } + case NTypes::N16: { + auto n = static_cast(node); + return n->getChild(k); + } + case NTypes::N48: { + auto n = static_cast(node); + return n->getChild(k); + } + case NTypes::N256: { + auto n = static_cast(node); + return n->getChild(k); + } + } + assert(false); + __builtin_unreachable(); + } + + void N::deleteChildren(N *node) { + if (N::isLeaf(node)) { + return; + } + switch (node->getType()) { + case NTypes::N4: { + auto n = static_cast(node); + n->deleteChildren(); + return; + } + case NTypes::N16: { + auto n = static_cast(node); + n->deleteChildren(); + return; + } + case NTypes::N48: { + auto n = static_cast(node); + n->deleteChildren(); + return; + } + case NTypes::N256: { + auto n = static_cast(node); + n->deleteChildren(); + return; + } + } + assert(false); + __builtin_unreachable(); + } + + template + N* N::removeAndShrink(curN *n, uint64_t v, N *parentNode, uint64_t parentVersion, uint8_t keyParent, uint8_t key, bool &needRestart) { + if (!n->isUnderfull() || parentNode == nullptr) { + if (parentNode != nullptr) { + parentNode->readUnlockOrRestart(parentVersion, needRestart); + if (needRestart) return nullptr; + } + n->upgradeToWriteLockOrRestart(v, needRestart); + if (needRestart) return nullptr; + + n->remove(key); + n->writeUnlock(); + return nullptr; + } + parentNode->upgradeToWriteLockOrRestart(parentVersion, needRestart); + if (needRestart) return nullptr; + + n->upgradeToWriteLockOrRestart(v, needRestart); + if (needRestart) { + parentNode->writeUnlock(); + return nullptr; + } + + auto nSmall = new smallerN(n->getPrefix(), n->getPrefixLength()); + + n->copyTo(nSmall); + nSmall->remove(key); + N::change(parentNode, keyParent, nSmall); + + n->writeUnlockObsolete(); + // Transaction::rcu_delete(n); + // threadInfo.getEpoche().markNodeForDeletion(n, threadInfo); + parentNode->writeUnlock(); + return n; + } + + N* N::removeAndUnlock(N *node, uint64_t v, uint8_t key, N *parentNode, uint64_t parentVersion, uint8_t keyParent, bool &needRestart) { + switch (node->getType()) { + case NTypes::N4: { + auto n = static_cast(node); + return removeAndShrink(n, v, parentNode, parentVersion, keyParent, key, needRestart); + } + case NTypes::N16: { + auto n = static_cast(node); + return removeAndShrink(n, v, parentNode, parentVersion, keyParent, key, needRestart); + } + case NTypes::N48: { + auto n = static_cast(node); + return removeAndShrink(n, v, parentNode, parentVersion, keyParent, key, needRestart); + } + case NTypes::N256: { + auto n = static_cast(node); + return removeAndShrink(n, v, parentNode, parentVersion, keyParent, key, needRestart); + } + } + return nullptr; + } + + bool N::isLocked(uint64_t version) const { + return ((version & 0b10) == 0b10); + } + + uint64_t N::readLockOrRestart(bool &needRestart) const { + uint64_t version; + version = typeVersionLockObsolete.load(); +/* do { + version = typeVersionLockObsolete.load(); + } while (isLocked(version));*/ + if (isLocked(version) || isObsolete(version)) { + needRestart = true; + } + return version; + //uint64_t version; + //while (isLocked(version)) _mm_pause(); + //return version; + } + + bool N::isObsolete(uint64_t version) { + return (version & 1) == 1; + } + + void N::checkOrRestart(uint64_t startRead, bool &needRestart) const { + readUnlockOrRestart(startRead, needRestart); + } + + void N::readUnlockOrRestart(uint64_t startRead, bool &needRestart) const { + needRestart = (startRead != typeVersionLockObsolete.load()); + } + + uint32_t N::getPrefixLength() const { + return prefixCount; + } + + bool N::hasPrefix() const { + return prefixCount > 0; + } + + uint32_t N::getCount() const { + return count; + } + + const uint8_t *N::getPrefix() const { + return prefix; + } + + void N::setPrefix(const uint8_t *prefix, uint32_t length) { + if (length > 0) { + memcpy(this->prefix, prefix, std::min(length, maxStoredPrefixLength)); + prefixCount = length; + } else { + prefixCount = 0; + } + } + + void N::addPrefixBefore(N *node, uint8_t key) { + uint32_t prefixCopyCount = std::min(maxStoredPrefixLength, node->getPrefixLength() + 1); + memmove(this->prefix + prefixCopyCount, this->prefix, + std::min(this->getPrefixLength(), maxStoredPrefixLength - prefixCopyCount)); + memcpy(this->prefix, node->prefix, std::min(prefixCopyCount, node->getPrefixLength())); + if (node->getPrefixLength() < maxStoredPrefixLength) { + this->prefix[prefixCopyCount - 1] = key; + } + this->prefixCount += node->getPrefixLength() + 1; + } + + + bool N::isLeaf(const N *n) { + return (reinterpret_cast(n) & (static_cast(1) << 63)) == (static_cast(1) << 63); + } + + N *N::setLeaf(TID tid) { + return reinterpret_cast(tid | (static_cast(1) << 63)); + } + + TID N::getLeaf(const N *n) { + return (reinterpret_cast(n) & ((static_cast(1) << 63) - 1)); + } + + std::tuple N::getSecondChild(N *node, const uint8_t key) { + switch (node->getType()) { + case NTypes::N4: { + auto n = static_cast(node); + return n->getSecondChild(key); + } + default: { + assert(false); + __builtin_unreachable(); + } + } + } + + void N::deleteNode(N *node) { + if (N::isLeaf(node)) { + // if (N::freeLeaf) { + // N::freeLeaf(N::getLeaf(node)); + // } + return; + } + switch (node->getType()) { + case NTypes::N4: { + auto n = static_cast(node); + delete n; + return; + } + case NTypes::N16: { + auto n = static_cast(node); + delete n; + return; + } + case NTypes::N48: { + auto n = static_cast(node); + delete n; + return; + } + case NTypes::N256: { + auto n = static_cast(node); + delete n; + return; + } + } + delete node; + } + + + TID N::getAnyChildTid(const N *n, bool &needRestart) { + const N *nextNode = n; + + while (true) { + const N *node = nextNode; + auto v = node->readLockOrRestart(needRestart); + if (needRestart) return 0; + + nextNode = getAnyChild(node); + node->readUnlockOrRestart(v, needRestart); + if (needRestart) return 0; + + assert(nextNode != nullptr); + if (isLeaf(nextNode)) { + return getLeaf(nextNode); + } + } + } + + uint64_t N::getChildren(const N *node, uint8_t start, uint8_t end, std::tuple children[], + uint32_t &childrenCount) { + switch (node->getType()) { + case NTypes::N4: { + auto n = static_cast(node); + return n->getChildren(start, end, children, childrenCount); + } + case NTypes::N16: { + auto n = static_cast(node); + return n->getChildren(start, end, children, childrenCount); + } + case NTypes::N48: { + auto n = static_cast(node); + return n->getChildren(start, end, children, childrenCount); + } + case NTypes::N256: { + auto n = static_cast(node); + return n->getChildren(start, end, children, childrenCount); + } + } + assert(false); + __builtin_unreachable(); + } +} diff --git a/ART/N.h b/ART/N.h new file mode 100644 index 00000000..ec580bfc --- /dev/null +++ b/ART/N.h @@ -0,0 +1,301 @@ +// +// Created by florian on 05.08.15. +// + +#pragma once +#ifndef ART_OPTIMISTIC_LOCK_COUPLING_N_H +#define ART_OPTIMISTIC_LOCK_COUPLING_N_H +//#define ART_NOREADLOCK +//#define ART_NOWRITELOCK +#include +#include +#include +#include +#include "Key.h" +#include "Sto.hh" + +namespace ART { } + +using TID = uint64_t; + +using namespace ART; +namespace ART_OLC { +/* + * SynchronizedTree + * LockCouplingTree + * LockCheckFreeReadTree + * UnsynchronizedTree + */ + + enum class NTypes : uint8_t { + N4 = 0, + N16 = 1, + N48 = 2, + N256 = 3 + }; + + static constexpr uint32_t maxStoredPrefixLength = 11; + + using Prefix = uint8_t[maxStoredPrefixLength]; + + class N { + protected: + N(NTypes type, const uint8_t *prefix, uint32_t prefixLength) { + setType(type); + setPrefix(prefix, prefixLength); + } + + N(const N &) = delete; + + N(N &&) = delete; + + //2b type 60b version 1b lock 1b obsolete + std::atomic typeVersionLockObsolete{0b100}; + // version 1, unlocked, not obsolete + uint32_t prefixCount = 0; + + uint8_t count = 0; + Prefix prefix; + + + void setType(NTypes type); + + static uint64_t convertTypeToVersion(NTypes type); + + public: + NTypes getType() const; + + // static void freeLeaf(TID tid); + + bool valid = true; + TVersion vers; + + uint32_t getCount() const; + + bool isLocked(uint64_t version) const; + + void writeLockOrRestart(bool &needRestart); + + void upgradeToWriteLockOrRestart(uint64_t &version, bool &needRestart); + + void writeUnlock(); + + uint64_t readLockOrRestart(bool &needRestart) const; + + /** + * returns true if node hasn't been changed in between + */ + void checkOrRestart(uint64_t startRead, bool &needRestart) const; + void readUnlockOrRestart(uint64_t startRead, bool &needRestart) const; + + static bool isObsolete(uint64_t version); + + /** + * can only be called when node is locked + */ + void writeUnlockObsolete() { + typeVersionLockObsolete.fetch_add(0b11); + } + + static N *getChild(const uint8_t k, const N *node); + + static N* insertAndUnlock(N *node, uint64_t v, N *parentNode, uint64_t parentVersion, uint8_t keyParent, uint8_t key, N *val, bool &needRestart); + + static bool change(N *node, uint8_t key, N *val); + + static N* removeAndUnlock(N *node, uint64_t v, uint8_t key, N *parentNode, uint64_t parentVersion, uint8_t keyParent, bool &needRestart); + + bool hasPrefix() const; + + const uint8_t *getPrefix() const; + + void setPrefix(const uint8_t *prefix, uint32_t length); + + void addPrefixBefore(N *node, uint8_t key); + + uint32_t getPrefixLength() const; + + static TID getLeaf(const N *n); + + static bool isLeaf(const N *n); + + static N *setLeaf(TID tid); + + static N *getAnyChild(const N *n); + + static TID getAnyChildTid(const N *n, bool &needRestart); + + static void deleteChildren(N *node); + + static void deleteNode(N *node); + + static std::tuple getSecondChild(N *node, const uint8_t k); + + template + static N* insertGrow(curN *n, uint64_t v, N *parentNode, uint64_t parentVersion, uint8_t keyParent, uint8_t key, N *val, bool &needRestart); + + template + static N* removeAndShrink(curN *n, uint64_t v, N *parentNode, uint64_t parentVersion, uint8_t keyParent, uint8_t key, bool &needRestart); + + static uint64_t getChildren(const N *node, uint8_t start, uint8_t end, std::tuple children[], + uint32_t &childrenCount); + }; + + class N4 : public N { + public: + uint8_t keys[4]; + N *children[4] = {nullptr, nullptr, nullptr, nullptr}; + + public: + N4(const uint8_t *prefix, uint32_t prefixLength) : N(NTypes::N4, prefix, + prefixLength) { } + + void insert(uint8_t key, N *n); + + template + void copyTo(NODE *n) const; + + bool change(uint8_t key, N *val); + + N *getChild(const uint8_t k) const; + + void remove(uint8_t k); + + N *getAnyChild() const; + + bool isFull() const; + + bool isUnderfull() const; + + std::tuple getSecondChild(const uint8_t key) const; + + void deleteChildren(); + + uint64_t getChildren(uint8_t start, uint8_t end, std::tuple *&children, + uint32_t &childrenCount) const; + }; + + class N16 : public N { + public: + uint8_t keys[16]; + N *children[16]; + + static uint8_t flipSign(uint8_t keyByte) { + // Flip the sign bit, enables signed SSE comparison of unsigned values, used by Node16 + return keyByte ^ 128; + } + + static inline unsigned ctz(uint16_t x) { + // Count trailing zeros, only defined for x>0 +#ifdef __GNUC__ + return __builtin_ctz(x); +#else + // Adapted from Hacker's Delight + unsigned n=1; + if ((x&0xFF)==0) {n+=8; x=x>>8;} + if ((x&0x0F)==0) {n+=4; x=x>>4;} + if ((x&0x03)==0) {n+=2; x=x>>2;} + return n-(x&1); +#endif + } + + N *const *getChildPos(const uint8_t k) const; + + public: + N16(const uint8_t *prefix, uint32_t prefixLength) : N(NTypes::N16, prefix, + prefixLength) { + memset(keys, 0, sizeof(keys)); + memset(children, 0, sizeof(children)); + } + + void insert(uint8_t key, N *n); + + template + void copyTo(NODE *n) const; + + bool change(uint8_t key, N *val); + + N *getChild(const uint8_t k) const; + + void remove(uint8_t k); + + N *getAnyChild() const; + + bool isFull() const; + + bool isUnderfull() const; + + void deleteChildren(); + + uint64_t getChildren(uint8_t start, uint8_t end, std::tuple *&children, + uint32_t &childrenCount) const; + }; + + class N48 : public N { + uint8_t childIndex[256]; + N *children[48]; + public: + static const uint8_t emptyMarker = 48; + + N48(const uint8_t *prefix, uint32_t prefixLength) : N(NTypes::N48, prefix, + prefixLength) { + memset(childIndex, emptyMarker, sizeof(childIndex)); + memset(children, 0, sizeof(children)); + } + + void insert(uint8_t key, N *n); + + template + void copyTo(NODE *n) const; + + bool change(uint8_t key, N *val); + + N *getChild(const uint8_t k) const; + + void remove(uint8_t k); + + N *getAnyChild() const; + + bool isFull() const; + + bool isUnderfull() const; + + void deleteChildren(); + + uint64_t getChildren(uint8_t start, uint8_t end, std::tuple *&children, + uint32_t &childrenCount) const; + }; + + class N256 : public N { + N *children[256]; + + public: + N256(const uint8_t *prefix, uint32_t prefixLength) : N(NTypes::N256, prefix, + prefixLength) { + memset(children, '\0', sizeof(children)); + } + + void insert(uint8_t key, N *val); + + template + void copyTo(NODE *n) const; + + bool change(uint8_t key, N *n); + + N *getChild(const uint8_t k) const; + + void remove(uint8_t k); + + N *getAnyChild() const; + + bool isFull() const; + + bool isUnderfull() const; + + void deleteChildren(); + + uint64_t getChildren(uint8_t start, uint8_t end, std::tuple *&children, + uint32_t &childrenCount) const; + }; +} +#endif //ART_OPTIMISTIC_LOCK_COUPLING_N_H diff --git a/ART/N16.cpp b/ART/N16.cpp new file mode 100644 index 00000000..be6f97e1 --- /dev/null +++ b/ART/N16.cpp @@ -0,0 +1,112 @@ +#include +#include +#include "N.h" +#include // x86 SSE intrinsics + +namespace ART_OLC { + + bool N16::isFull() const { + return count == 16; + } + + bool N16::isUnderfull() const { + return count == 3; + } + + void N16::insert(uint8_t key, N *n) { + uint8_t keyByteFlipped = flipSign(key); + __m128i cmp = _mm_cmplt_epi8(_mm_set1_epi8(keyByteFlipped), _mm_loadu_si128(reinterpret_cast<__m128i *>(keys))); + uint16_t bitfield = _mm_movemask_epi8(cmp) & (0xFFFF >> (16 - count)); + unsigned pos = bitfield ? ctz(bitfield) : count; + memmove(keys + pos + 1, keys + pos, count - pos); + memmove(children + pos + 1, children + pos, (count - pos) * sizeof(uintptr_t)); + keys[pos] = keyByteFlipped; + children[pos] = n; + count++; + } + + template + void N16::copyTo(NODE *n) const { + for (unsigned i = 0; i < count; i++) { + n->insert(flipSign(keys[i]), children[i]); + } + } + + bool N16::change(uint8_t key, N *val) { + N **childPos = const_cast(getChildPos(key)); + assert(childPos != nullptr); + *childPos = val; + return true; + } + + N *const *N16::getChildPos(const uint8_t k) const { + __m128i cmp = _mm_cmpeq_epi8(_mm_set1_epi8(flipSign(k)), + _mm_loadu_si128(reinterpret_cast(keys))); + unsigned bitfield = _mm_movemask_epi8(cmp) & ((1 << count) - 1); + if (bitfield) { + return &children[ctz(bitfield)]; + } else { + return nullptr; + } + } + + N *N16::getChild(const uint8_t k) const { + N *const *childPos = getChildPos(k); + if (childPos == nullptr) { + return nullptr; + } else { + return *childPos; + } + } + + void N16::remove(uint8_t k) { + N *const *leafPlace = getChildPos(k); + assert(leafPlace != nullptr); + std::size_t pos = leafPlace - children; + memmove(keys + pos, keys + pos + 1, count - pos - 1); + memmove(children + pos, children + pos + 1, (count - pos - 1) * sizeof(N *)); + count--; + assert(getChild(k) == nullptr); + } + + N *N16::getAnyChild() const { + for (int i = 0; i < count; ++i) { + if (N::isLeaf(children[i])) { + return children[i]; + } + } + return children[0]; + } + + void N16::deleteChildren() { + for (std::size_t i = 0; i < count; ++i) { + N::deleteChildren(children[i]); + N::deleteNode(children[i]); + } + } + + uint64_t N16::getChildren(uint8_t start, uint8_t end, std::tuple *&children, + uint32_t &childrenCount) const { + restart: + bool needRestart = false; + uint64_t v; + v = readLockOrRestart(needRestart); + if (needRestart) goto restart; + childrenCount = 0; + auto startPos = getChildPos(start); + auto endPos = getChildPos(end); + if (startPos == nullptr) { + startPos = this->children; + } + if (endPos == nullptr) { + endPos = this->children + (count - 1); + } + for (auto p = startPos; p <= endPos; ++p) { + children[childrenCount] = std::make_tuple(flipSign(keys[p - this->children]), *p); + childrenCount++; + } + readUnlockOrRestart(v, needRestart); + if (needRestart) goto restart; + return v; + } +} \ No newline at end of file diff --git a/ART/N256.cpp b/ART/N256.cpp new file mode 100644 index 00000000..c667a794 --- /dev/null +++ b/ART/N256.cpp @@ -0,0 +1,84 @@ +#include +#include +#include "N.h" + +namespace ART_OLC { + + bool N256::isFull() const { + return false; + } + + bool N256::isUnderfull() const { + return count == 37; + } + + void N256::deleteChildren() { + for (uint64_t i = 0; i < 256; ++i) { + if (children[i] != nullptr) { + N::deleteChildren(children[i]); + N::deleteNode(children[i]); + } + } + } + + void N256::insert(uint8_t key, N *val) { + children[key] = val; + count++; + } + + template + void N256::copyTo(NODE *n) const { + for (int i = 0; i < 256; ++i) { + if (children[i] != nullptr) { + n->insert(i, children[i]); + } + } + } + + bool N256::change(uint8_t key, N *n) { + children[key] = n; + return true; + } + + N *N256::getChild(const uint8_t k) const { + return children[k]; + } + + void N256::remove(uint8_t k) { + children[k] = nullptr; + count--; + } + + N *N256::getAnyChild() const { + N *anyChild = nullptr; + for (uint64_t i = 0; i < 256; ++i) { + if (children[i] != nullptr) { + if (N::isLeaf(children[i])) { + return children[i]; + } else { + anyChild = children[i]; + } + } + } + return anyChild; + } + + uint64_t N256::getChildren(uint8_t start, uint8_t end, std::tuple *&children, + uint32_t &childrenCount) const { + restart: + bool needRestart = false; + uint64_t v; + v = readLockOrRestart(needRestart); + if (needRestart) goto restart; + childrenCount = 0; + for (unsigned i = start; i <= end; i++) { + if (this->children[i] != nullptr) { + children[childrenCount] = std::make_tuple(i, this->children[i]); + childrenCount++; + } + } + readUnlockOrRestart(v, needRestart); + if (needRestart) goto restart; + return v; + } +} \ No newline at end of file diff --git a/ART/N4.cpp b/ART/N4.cpp new file mode 100644 index 00000000..0a3d4ea1 --- /dev/null +++ b/ART/N4.cpp @@ -0,0 +1,109 @@ +#include +#include +#include "N.h" + +namespace ART_OLC { + + void N4::deleteChildren() { + for (uint32_t i = 0; i < count; ++i) { + N::deleteChildren(children[i]); + N::deleteNode(children[i]); + } + } + + bool N4::isFull() const { + return count == 4; + } + + bool N4::isUnderfull() const { + return false; + } + + void N4::insert(uint8_t key, N *n) { + unsigned pos; + for (pos = 0; (pos < count) && (keys[pos] < key); pos++); + memmove(keys + pos + 1, keys + pos, count - pos); + memmove(children + pos + 1, children + pos, (count - pos) * sizeof(N*)); + keys[pos] = key; + children[pos] = n; + count++; + } + + template + void N4::copyTo(NODE *n) const { + for (uint32_t i = 0; i < count; ++i) { + n->insert(keys[i], children[i]); + } + } + + bool N4::change(uint8_t key, N *val) { + for (uint32_t i = 0; i < count; ++i) { + if (keys[i] == key) { + children[i] = val; + return true; + } + } + assert(false); + __builtin_unreachable(); + } + + N *N4::getChild(const uint8_t k) const { + for (uint32_t i = 0; i < count; ++i) { + if (keys[i] == k) { + return children[i]; + } + } + return nullptr; + } + + void N4::remove(uint8_t k) { + for (uint32_t i = 0; i < count; ++i) { + if (keys[i] == k) { + memmove(keys + i, keys + i + 1, count - i - 1); + memmove(children + i, children + i + 1, (count - i - 1) * sizeof(N *)); + count--; + return; + } + } + } + + N *N4::getAnyChild() const { + N *anyChild = nullptr; + for (uint32_t i = 0; i < count; ++i) { + if (N::isLeaf(children[i])) { + return children[i]; + } else { + anyChild = children[i]; + } + } + return anyChild; + } + + std::tuple N4::getSecondChild(const uint8_t key) const { + for (uint32_t i = 0; i < count; ++i) { + if (keys[i] != key) { + return std::make_tuple(children[i], keys[i]); + } + } + return std::make_tuple(nullptr, 0); + } + + uint64_t N4::getChildren(uint8_t start, uint8_t end, std::tuple *&children, + uint32_t &childrenCount) const { + restart: + bool needRestart = false; + uint64_t v; + v = readLockOrRestart(needRestart); + if (needRestart) goto restart; + childrenCount = 0; + for (uint32_t i = 0; i < count; ++i) { + if (this->keys[i] >= start && this->keys[i] <= end) { + children[childrenCount] = std::make_tuple(this->keys[i], this->children[i]); + childrenCount++; + } + } + readUnlockOrRestart(v, needRestart); + if (needRestart) goto restart; + return v; + } +} \ No newline at end of file diff --git a/ART/N48.cpp b/ART/N48.cpp new file mode 100644 index 00000000..b9d1bfb7 --- /dev/null +++ b/ART/N48.cpp @@ -0,0 +1,96 @@ +#include +#include +#include "N.h" + +namespace ART_OLC { + + bool N48::isFull() const { + return count == 48; + } + + bool N48::isUnderfull() const { + return count == 12; + } + + void N48::insert(uint8_t key, N *n) { + unsigned pos = count; + if (children[pos]) { + for (pos = 0; children[pos] != nullptr; pos++); + } + children[pos] = n; + childIndex[key] = (uint8_t) pos; + count++; + } + + template + void N48::copyTo(NODE *n) const { + for (unsigned i = 0; i < 256; i++) { + if (childIndex[i] != emptyMarker) { + n->insert(i, children[childIndex[i]]); + } + } + } + + bool N48::change(uint8_t key, N *val) { + children[childIndex[key]] = val; + return true; + } + + N *N48::getChild(const uint8_t k) const { + if (childIndex[k] == emptyMarker) { + return nullptr; + } else { + return children[childIndex[k]]; + } + } + + void N48::remove(uint8_t k) { + assert(childIndex[k] != emptyMarker); + children[childIndex[k]] = nullptr; + childIndex[k] = emptyMarker; + count--; + assert(getChild(k) == nullptr); + } + + N *N48::getAnyChild() const { + N *anyChild = nullptr; + for (unsigned i = 0; i < 256; i++) { + if (childIndex[i] != emptyMarker) { + if (N::isLeaf(children[childIndex[i]])) { + return children[childIndex[i]]; + } else { + anyChild = children[childIndex[i]]; + }; + } + } + return anyChild; + } + + void N48::deleteChildren() { + for (unsigned i = 0; i < 256; i++) { + if (childIndex[i] != emptyMarker) { + N::deleteChildren(children[childIndex[i]]); + N::deleteNode(children[childIndex[i]]); + } + } + } + + uint64_t N48::getChildren(uint8_t start, uint8_t end, std::tuple *&children, + uint32_t &childrenCount) const { + restart: + bool needRestart = false; + uint64_t v; + v = readLockOrRestart(needRestart); + if (needRestart) goto restart; + childrenCount = 0; + for (unsigned i = start; i <= end; i++) { + if (this->childIndex[i] != emptyMarker) { + children[childrenCount] = std::make_tuple(i, this->children[this->childIndex[i]]); + childrenCount++; + } + } + readUnlockOrRestart(v, needRestart); + if (needRestart) goto restart; + return v; + } +} \ No newline at end of file diff --git a/ART/Tree.cpp b/ART/Tree.cpp new file mode 100644 index 00000000..26cbeeb7 --- /dev/null +++ b/ART/Tree.cpp @@ -0,0 +1,727 @@ +#include +#include +#include "Tree.h" +#include "N.cpp" +#include "Key.h" +#include "Sto.hh" +#include "Transaction.hh" + +namespace ART_OLC { + + Tree::Tree() : root(new N256(nullptr, 0)) { + } + + void Tree::setLoadKey(LoadKeyFunction f) { + loadKey = f; + } + + Tree::Tree(LoadKeyFunction loadKey) : root(new N256(nullptr, 0)), loadKey(loadKey) { + } + + Tree::~Tree() { + N::deleteChildren(root); + N::deleteNode(root); + } + + // ThreadInfo Tree::getThreadInfo() { + // return ThreadInfo(this->epoche); + // } + + std::pair Tree::lookup(const Key &k) const { + // EpocheGuardReadonly epocheGuard(threadEpocheInfo); + restart: + bool needRestart = false; + + N *node; + N *parentNode = nullptr; + uint64_t v; + uint32_t level = 0; + bool optimisticPrefixMatch = false; + + node = root; + v = node->readLockOrRestart(needRestart); + if (needRestart) goto restart; + while (true) { + switch (checkPrefix(node, k, level)) { // increases level + case CheckPrefixResult::NoMatch: + node->readUnlockOrRestart(v, needRestart); + if (needRestart) goto restart; + return {0, node}; + case CheckPrefixResult::OptimisticMatch: + optimisticPrefixMatch = true; + // fallthrough + case CheckPrefixResult::Match: + if (k.getKeyLen() <= level) { + return {0, node}; + } + parentNode = node; + node = N::getChild(k[level], parentNode); + parentNode->checkOrRestart(v,needRestart); + if (needRestart) goto restart; + + if (node == nullptr) { + return {0, parentNode}; + } + if (N::isLeaf(node)) { + parentNode->readUnlockOrRestart(v, needRestart); + if (needRestart) goto restart; + + TID tid = N::getLeaf(node); + if (level < k.getKeyLen() - 1 || optimisticPrefixMatch) { + return {checkKey(tid, k), parentNode}; + } + return {tid, parentNode}; + } + level++; + } + uint64_t nv = node->readLockOrRestart(needRestart); + if (needRestart) goto restart; + + parentNode->readUnlockOrRestart(v, needRestart); + if (needRestart) goto restart; + v = nv; + } + } + + bool Tree::lookupRange(const Key &start, const Key &end, Key &continueKey, TID result[], + std::size_t resultSize, std::size_t &resultsFound, std::function observe_node) const { + for (uint32_t i = 0; i < std::min(start.getKeyLen(), end.getKeyLen()); ++i) { + if (start[i] > end[i]) { + resultsFound = 0; + return false; + } else if (start[i] < end[i]) { + break; + } + } + // EpocheGuard epocheGuard(threadEpocheInfo); + TID toContinue = 0; + std::function copy = [&result, &resultSize, &resultsFound, &toContinue, ©](const N *node) { + if (N::isLeaf(node)) { + if (resultsFound == resultSize) { + toContinue = N::getLeaf(node); + return; + } + result[resultsFound] = N::getLeaf(node); + resultsFound++; + } else { + std::tuple children[256]; + uint32_t childrenCount = 0; + N::getChildren(node, 0u, 255u, children, childrenCount); + for (uint32_t i = 0; i < childrenCount; ++i) { + const N *n = std::get<1>(children[i]); + copy(n); + if (toContinue != 0) { + break; + } + } + } + }; + std::function findStart = [©, &start, &findStart, &toContinue, this]( + N *node, uint8_t nodeK, uint32_t level, const N *parentNode, uint64_t vp) { + if (N::isLeaf(node)) { + copy(node); + return; + } + uint64_t v; + PCCompareResults prefixResult; + + { + readAgain: + bool needRestart = false; + v = node->readLockOrRestart(needRestart); + if (needRestart) goto readAgain; + + prefixResult = checkPrefixCompare(node, start, 0, level, loadKey, needRestart); + if (needRestart) goto readAgain; + + parentNode->readUnlockOrRestart(vp, needRestart); + if (needRestart) { + readParentAgain: + vp = parentNode->readLockOrRestart(needRestart); + if (needRestart) goto readParentAgain; + + node = N::getChild(nodeK, parentNode); + + parentNode->readUnlockOrRestart(vp, needRestart); + if (needRestart) goto readParentAgain; + + if (node == nullptr) { + return; + } + if (N::isLeaf(node)) { + copy(node); + return; + } + goto readAgain; + } + node->readUnlockOrRestart(v, needRestart); + if (needRestart) goto readAgain; + } + + switch (prefixResult) { + case PCCompareResults::Bigger: + copy(node); + break; + case PCCompareResults::Equal: { + uint8_t startLevel = (start.getKeyLen() > level) ? start[level] : 0; + std::tuple children[256]; + uint32_t childrenCount = 0; + v = N::getChildren(node, startLevel, 255, children, childrenCount); + for (uint32_t i = 0; i < childrenCount; ++i) { + const uint8_t k = std::get<0>(children[i]); + N *n = std::get<1>(children[i]); + if (k == startLevel) { + findStart(n, k, level + 1, node, v); + } else if (k > startLevel) { + copy(n); + } + if (toContinue != 0) { + break; + } + } + break; + } + case PCCompareResults::Smaller: + break; + } + }; + std::function findEnd = [©, &end, &toContinue, &findEnd, this]( + N *node, uint8_t nodeK, uint32_t level, const N *parentNode, uint64_t vp) { + if (N::isLeaf(node)) { + return; + } + uint64_t v; + PCCompareResults prefixResult; + { + readAgain: + bool needRestart = false; + v = node->readLockOrRestart(needRestart); + if (needRestart) goto readAgain; + + prefixResult = checkPrefixCompare(node, end, 255, level, loadKey, needRestart); + if (needRestart) goto readAgain; + + parentNode->readUnlockOrRestart(vp, needRestart); + if (needRestart) { + readParentAgain: + vp = parentNode->readLockOrRestart(needRestart); + if (needRestart) goto readParentAgain; + + node = N::getChild(nodeK, parentNode); + + parentNode->readUnlockOrRestart(vp, needRestart); + if (needRestart) goto readParentAgain; + + if (node == nullptr) { + return; + } + if (N::isLeaf(node)) { + return; + } + goto readAgain; + } + node->readUnlockOrRestart(v, needRestart); + if (needRestart) goto readAgain; + } + switch (prefixResult) { + case PCCompareResults::Smaller: + copy(node); + break; + case PCCompareResults::Equal: { + uint8_t endLevel = (end.getKeyLen() > level) ? end[level] : 255; + std::tuple children[256]; + uint32_t childrenCount = 0; + v = N::getChildren(node, 0, endLevel, children, childrenCount); + for (uint32_t i = 0; i < childrenCount; ++i) { + const uint8_t k = std::get<0>(children[i]); + N *n = std::get<1>(children[i]); + if (k == endLevel) { + findEnd(n, k, level + 1, node, v); + } else if (k < endLevel) { + copy(n); + } + if (toContinue != 0) { + break; + } + } + break; + } + case PCCompareResults::Bigger: + break; + } + }; + + restart: + bool needRestart = false; + + resultsFound = 0; + + uint32_t level = 0; + N *node = nullptr; + N *nextNode = root; + N *parentNode; + uint64_t v = 0; + uint64_t vp; + + while (true) { + parentNode = node; + vp = v; + node = nextNode; + PCEqualsResults prefixResult; + v = node->readLockOrRestart(needRestart); + if (needRestart) goto restart; + prefixResult = checkPrefixEquals(node, level, start, end, loadKey, needRestart); + if (needRestart) goto restart; + if (parentNode != nullptr) { + parentNode->readUnlockOrRestart(vp, needRestart); + if (needRestart) goto restart; + } + observe_node(node); + node->readUnlockOrRestart(v, needRestart); + if (needRestart) goto restart; + + switch (prefixResult) { + case PCEqualsResults::NoMatch: { + return false; + } + case PCEqualsResults::Contained: { + copy(node); + break; + } + case PCEqualsResults::BothMatch: { + uint8_t startLevel = (start.getKeyLen() > level) ? start[level] : 0; + uint8_t endLevel = (end.getKeyLen() > level) ? end[level] : 255; + if (startLevel != endLevel) { + std::tuple children[256]; + uint32_t childrenCount = 0; + v = N::getChildren(node, startLevel, endLevel, children, childrenCount); + for (uint32_t i = 0; i < childrenCount; ++i) { + const uint8_t k = std::get<0>(children[i]); + N *n = std::get<1>(children[i]); + if (k == startLevel) { + findStart(n, k, level + 1, node, v); + } else if (k > startLevel && k < endLevel) { + copy(n); + } else if (k == endLevel) { + findEnd(n, k, level + 1, node, v); + } + if (toContinue) { + break; + } + } + } else { + nextNode = N::getChild(startLevel, node); + node->readUnlockOrRestart(v, needRestart); + if (needRestart) goto restart; + level++; + continue; + } + break; + } + } + break; + } + if (toContinue != 0) { + loadKey(toContinue, continueKey); + return true; + } else { + return false; + } + } + + + TID Tree::checkKey(const TID tid, const Key &k) const { + Key kt; + this->loadKey(tid, kt); + if (k == kt) { + return tid; + } + return 0; + } + + // not thread safe + #include + static void printPrefix(std::tuple node) { + printf("%c", std::get<0>(node)); + auto n = std::get<1>(node); + if (n->hasPrefix()) { + const char* prefix = (const char*) n->getPrefix(); + int prefixLen = n->getPrefixLength(); + for (int i = 0; i < prefixLen; i++) { + printf("%c", prefix[i]); + } + } + } + void Tree::print() const { + std::vector> parents = {std::make_tuple(0, root)}; + std::vector> childrenVec; + + uint32_t level = 0; + + while (parents.size()) { + // get all kids + for (std::tuple parent : parents) { + N* par = std::get<1>(parent); + printf("%d: ", level); + printPrefix(parent); + printf("\n"); + std::tuple children[256]; + uint32_t childrenCount = 0; + N::getChildren(par, 0u, 255u, children, childrenCount); + for (int i = 0; i < level; ++i) { + printf("\t"); + } + for (uint32_t i = 0; i < childrenCount; ++i) { + N* child = std::get<1>(children[i]); + if (child) { + if (N::isLeaf(child)) { + Key k; + loadKey(N::getLeaf(child), k); + printf("%s ", k.data); + } else { + printf("\""); + printPrefix(children[i]); + printf("\" "); + childrenVec.push_back(children[i]); + } + } + } + printf("\n"); + } + + // make kids into parents + parents = childrenVec; + childrenVec.clear(); + ++level; + } + } + + Tree::ins_return_type Tree::insert(const Key &k, std::function make_tid) { + // EpocheGuard epocheGuard(epocheInfo); + restart: + bool needRestart = false; + + N *node = nullptr; + N *nextNode = root; + N *parentNode = nullptr; + uint8_t parentKey, nodeKey = 0; + uint64_t parentVersion = 0; + uint32_t level = 0; + TID tid = 0; + + while (true) { + parentNode = node; + parentKey = nodeKey; + node = nextNode; + auto v = node->readLockOrRestart(needRestart); + if (needRestart) goto restart; + + uint32_t nextLevel = level; + + uint8_t nonMatchingKey; + Prefix remainingPrefix; + auto res = checkPrefixPessimistic(node, k, nextLevel, nonMatchingKey, remainingPrefix, + this->loadKey, needRestart); // increases level + if (needRestart) goto restart; + switch (res) { + case CheckPrefixPessimisticResult::NoMatch: { + parentNode->upgradeToWriteLockOrRestart(parentVersion, needRestart); + if (needRestart) goto restart; + + node->upgradeToWriteLockOrRestart(v, needRestart); + if (needRestart) { + parentNode->writeUnlock(); + goto restart; + } + // 1) Create new node which will be parent of node, Set common prefix, level to this node + auto newNode = new N4(node->getPrefix(), nextLevel - level); + + // 2) add node and (tid, *k) as children + tid = make_tid(); + newNode->insert(k[nextLevel], N::setLeaf(tid)); + newNode->insert(nonMatchingKey, node); + + // 3) upgradeToWriteLockOrRestart, update parentNode to point to the new node, unlock + N::change(parentNode, parentKey, newNode); + parentNode->writeUnlock(); + + // 4) update prefix of node, unlock + node->setPrefix(remainingPrefix, + node->getPrefixLength() - ((nextLevel - level) + 1)); + + node->writeUnlock(); + return ins_return_type(tid, node, nullptr); + } + case CheckPrefixPessimisticResult::Match: + break; + } + // if (nextLevel >= k.getKeyLen()) { + // node->readUnlockOrRestart(v, needRestart); + // if (needRestart) goto restart; + // if (parentNode != nullptr) { + // parentNode->readUnlockOrRestart(parentVersion, needRestart); + // if (needRestart) goto restart; + // } + // return {0, ; + // } + level = nextLevel; + nodeKey = k[level]; + nextNode = N::getChild(nodeKey, node); + node->checkOrRestart(v,needRestart); + if (needRestart) goto restart; + + if (nextNode == nullptr) { + if (!tid) tid = make_tid(); + N* newNode = N::insertAndUnlock(node, v, parentNode, parentVersion, parentKey, nodeKey, N::setLeaf(tid), needRestart); + if (needRestart) goto restart; + if (newNode) { + return ins_return_type(tid, newNode, node); + } + return ins_return_type(tid, node, nullptr); + // return ins_return_type(tid, newNode == nullptr ? node : newNode, newNode == nullptr ? nullptr : node); + } + + if (parentNode != nullptr) { + parentNode->readUnlockOrRestart(parentVersion, needRestart); + if (needRestart) goto restart; + } + + if (N::isLeaf(nextNode)) { + node->upgradeToWriteLockOrRestart(v, needRestart); + if (needRestart) goto restart; + + Key key; + loadKey(N::getLeaf(nextNode), key); + + level++; + + if (level >= k.getKeyLen() || level >= key.getKeyLen()) { + node->writeUnlock(); + return ins_return_type(N::getLeaf(nextNode), nullptr, nullptr); + } + + uint32_t prefixLength = 0; + + while (key[level + prefixLength] == k[level + prefixLength]) { + prefixLength++; + if (level + prefixLength >= k.getKeyLen() || level + prefixLength >= key.getKeyLen()) { + node->writeUnlock(); + return ins_return_type(N::getLeaf(nextNode), nullptr, nullptr); + } + } + + auto n4 = new N4(&k[level], prefixLength); + tid = make_tid(); + n4->insert(k[level + prefixLength], N::setLeaf(tid)); + n4->insert(key[level + prefixLength], nextNode); + N::change(node, k[level - 1], n4); + node->writeUnlock(); + return ins_return_type(tid, node, nullptr); + } + level++; + parentVersion = v; + } + } + + N* Tree::remove(const Key &k, TID tid) { + // EpocheGuard epocheGuard(threadInfo); + restart: + bool needRestart = false; + + N *node = nullptr; + N *nextNode = root; + N *parentNode = nullptr; + uint8_t parentKey, nodeKey = 0; + uint64_t parentVersion = 0; + uint32_t level = 0; + + while (true) { + parentNode = node; + parentKey = nodeKey; + node = nextNode; + auto v = node->readLockOrRestart(needRestart); + if (needRestart) goto restart; + + switch (checkPrefix(node, k, level)) { // increases level + case CheckPrefixResult::NoMatch: + node->readUnlockOrRestart(v, needRestart); + if (needRestart) goto restart; + return nullptr; + case CheckPrefixResult::OptimisticMatch: + // fallthrough + case CheckPrefixResult::Match: { + nodeKey = k[level]; + nextNode = N::getChild(nodeKey, node); + + node->checkOrRestart(v, needRestart); + if (needRestart) goto restart; + + if (nextNode == nullptr) { + node->readUnlockOrRestart(v, needRestart); + if (needRestart) goto restart; + return nullptr; + } + if (N::isLeaf(nextNode)) { + if (N::getLeaf(nextNode) != tid) { + return nullptr; + } + assert(parentNode == nullptr || node->getCount() != 1); + if (node->getCount() == 2 && parentNode != nullptr) { + parentNode->upgradeToWriteLockOrRestart(parentVersion, needRestart); + if (needRestart) goto restart; + + node->upgradeToWriteLockOrRestart(v, needRestart); + if (needRestart) { + parentNode->writeUnlock(); + goto restart; + } + // 1. check remaining entries + N *secondNodeN; + uint8_t secondNodeK; + std::tie(secondNodeN, secondNodeK) = N::getSecondChild(node, nodeKey); + if (N::isLeaf(secondNodeN)) { + //N::remove(node, k[level]); not necessary + N::change(parentNode, parentKey, secondNodeN); + + parentNode->writeUnlock(); + node->writeUnlockObsolete(); + return node; + // Transaction::rcu_delete(node); + // this->epoche.markNodeForDeletion(node, threadInfo); + } else { + secondNodeN->writeLockOrRestart(needRestart); + if (needRestart) { + node->writeUnlock(); + parentNode->writeUnlock(); + goto restart; + } + + //N::remove(node, k[level]); not necessary + N::change(parentNode, parentKey, secondNodeN); + parentNode->writeUnlock(); + + secondNodeN->addPrefixBefore(node, secondNodeK); + secondNodeN->writeUnlock(); + + node->writeUnlockObsolete(); + return node; + // Transaction::rcu_delete(node); + // this->epoche.markNodeForDeletion(node, threadInfo); + } + } else { + auto old = N::removeAndUnlock(node, v, k[level], parentNode, parentVersion, parentKey, needRestart); + if (needRestart) goto restart; + return old; + } + return nullptr; + } + level++; + parentVersion = v; + } + } + } + } + + inline typename Tree::CheckPrefixResult Tree::checkPrefix(N *n, const Key &k, uint32_t &level) { + if (n->hasPrefix()) { + if (k.getKeyLen() <= level + n->getPrefixLength()) { + return CheckPrefixResult::NoMatch; + } + for (uint32_t i = 0; i < std::min(n->getPrefixLength(), maxStoredPrefixLength); ++i) { + if (n->getPrefix()[i] != k[level]) { + return CheckPrefixResult::NoMatch; + } + ++level; + } + if (n->getPrefixLength() > maxStoredPrefixLength) { + level = level + (n->getPrefixLength() - maxStoredPrefixLength); + return CheckPrefixResult::OptimisticMatch; + } + } + return CheckPrefixResult::Match; + } + + typename Tree::CheckPrefixPessimisticResult Tree::checkPrefixPessimistic(N *n, const Key &k, uint32_t &level, + uint8_t &nonMatchingKey, + Prefix &nonMatchingPrefix, + LoadKeyFunction loadKey, bool &needRestart) { + if (n->hasPrefix()) { + uint32_t prevLevel = level; + Key kt; + for (uint32_t i = 0; i < n->getPrefixLength(); ++i) { + if (i == maxStoredPrefixLength) { + auto anyTID = N::getAnyChildTid(n, needRestart); + if (needRestart) return CheckPrefixPessimisticResult::Match; + loadKey(anyTID, kt); + } + uint8_t curKey = i >= maxStoredPrefixLength ? kt[level] : n->getPrefix()[i]; + if (curKey != k[level]) { + nonMatchingKey = curKey; + if (n->getPrefixLength() > maxStoredPrefixLength) { + if (i < maxStoredPrefixLength) { + auto anyTID = N::getAnyChildTid(n, needRestart); + if (needRestart) return CheckPrefixPessimisticResult::Match; + loadKey(anyTID, kt); + } + memcpy(nonMatchingPrefix, &kt[0] + level + 1, std::min((n->getPrefixLength() - (level - prevLevel) - 1), + maxStoredPrefixLength)); + } else { + memcpy(nonMatchingPrefix, n->getPrefix() + i + 1, n->getPrefixLength() - i - 1); + } + return CheckPrefixPessimisticResult::NoMatch; + } + ++level; + } + } + return CheckPrefixPessimisticResult::Match; + } + + typename Tree::PCCompareResults Tree::checkPrefixCompare(const N *n, const Key &k, uint8_t fillKey, uint32_t &level, + LoadKeyFunction loadKey, bool &needRestart) { + if (n->hasPrefix()) { + Key kt; + for (uint32_t i = 0; i < n->getPrefixLength(); ++i) { + if (i == maxStoredPrefixLength) { + auto anyTID = N::getAnyChildTid(n, needRestart); + if (needRestart) return PCCompareResults::Equal; + loadKey(anyTID, kt); + } + uint8_t kLevel = (k.getKeyLen() > level) ? k[level] : fillKey; + + uint8_t curKey = i >= maxStoredPrefixLength ? kt[level] : n->getPrefix()[i]; + if (curKey < kLevel) { + return PCCompareResults::Smaller; + } else if (curKey > kLevel) { + return PCCompareResults::Bigger; + } + ++level; + } + } + return PCCompareResults::Equal; + } + + typename Tree::PCEqualsResults Tree::checkPrefixEquals(const N *n, uint32_t &level, const Key &start, const Key &end, + LoadKeyFunction loadKey, bool &needRestart) { + if (n->hasPrefix()) { + Key kt; + for (uint32_t i = 0; i < n->getPrefixLength(); ++i) { + if (i == maxStoredPrefixLength) { + auto anyTID = N::getAnyChildTid(n, needRestart); + if (needRestart) return PCEqualsResults::BothMatch; + loadKey(anyTID, kt); + } + uint8_t startLevel = (start.getKeyLen() > level) ? start[level] : 0; + uint8_t endLevel = (end.getKeyLen() > level) ? end[level] : 255; + + uint8_t curKey = i >= maxStoredPrefixLength ? kt[level] : n->getPrefix()[i]; + if (curKey > startLevel && curKey < endLevel) { + return PCEqualsResults::Contained; + } else if (curKey < startLevel || curKey > endLevel) { + return PCEqualsResults::NoMatch; + } + ++level; + } + } + return PCEqualsResults::BothMatch; + } +} diff --git a/ART/Tree.h b/ART/Tree.h new file mode 100644 index 00000000..67bb066d --- /dev/null +++ b/ART/Tree.h @@ -0,0 +1,84 @@ +#pragma once +// +// Created by florian on 18.11.15. +// + +#ifndef ART_OPTIMISTICLOCK_COUPLING_N_H +#define ART_OPTIMISTICLOCK_COUPLING_N_H +#include "N.h" + +using namespace ART; + +namespace ART_OLC { + class Tree { + public: + using LoadKeyFunction = void (*)(TID tid, Key &key); + typedef std::tuple ins_return_type; // (success, found) + + private: + N *const root; + + TID checkKey(const TID tid, const Key &k) const; + + LoadKeyFunction loadKey; + + // Epoche epoche{256}; + + public: + enum class CheckPrefixResult : uint8_t { + Match, + NoMatch, + OptimisticMatch + }; + + enum class CheckPrefixPessimisticResult : uint8_t { + Match, + NoMatch, + }; + + enum class PCCompareResults : uint8_t { + Smaller, + Equal, + Bigger, + }; + enum class PCEqualsResults : uint8_t { + BothMatch, + Contained, + NoMatch + }; + static CheckPrefixResult checkPrefix(N* n, const Key &k, uint32_t &level); + + static CheckPrefixPessimisticResult checkPrefixPessimistic(N *n, const Key &k, uint32_t &level, + uint8_t &nonMatchingKey, + Prefix &nonMatchingPrefix, + LoadKeyFunction loadKey, bool &needRestart); + + static PCCompareResults checkPrefixCompare(const N* n, const Key &k, uint8_t fillKey, uint32_t &level, LoadKeyFunction loadKey, bool &needRestart); + + static PCEqualsResults checkPrefixEquals(const N* n, uint32_t &level, const Key &start, const Key &end, LoadKeyFunction loadKey, bool &needRestart); + + public: + + Tree(); + Tree(LoadKeyFunction loadKey); + + void setLoadKey(LoadKeyFunction f); + + Tree(const Tree &) = delete; + + Tree(Tree &&t) : root(t.root), loadKey(t.loadKey) { } + + ~Tree(); + + std::pair lookup(const Key &k) const; + + bool lookupRange(const Key &start, const Key &end, Key &continueKey, TID result[], std::size_t resultLen, + std::size_t &resultCount, std::function observe_node) const; + + ins_return_type insert(const Key &k, std::function make_tid); + + N* remove(const Key &k, TID tid); + void print() const; + }; +} +#endif //ART_OPTIMISTICLOCK_COUPLING_N_H diff --git a/GNUmakefile.in b/GNUmakefile.in index 252ebc3d..9226606b 100644 --- a/GNUmakefile.in +++ b/GNUmakefile.in @@ -95,6 +95,11 @@ UNIT_PROGRAMS = unit-tarray \ unit-tintpredicate \ unit-tcounter \ unit-tbox \ + unit-tint \ + unit-tart \ + unit-artindex \ + bench-tart \ + tart-bank \ unit-tgeneric \ unit-rcu \ unit-tvector \ @@ -105,6 +110,7 @@ UNIT_PROGRAMS = unit-tarray \ unit-swisstarray \ unit-swisstgeneric \ unit-masstree \ + bench-masstree \ unit-dboindex ACT_UNIT_PROGRAMS = unit-tarray \ @@ -112,6 +118,11 @@ ACT_UNIT_PROGRAMS = unit-tarray \ unit-tintpredicate \ unit-tcounter \ unit-tbox \ + unit-tint \ + unit-tart \ + unit-artindex \ + bench-tart \ + tart-bank \ unit-rcu \ unit-tvector \ unit-tvector-nopred \ @@ -120,6 +131,7 @@ ACT_UNIT_PROGRAMS = unit-tarray \ unit-swisstarray \ unit-swisstgeneric \ unit-masstree \ + bench-masstree \ unit-dboindex PROGRAMS = concurrent \ @@ -155,6 +167,9 @@ check: act-unit %.o: %.c config.h $(DEPSDIR)/stamp $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(OPTFLAGS) $(DEPCFLAGS) -include config.h -c -o $@ $< +$(OBJ)/%.o: ART/%.cpp config.h $(DEPSDIR)/stamp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(OPTFLAGS) $(DEPCFLAGS) -include config.h -c -o $@ $< + $(OBJ)/%.o: lib/%.c config.h $(DEPSDIR)/stamp $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(OPTFLAGS) $(DEPCFLAGS) -include config.h -c -o $@ $< @@ -178,7 +193,9 @@ MASSTREE_OBJS = $(MASSTREEDIR)/kvio.o \ $(MASSTREEDIR)/checkpoint.o \ $(MASSTREEDIR)/string_slice.o -STO_OBJS = $(OBJ)/Packer.o $(OBJ)/Transaction.o $(OBJ)/TRcu.o $(OBJ)/clp.o $(OBJ)/ContentionManager.o $(LIBOBJS) +ART_OBJS = $(OBJ)/Tree.o + +STO_OBJS = $(OBJ)/Packer.o $(OBJ)/Transaction.o $(OBJ)/TRcu.o $(OBJ)/clp.o $(OBJ)/ContentionManager.o $(LIBOBJS) $(ART_OBJS) INDEX_OBJS = $(STO_OBJS) $(MASSTREE_OBJS) $(OBJ)/DB_index.o STO_DEPS = $(STO_OBJS) $(MASSTREEDIR)/libjson.a INDEX_DEPS = $(INDEX_OBJS) $(MASSTREEDIR)/libjson.a @@ -213,6 +230,21 @@ unit-tcounter: $(OBJ)/unit-tcounter.o $(STO_DEPS) unit-tbox: $(OBJ)/unit-tbox.o $(STO_DEPS) $(CXX) $(CXXFLAGS) $(OPTFLAGS) -o $@ $< $(STO_OBJS) $(LDFLAGS) $(LIBS) +unit-tint: $(OBJ)/unit-tint.o $(STO_DEPS) + $(CXX) $(CXXFLAGS) $(OPTFLAGS) -o $@ $< $(STO_OBJS) $(LDFLAGS) $(LIBS) + +unit-tart: $(OBJ)/unit-tart.o $(STO_DEPS) + $(CXX) $(CXXFLAGS) $(OPTFLAGS) -o $@ $< $(STO_OBJS) $(LDFLAGS) $(LIBS) + +unit-artindex: $(OBJ)/unit-artindex.o $(INDEX_DEPS) + $(CXX) $(CXXFLAGS) $(OPTFLAGS) -o $@ $< $(INDEX_OBJS) $(LDFLAGS) $(LIBS) + +bench-tart: $(OBJ)/bench-tart.o $(INDEX_DEPS) + $(CXX) $(CXXFLAGS) $(OPTFLAGS) -o $@ $< $(INDEX_OBJS) $(LDFLAGS) $(LIBS) + +tart-bank: $(OBJ)/tart-bank.o $(INDEX_DEPS) + $(CXX) $(CXXFLAGS) $(OPTFLAGS) -o $@ $< $(INDEX_OBJS) $(LDFLAGS) $(LIBS) + unit-tgeneric: $(OBJ)/unit-tgeneric.o $(STO_DEPS) $(CXX) $(CXXFLAGS) $(OPTFLAGS) -o $@ $< $(STO_OBJS) $(LDFLAGS) $(LIBS) @@ -240,6 +272,9 @@ unit-swisstgeneric: $(OBJ)/unit-swisstgeneric.o $(STO_DEPS) unit-masstree: $(OBJ)/unit-masstree.o $(STO_DEPS) $(CXX) $(CXXFLAGS) $(OPTFLAGS) -o $@ $< $(STO_DEPS) $(LDFLAGS) $(LIBS) +bench-masstree: $(OBJ)/bench-masstree.o $(STO_DEPS) + $(CXX) $(CXXFLAGS) $(OPTFLAGS) -o $@ $< $(STO_DEPS) $(LDFLAGS) $(LIBS) + unit-dboindex: $(OBJ)/unit-dboindex.o $(INDEX_DEPS) $(CXX) $(CXXFLAGS) $(OPTFLAGS) -o $@ $< $(INDEX_DEPS) $(LDFLAGS) $(LIBS) @@ -294,6 +329,9 @@ wiki_bench: $(OBJ)/Wikipedia_bench.o $(OBJ)/Wikipedia_data.o $(INDEX_OBJS) voter_bench: $(OBJ)/Voter_bench.o $(OBJ)/Voter_data.o $(INDEX_OBJS) $(CXX) $(CXXFLAGS) $(OPTFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) +tart_bench: $(OBJ)/TART_bench.o $(INDEX_OBJS) + $(CXX) $(CXXFLAGS) $(OPTFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS) + $(MASSTREE_OBJS): masstree ; .PHONY: masstree diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 16a83563..b41c807f 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(micro_bench MicroBenchmarks.cc Micro_structs.hh ${COMMON_HEADERS} add_executable(pred_bench Predicate_bench.cc Predicate_bench.hh ${COMMON_HEADERS}) add_executable(wiki_bench Wikipedia_bench.cc Wikipedia_data.cc Wikipedia_bench.hh Wikipedia_txns.hh Wikipedia_structs.hh Wikipedia_loader.hh ${COMMON_HEADERS} Wikipedia_selectors.hh) add_executable(voter_bench Voter_txns.hh Voter_structs.hh Voter_bench.hh Voter_bench.cc Voter_data.cc ${COMMON_HEADERS}) +add_executable(tart_bench TART_bench.hh TART_bench.cc ${COMMON_HEADERS}) target_link_libraries(tpcc_bench db_index sto clp masstree json dprint xxhash ${PLATFORM_LIBRARIES}) target_link_libraries(ycsb_bench db_index sto clp masstree json dprint xxhash ${PLATFORM_LIBRARIES}) @@ -15,3 +16,4 @@ target_link_libraries(micro_bench db_index sto clp masstree json dprint ${PLATFO target_link_libraries(pred_bench db_index sto clp masstree json dprint ${PLATFORM_LIBRARIES}) target_link_libraries(wiki_bench db_index sto clp masstree json dprint ${PLATFORM_LIBRARIES}) target_link_libraries(voter_bench db_index sto clp masstree json dprint ${PLATFORM_LIBRARIES}) +target_link_libraries(tart_bench db_index sto clp masstree json dprint ${PLATFORM_LIBRARIES}) diff --git a/benchmark/DB_index.hh b/benchmark/DB_index.hh index a53f16be..9c1925f5 100644 --- a/benchmark/DB_index.hh +++ b/benchmark/DB_index.hh @@ -12,6 +12,8 @@ #include "masstree_remove.hh" #include "masstree_scan.hh" #include "string.hh" +#include "../ART/Key.h" +#include "../ART/Tree.h" #include #include "VersionSelector.hh" @@ -565,6 +567,934 @@ private: enum class RowAccess : int { None = 0, ObserveExists, ObserveValue, UpdateValue }; +template +class art_index : public TObject { +public: + typedef K key_type; + typedef V value_type; + + //typedef typename get_occ_version::type occ_version_type; + typedef typename get_version::type version_type; + + static constexpr typename version_type::type invalid_bit = TransactionTid::user_bit; + static constexpr TransItem::flags_type insert_bit = TransItem::user0_bit; + static constexpr TransItem::flags_type delete_bit = TransItem::user0_bit << 1u; + static constexpr TransItem::flags_type row_update_bit = TransItem::user0_bit << 2u; + static constexpr TransItem::flags_type row_cell_bit = TransItem::user0_bit << 3u; + static constexpr TransItem::flags_type self_upgrade_bit = TransItem::user0_bit << 4u; + static constexpr uintptr_t internode_bit = 1; + + typedef typename value_type::NamedColumn NamedColumn; + typedef IndexValueContainer value_container_type; + + static constexpr bool value_is_small = is_small::value; + + static constexpr bool index_read_my_write = DBParams::RdMyWr; + + struct internal_elem { + key_type key; + value_container_type row_container; + bool deleted; + + internal_elem(const key_type& k, const value_type& v, bool valid) + : key(k), + row_container((valid ? Sto::initialized_tid() : (Sto::initialized_tid() | invalid_bit)), + !valid, v), + deleted(false) {} + + version_type& version() { + return row_container.row_version(); + } + + bool valid() { + return !(version().value() & invalid_bit); + } + }; + + struct column_access_t { + int col_id; + bool update; + + column_access_t(NamedColumn column, bool for_update) + : col_id(static_cast(column)), update(for_update) {} + }; + + struct cell_access_t { + int cell_id; + bool update; + + cell_access_t(int cid, bool for_update) + : cell_id(cid), update(for_update) {} + }; + + // TransItem key format: + // |----internal_elem pointer----|--cell id--|I| + // 48 bits 15 bits 1 + + // I: internode bit + // cell id: valid range 0-32767 (0x7fff) + // cell id 0 identifies the row item + + class item_key_t { + typedef uintptr_t type; + static constexpr unsigned shift = 16u; + static constexpr type cell_mask = type(0xfffe); + type key_; + + public: + item_key_t() : key_() {}; + item_key_t(internal_elem *e, int cell_num) : key_((reinterpret_cast(e) << shift) + | ((static_cast(cell_num) << 1u) & cell_mask)) {}; + + static item_key_t row_item_key(internal_elem *e) { + return item_key_t(e, 0); + } + + internal_elem *internal_elem_ptr() const { + return reinterpret_cast(key_ >> shift); + } + + int cell_num() const { + return static_cast((key_ & cell_mask) >> 1); + } + + bool is_row_item() const { + return (cell_num() == 0); + } + }; + + static std::vector + column_to_cell_accesses(std::function c_c_map, std::initializer_list accesses) { + // pair: {accessed, for update} + std::vector> all_cells(value_container_type::num_versions, {false, false}); + // the returned list + std::vector cell_accesses; + + for (auto ca : accesses) { + int cell_id = c_c_map(ca.col_id); + all_cells[cell_id].first = true; + all_cells[cell_id].second |= ca.update; + } + + for (auto it = all_cells.begin(); it != all_cells.end(); ++it) { + if (it->first) + cell_accesses.emplace_back(static_cast(it-all_cells.begin()), it->second); + } + return cell_accesses; + } + + // struct table_params : public Masstree::nodeparams<15,15> { + // typedef internal_elem* value_type; + // typedef Masstree::value_print value_print_type; + // typedef threadinfo threadinfo_type; + // }; + + typedef Masstree::Str Str; + typedef ART_OLC::Tree table_type; + // typedef Masstree::unlocked_tcursor unlocked_cursor_type; + // typedef Masstree::tcursor cursor_type; + // typedef Masstree::leaf leaf_type; + + typedef typename ART_OLC::N node_type; + // typedef typename table_type::node_type node_type; + typedef TVersion nodeversion_value_type; + + typedef std::tuple sel_return_type; + typedef std::tuple ins_return_type; + typedef std::tuple del_return_type; + + // static __thread typename table_params::threadinfo_type *ti; + + art_index(size_t init_size) { + this->table_init(); + (void)init_size; + } + art_index() { + this->table_init(); + } + + void table_init() { + // if (ti == nullptr) + // ti = threadinfo::make(threadinfo::TI_MAIN, -1); + // table_.initialize(*ti); + // table_ = new ART_OLC::Tree(); + table_.setLoadKey(art_index::load_key); + // TODO: set load key + key_gen_ = 0; + } + + static void thread_init() { + // if (ti == nullptr) + // ti = threadinfo::make(threadinfo::TI_PROCESS, TThread::id()); + // Transaction::tinfo[TThread::id()].trans_start_callback = []() { + // ti->rcu_start(); + // }; + // Transaction::tinfo[TThread::id()].trans_end_callback = []() { + // ti->rcu_stop(); + // }; + } + + uint64_t gen_key() { + return fetch_and_add(&key_gen_, 1); + } + + sel_return_type + select_row(const key_type& key, RowAccess acc) { + Key art_key; + make_key(key, art_key); + auto r = table_.lookup(art_key); + internal_elem* e = (internal_elem*) r.first; + if (e) { + return select_row(reinterpret_cast(e), acc); + } else { + if (!register_internode_version(r.second, r.second->vers)) + goto abort; + return sel_return_type(true, false, 0, nullptr); + } + + abort: + return sel_return_type(false, false, 0, nullptr); + } + + sel_return_type + select_row(const key_type& key, std::initializer_list accesses) { + Key art_key; + make_key(key, art_key); + auto r = table_.lookup(art_key); + internal_elem* e = (internal_elem*) r.first; + if (e) { + return select_row(reinterpret_cast(e), accesses); + } else { + if (!register_internode_version(r.second, r.second->vers)) + goto abort; + return sel_return_type(true, false, 0, nullptr); + } + + abort: + return sel_return_type(false, false, 0, nullptr); + } + + sel_return_type + select_row(uintptr_t rid, RowAccess access) { + auto e = reinterpret_cast(rid); + bool ok = true; + TransProxy row_item = Sto::item(this, item_key_t::row_item_key(e)); + + if (is_phantom(e, row_item)) + goto abort; + + if (index_read_my_write) { + if (has_delete(row_item)) { + return sel_return_type(true, false, 0, nullptr); + } + if (has_row_update(row_item)) { + value_type *vptr; + if (has_insert(row_item)) + vptr = &e->row_container.row; + else + vptr = row_item.template raw_write_value(); + return sel_return_type(true, true, rid, vptr); + } + } + + switch (access) { + case RowAccess::UpdateValue: + ok = version_adapter::select_for_update(row_item, e->version()); + row_item.add_flags(row_update_bit); + break; + case RowAccess::ObserveExists: + case RowAccess::ObserveValue: + ok = row_item.observe(e->version()); + break; + default: + break; + } + + if (!ok) + goto abort; + + return sel_return_type(true, true, rid, &(e->row_container.row)); + + abort: + return sel_return_type(false, false, 0, nullptr); + } + + sel_return_type + select_row(uintptr_t rid, std::initializer_list accesses) { + auto e = reinterpret_cast(rid); + TransProxy row_item = Sto::item(this, item_key_t::row_item_key(e)); + + // Translate from column accesses to cell accesses + // all buffered writes are only stored in the wdata_ of the row item (to avoid redundant copies) + auto cell_accesses = column_to_cell_accesses(value_container_type::map, accesses); + + std::vector cell_items; + bool any_has_write; + bool ok; + std::tie(any_has_write, cell_items) = extract_item_list(cell_accesses, e); + + if (is_phantom(e, row_item)) + goto abort; + + if (index_read_my_write) { + if (has_delete(row_item)) { + return sel_return_type(true, false, 0, nullptr); + } + if (any_has_write || has_row_update(row_item)) { + value_type *vptr; + if (has_insert(row_item)) + vptr = &e->row_container.row; + else + vptr = row_item.template raw_write_value(); + return sel_return_type(true, true, rid, vptr); + } + } + + ok = access_all(cell_accesses, cell_items, e); + if (!ok) + goto abort; + + return sel_return_type(true, true, rid, &(e->row_container.row)); + + abort: + return sel_return_type(false, false, 0, nullptr); + } + + void update_row(uintptr_t rid, value_type *new_row) { + auto row_item = Sto::item(this, item_key_t::row_item_key(reinterpret_cast(rid))); + if (value_is_small) { + row_item.add_write(*new_row); + } else { + row_item.add_write(new_row); + } + // Just update the pointer, don't set the actual write flag + // we don't want to confuse installs at commit time + //row_item.clear_write(); + } + + // insert assumes common case where the row doesn't exist in the table + // if a row already exists, then use select (FOR UPDATE) instead + ins_return_type + insert_row(const key_type& key, value_type *vptr, bool overwrite = false) { + // cursor_type lp(table_, key); + // bool found = lp.find_insert(*ti); + + Key art_key; + make_key(key, art_key); + + TID t; + node_type* parent; + node_type* old; + std::tie(t, parent, old) = table_.insert(art_key, [key, vptr] { + auto e = new internal_elem(key, vptr ? *vptr : value_type(), + false /*!valid*/); + return (TID) e; + }); + internal_elem* e = (internal_elem*) t; + bool found = !parent; + if (found) { + // NB: the insert method only manipulates the row_item. It is possible + // this insert is overwriting some previous updates on selected columns + // The expected behavior is that this row-level operation should overwrite + // all changes made by previous updates (in the same transaction) on this + // row. We achieve this by granting this row_item a higher priority. + // During the install phase, if we notice that the row item has already + // been locked then we simply ignore installing any changes made by cell items. + // It should be trivial for a cell item to find the corresponding row item + // and figure out if the row-level version is locked. + TransProxy row_item = Sto::item(this, item_key_t::row_item_key(e)); + + if (is_phantom(e, row_item)) + goto abort; + + if (index_read_my_write) { + if (has_delete(row_item)) { + auto proxy = row_item.clear_flags(delete_bit).clear_write(); + + if (value_is_small) + proxy.add_write(*vptr); + else + proxy.add_write(vptr); + + return ins_return_type(true, false); + } + } + + if (overwrite) { + bool ok; + if (value_is_small) + ok = version_adapter::select_for_overwrite(row_item, e->version(), *vptr); + else + ok = version_adapter::select_for_overwrite(row_item, e->version(), vptr); + if (!ok) + goto abort; + if (index_read_my_write) { + if (has_insert(row_item)) { + copy_row(e, vptr); + } + } + } else { + // observes that the row exists, but nothing more + if (!row_item.observe(e->version())) + goto abort; + } + + } else { + TransProxy row_item = Sto::item(this, item_key_t::row_item_key(e)); + //if (value_is_small) + // item.add_write(*vptr); + //else + // item.add_write(vptr); + row_item.add_write(); + row_item.add_flags(insert_bit); + + TransProxy internode_item = Sto::item(this, get_internode_key(parent)); + internode_item.add_write(); + + if (old) { + old->valid = false; + TransProxy old_item = Sto::item(this, get_internode_key(old)); + old_item.add_flags(self_upgrade_bit); + if (old_item.has_read()) { + if (!register_internode_version(parent, parent->vers)) + goto abort; + } + } + + // update the node version already in the read set and modified by split + // if (!update_internode_version(node, orig_nv, new_nv)) + // goto abort; + } + + return ins_return_type(true, found); + + abort: + return ins_return_type(false, false); + } + + del_return_type + delete_row(const key_type& key) { + // unlocked_cursor_type lp(table_, key); + // bool found = lp.find_unlocked(*ti); + Key art_key; + make_key(key, art_key); + auto r = table_.lookup(art_key); + internal_elem* e = (internal_elem*) r.first; + bool found = e; + if (found) { + TransProxy row_item = Sto::item(this, item_key_t::row_item_key(e)); + + if (is_phantom(e, row_item)) + goto abort; + + if (index_read_my_write) { + if (has_delete(row_item)) + return del_return_type(true, false); + if (!e->valid() && has_insert(row_item)) { + row_item.add_flags(delete_bit); + return del_return_type(true, true); + } + } + + // select_for_update will register an observation and set the write bit of + // the TItem + if (!version_adapter::select_for_update(row_item, e->version())) + goto abort; + fence(); + if (e->deleted) + goto abort; + row_item.add_flags(delete_bit); + } else { + if (!register_internode_version(r.second, r.second->vers)) + goto abort; + } + + return del_return_type(true, found); + + abort: + return del_return_type(false, false); + } + + // template + // bool range_scan(const key_type& begin, const key_type& end, Callback callback, + // std::initializer_list accesses, bool phantom_protection = true, int limit = -1) { + // assert((limit == -1) || (limit > 0)); + // auto node_callback = [&] (leaf_type* node, + // typename unlocked_cursor_type::nodeversion_value_type version) { + // return ((!phantom_protection) || register_internode_version(node, version)); + // }; + // + // auto cell_accesses = column_to_cell_accesses(value_container_type::map, accesses); + // + // auto value_callback = [&] (const lcdf::Str& key, internal_elem *e, bool& ret) { + // TransProxy row_item = index_read_my_write ? Sto::item(this, item_key_t::row_item_key(e)) + // : Sto::fresh_item(this, item_key_t::row_item_key(e)); + // + // bool any_has_write; + // std::vector cell_items; + // std::tie(any_has_write, cell_items) = extract_item_list(cell_accesses, e); + // + // if (index_read_my_write) { + // if (has_delete(row_item)) { + // ret = true; + // return true; + // } + // if (any_has_write) { + // if (has_insert(row_item)) + // ret = callback(key_type(key), e->row_container.row); + // else + // ret = callback(key_type(key), *(row_item.template raw_write_value())); + // return true; + // } + // } + // + // bool ok = access_all(cell_accesses, cell_items, e); + // if (!ok) + // return false; + // //bool ok = item.observe(e->version); + // //if (Adaptive) { + // // ok = item.observe(e->version, true#<{(|force occ|)}>#); + // //} else { + // // ok = item.observe(e->version); + // //} + // + // // skip invalid (inserted but yet committed) values, but do not abort + // if (!e->valid()) { + // ret = true; + // return true; + // } + // + // ret = callback(key_type(key), e->row_container.row); + // return true; + // }; + // + // range_scanner + // scanner(end, node_callback, value_callback); + // if (Reverse) + // table_.rscan(begin, true, scanner, limit, *ti); + // else + // table_.scan(begin, true, scanner, limit, *ti); + // return scanner.scan_succeeded_; + // } + // + // template + // bool range_scan(const key_type& begin, const key_type& end, Callback callback, + // RowAccess access, bool phantom_protection = true, int limit = -1) { + // assert((limit == -1) || (limit > 0)); + // auto node_callback = [&] (leaf_type* node, + // typename unlocked_cursor_type::nodeversion_value_type version) { + // return ((!phantom_protection) || register_internode_version(node, version)); + // }; + // + // auto value_callback = [&] (const lcdf::Str& key, internal_elem *e, bool& ret) { + // TransProxy row_item = index_read_my_write ? Sto::item(this, item_key_t::row_item_key(e)) + // : Sto::fresh_item(this, item_key_t::row_item_key(e)); + // + // if (index_read_my_write) { + // if (has_delete(row_item)) { + // ret = true; + // return true; + // } + // if (has_row_update(row_item)) { + // if (has_insert(row_item)) + // ret = callback(key_type(key), e->row_container.row); + // else + // ret = callback(key_type(key), *(row_item.template raw_write_value())); + // return true; + // } + // } + // + // bool ok = true; + // switch (access) { + // case RowAccess::ObserveValue: + // case RowAccess::ObserveExists: + // ok = row_item.observe(e->version()); + // break; + // case RowAccess::None: + // break; + // default: + // always_assert(false, "unsupported access type in range_scan"); + // break; + // } + // + // if (!ok) + // return false; + // + // // skip invalid (inserted but yet committed) values, but do not abort + // if (!e->valid()) { + // ret = true; + // return true; + // } + // + // ret = callback(key_type(key), e->row_container.row); + // return true; + // }; + // + // range_scanner + // scanner(end, node_callback, value_callback); + // if (Reverse) + // table_.rscan(begin, true, scanner, limit, *ti); + // else + // table_.scan(begin, true, scanner, limit, *ti); + // return scanner.scan_succeeded_; + // } + + value_type *nontrans_get(const key_type& k) { + // unlocked_cursor_type lp(table_, k); + // bool found = lp.find_unlocked(*ti); + Key art_key; + make_key(k, art_key); + auto r = table_.lookup(art_key); + internal_elem* e = (internal_elem*) r.first; + if (e) { + return &(e->row_container.row); + } else + return nullptr; + } + + void nontrans_put(const key_type& k, const value_type& v) { + // cursor_type lp(table_, k); + // bool found = lp.find_insert(*ti); + + Key art_key; + make_key(k, art_key); + + TID t; + node_type* parent; + node_type* old; + std::tie(t, parent, old) = table_.insert(art_key, [k, v] { + internal_elem *e = new internal_elem(k, v, true); + return (TID) e; + }); + if (!parent) { + internal_elem *e = (internal_elem*) t; + if (value_is_small) + e->row_container.row = v; + else + copy_row(e, &v); + // lp.finish(0, *ti); + } + if (old) { + // printf("hi\n"); + // Transaction::rcu_delete(old); + delete old; + } + } + + // TObject interface methods + bool lock(TransItem& item, Transaction &txn) override { + // assert(!is_internode(item)); + if (is_internode(item)) { + node_type* n = get_internode_address(item); + return txn.try_lock(item, n->vers); + } + auto key = item.key(); + auto e = key.internal_elem_ptr(); + if (key.is_row_item()) + return txn.try_lock(item, e->version()); + else + return txn.try_lock(item, e->row_container.version_at(key.cell_num())); + } + + bool check(TransItem& item, Transaction& txn) override { + if (is_internode(item)) { + node_type *n = get_internode_address(item); + // XXX: What is this + // auto curr_nv = n->vers; + // auto read_nv = item.template read_value(); + // return (curr_nv == read_nv); + return (item.has_flag(self_upgrade_bit) || n->valid) && n->vers.cp_check_version(txn, item); + } else { + auto key = item.key(); + auto e = key.internal_elem_ptr(); + if (key.is_row_item()) + return e->version().cp_check_version(txn, item); + else + return e->row_container.version_at(key.cell_num()).cp_check_version(txn, item); + } + } + + void install(TransItem& item, Transaction& txn) override { + if (is_internode(item)) { + node_type* n = get_internode_address(item); + txn.set_version_unlock(n->vers, item); + return; + } + + // assert(!is_internode(item)); + auto key = item.key(); + auto e = key.internal_elem_ptr(); + + if (key.is_row_item()) { + //assert(e->version.is_locked()); + if (has_delete(item)) { + if (!has_insert(item)) { + assert(e->valid() && !e->deleted); + txn.set_version(e->version()); + e->deleted = true; + fence(); + } + return; + } + + value_type *vptr; + if (value_is_small) { + vptr = &(item.write_value()); + } else { + vptr = item.write_value(); + } + + if (!has_insert(item)) { + if (has_row_update(item)) { + if (value_is_small) { + e->row_container.row = *vptr; + } else { + copy_row(e, vptr); + } + } else if (has_row_cell(item)) { + // install only the difference part + // not sure if works when there are more than 1 minor version fields + // should still work + e->row_container.install_cell(0, vptr); + } + } + + // like in the hashtable (unordered_index), no need for the hacks + // treating opacity as a special case + txn.set_version_unlock(e->version(), item); + } else { + // skip installation if row-level update is present + auto row_item = Sto::item(this, item_key_t::row_item_key(e)); + if (!has_row_update(row_item)) { + value_type *vptr; + if (value_is_small) + vptr = &(row_item.template raw_write_value()); + else + vptr = row_item.template raw_write_value(); + + e->row_container.install_cell(key.cell_num(), vptr); + } + + txn.set_version_unlock(e->row_container.version_at(key.cell_num()), item); + } + } + + void unlock(TransItem& item) override { + // assert(!is_internode(item)); + if (is_internode(item)) { + node_type* n = get_internode_address(item); + n->vers.cp_unlock(item); + return; + } + auto key = item.key(); + auto e = key.internal_elem_ptr(); + if (key.is_row_item()) + e->version().cp_unlock(item); + else + e->row_container.version_at(key.cell_num()).cp_unlock(item); + } + + void cleanup(TransItem& item, bool committed) override { + if (committed ? has_delete(item) : has_insert(item)) { + auto key = item.key(); + assert(key.is_row_item()); + internal_elem *e = key.internal_elem_ptr(); + bool ok = _remove(e->key); + if (!ok) { + std::cout << committed << "," << has_delete(item) << "," << has_insert(item) << std::endl; + always_assert(false, "insert-bit exclusive ownership violated"); + } + item.clear_needs_unlock(); + } + } + +protected: + // template + // class range_scanner { + // public: + // range_scanner(const Str upper, NodeCallback ncb, ValueCallback vcb) : + // boundary_(upper), boundary_compar_(false), scan_succeeded_(true), + // node_callback_(ncb), value_callback_(vcb) {} + // + // template + // void check(const ITER& iter, const KEY& key) { + // int min = std::min(boundary_.length(), key.prefix_length()); + // int cmp = memcmp(boundary_.data(), key.full_string().data(), min); + // if (!Reverse) { + // if (cmp < 0 || (cmp == 0 && boundary_.length() <= key.prefix_length())) + // boundary_compar_ = true; + // else if (cmp == 0) { + // uint64_t last_ikey = iter.node()->ikey0_[iter.permutation()[iter.permutation().size() - 1]]; + // uint64_t slice = string_slice::make_comparable(boundary_.data() + key.prefix_length(), + // std::min(boundary_.length() - key.prefix_length(), 8)); + // boundary_compar_ = (slice <= last_ikey); + // } + // } else { + // if (cmp >= 0) + // boundary_compar_ = true; + // } + // } + // + // template + // void visit_leaf(const ITER& iter, const Masstree::key& key, threadinfo&) { + // if (!node_callback_(iter.node(), iter.full_version_value())) { + // scan_succeeded_ = false; + // } + // if (this->boundary_) { + // check(iter, key); + // } + // } + // + // bool visit_value(const Masstree::key& key, internal_elem *e, threadinfo&) { + // if (this->boundary_compar_) { + // if ((Reverse && (boundary_ >= key.full_string())) || + // (!Reverse && (boundary_ <= key.full_string()))) + // return false; + // } + // bool visited = false; + // if (!value_callback_(key.full_string(), e, visited)) { + // scan_succeeded_ = false; + // return false; + // } else { + // if (!visited) + // scan_succeeded_ = false; + // return visited; + // } + // } + // + // Str boundary_; + // bool boundary_compar_; + // bool scan_succeeded_; + // + // NodeCallback node_callback_; + // ValueCallback value_callback_; + // }; + +private: + table_type table_; + uint64_t key_gen_; + + std::pair> + extract_item_list(const std::vector& cell_accesses, internal_elem *e) { + bool any_has_write = false; + std::vector cell_items; + cell_items.reserve(cell_accesses.size()); + for (auto& ca : cell_accesses) { + auto item = Sto::item(this, item_key_t(e, ca.cell_id)); + if (index_read_my_write && !any_has_write && item.has_write()) + any_has_write = true; + cell_items.push_back(std::move(item)); + } + return {any_has_write, cell_items}; + } + + static bool + access_all(std::vector& cell_accesses, std::vector& cell_items, internal_elem *e) { + for (auto it = cell_items.begin(); it != cell_items.end(); ++it) { + auto idx = it - cell_items.begin(); + auto& access = cell_accesses[idx]; + if (access.update) { + if (!version_adapter::select_for_update(*it, e->row_container.version_at(access.cell_id))) + return false; + if (it->item().key().is_row_item()) { + it->item().add_flags(row_cell_bit); + } + } else { + if (!it->observe(e->row_container.version_at(access.cell_id))) + return false; + } + } + return true; + } + + static bool has_insert(const TransItem& item) { + return (item.flags() & insert_bit) != 0; + } + static bool has_delete(const TransItem& item) { + return (item.flags() & delete_bit) != 0; + } + static bool has_row_update(const TransItem& item) { + return (item.flags() & row_update_bit) != 0; + } + static bool has_row_cell(const TransItem& item) { + return (item.flags() & row_cell_bit) != 0; + } + static bool is_phantom(internal_elem *e, const TransItem& item) { + return (!e->valid() && !has_insert(item)); + } + + static void load_key(TID tid, Key& key) { + internal_elem* e = (internal_elem*) tid; + Str s = e->key; + key.set(s.data(), s.length()); + } + + static void make_key(Str key, Key& art_key) { + art_key.set(key.data(), key.length()); + } + + bool register_internode_version(node_type *node, nodeversion_value_type nodeversion) { + TransProxy item = Sto::item(this, get_internode_key(node)); + if (DBParams::Opaque) + return item.observe(nodeversion); + else + return item.observe(nodeversion); + } + bool update_internode_version(node_type *node, + nodeversion_value_type prev_nv, nodeversion_value_type new_nv) { + TransProxy item = Sto::item(this, get_internode_key(node)); + if (!item.has_read()) { + return true; + } + if (prev_nv == item.template read_value()) { + item.update_read(prev_nv, new_nv); + return true; + } + return false; + } + + bool _remove(const key_type& key) { + // cursor_type lp(table_, key); + // bool found = lp.find_locked(*ti); + Key art_key; + make_key(key, art_key); + auto r = table_.lookup(art_key); + internal_elem* e = (internal_elem*) r.first; + + if (e) { + node_type* old = table_.remove(art_key, (TID) e); + if (old) { + old->valid = false; + Transaction::rcu_delete(old); + } + // lp.finish(-1, *ti); + Transaction::rcu_delete(e); + return true; + } else { + // XXX is this correct? + // lp.finish(0, *ti); + } + return false; + } + + static uintptr_t get_internode_key(node_type* node) { + return reinterpret_cast(node) | internode_bit; + } + static bool is_internode(TransItem& item) { + return (item.key() & internode_bit) != 0; + } + static node_type *get_internode_address(TransItem& item) { + assert(is_internode(item)); + return reinterpret_cast(item.key() & ~internode_bit); + } + + static void copy_row(internal_elem *e, const value_type *new_row) { + if (new_row == nullptr) + return; + e->row_container.row = *new_row; + } +}; + template class ordered_index : public TObject { public: diff --git a/benchmark/DB_params.hh b/benchmark/DB_params.hh index c6771d0b..828d339e 100644 --- a/benchmark/DB_params.hh +++ b/benchmark/DB_params.hh @@ -30,7 +30,7 @@ inline db_params_id parse_dbid(const char *id_string) { class db_default_params { public: static constexpr db_params_id Id = db_params_id::Default; - static constexpr bool RdMyWr = false; + static constexpr bool RdMyWr = true; static constexpr bool TwoPhaseLock = false; static constexpr bool Adaptive = false; static constexpr bool Opaque = false; @@ -75,4 +75,4 @@ public: static double processor_tsc_frequency; // in GHz }; -}; // namespace db_params \ No newline at end of file +}; // namespace db_params diff --git a/benchmark/MicroBenchmarks.hh b/benchmark/MicroBenchmarks.hh index 3ac6f321..0f6d2a3e 100644 --- a/benchmark/MicroBenchmarks.hh +++ b/benchmark/MicroBenchmarks.hh @@ -11,6 +11,7 @@ #include "DB_structs.hh" #include "DB_index.hh" #include "DB_params.hh" +#include "TART_index.hh" #include "Micro_structs.hh" @@ -389,6 +390,8 @@ public: typedef typename compute_value_type::type value_type; typedef typename value_type::NamedColumn nc; typedef typename tpcc::ordered_index index_type; + // typedef typename tpcc::art_index index_type; + // typedef typename bench::tart_index index_type; typedef typename index_type::column_access_t column_access_t; explicit MasstreeTester(size_t num_threads) : Base(num_threads), mt_() {} diff --git a/benchmark/Predicate_bench.hh b/benchmark/Predicate_bench.hh index 5d519f37..32353213 100644 --- a/benchmark/Predicate_bench.hh +++ b/benchmark/Predicate_bench.hh @@ -4,6 +4,7 @@ #include "TBox.hh" #include "TCounter.hh" #include "DB_params.hh" +#include "TART_index.hh" namespace predicate_bench { @@ -32,7 +33,7 @@ template class predicate_db { public: template - using OIndex = bench::ordered_index; + using OIndex = bench::tart_index; typedef OIndex> table_type; table_type& table() { diff --git a/benchmark/TART_bench.cc b/benchmark/TART_bench.cc new file mode 100644 index 00000000..e6bc4851 --- /dev/null +++ b/benchmark/TART_bench.cc @@ -0,0 +1,77 @@ +#include "clp.h" + +#include "DB_profiler.hh" +#include "PlatformFeatures.hh" +#include "TART_bench.hh" + +double db_params::constants::processor_tsc_frequency; + +enum { opt_nthrs = 1, opt_time }; + +struct cmd_params { + int num_threads; + double time_limit; + + cmd_params() : num_threads(1), time_limit(10.0) {} +}; + +static const Clp_Option options[] = { + { "nthreads", 't', opt_nthrs, Clp_ValInt, Clp_Optional }, + { "time", 'l', opt_time, Clp_ValDouble, Clp_Optional }, +}; + +int main(int argc, const char * const *argv) { + typedef db_params::db_default_params params; + typedef tart_bench::tart_runner r_type; + typedef tart_bench::tart_db db_type; + + using tart_bench::initialize_db; + + cmd_params p; + + Clp_Parser *clp = Clp_NewParser(argc, argv, arraysize(options), options); + int ret_code = 0; + int opt; + bool clp_stop = false; + while (!clp_stop && ((opt = Clp_Next(clp)) != Clp_Done)) { + switch (opt) { + case opt_nthrs: + p.num_threads = clp->val.i; + break; + case opt_time: + p.time_limit = clp->val.d; + break; + default: + ret_code = 1; + clp_stop = true; + break; + } + } + Clp_DeleteParser(clp); + if (ret_code != 0) + return ret_code; + + auto freq = determine_cpu_freq(); + if (freq == 0.0) + return -1; + db_params::constants::processor_tsc_frequency = freq; + + db_type db; + const size_t db_size = 256; + initialize_db(db, db_size); + + bench::db_profiler prof(false/*don't spawn perf*/); + auto nthreads = p.num_threads; + auto time_limit = p.time_limit; + std::cout << "Number of threads: " << nthreads << std::endl; + + r_type runner(nthreads, time_limit, db); + + size_t ncommits; + + prof.start(Profiler::perf_mode::record); + ncommits = runner.run(); + prof.finish(ncommits); + + return 0; +} diff --git a/benchmark/TART_bench.hh b/benchmark/TART_bench.hh new file mode 100644 index 00000000..ceec13c5 --- /dev/null +++ b/benchmark/TART_bench.hh @@ -0,0 +1,136 @@ +#include + +#include "DB_index.hh" +#include "TART_index.hh" +#include "TBox.hh" +#include "TCounter.hh" +#include "DB_params.hh" + +namespace tart_bench { + +typedef std::pair tart_key; +typedef uintptr_t tart_row; + +struct oi_value { + enum class NamedColumn : int { val = 0 }; + uintptr_t val; + + oi_value(uintptr_t v) { + val = v; + } +}; + +struct oi_key { + uint64_t key; + oi_key(uint64_t k) { + key = k; + } + bool operator==(const oi_key& other) const { + return (key == other.key); + } + bool operator!=(const oi_key& other) const { + return !(*this == other); + } + operator lcdf::Str() const { + return lcdf::Str((const char *)this, sizeof(*this)); + } +}; + +template +class tart_db { +public: + template + using TIndex = bench::ordered_index; + typedef TIndex table_type; + + table_type& table() { + return tbl_; + } + size_t size() const { + return size_; + } + size_t& size() { + return size_; + } +private: + size_t size_; + table_type tbl_; +}; + +template +void initialize_db(tart_db& db, size_t db_size) { + //db.table().thread_init(); + for (size_t i = 0; i < db_size; i++) + db.table().nontrans_put(oi_key(i), new oi_value{rand()}); + db.size() = db_size; +} + +template +class tart_runner { +public: + using RowAccess = bench::RowAccess; + void run_txn(size_t key) { + TRANSACTION_E { + bool success, found; + oi_value* value; + std::tie(success, found, std::ignore, value) = db.table().select_row(oi_key(key), RowAccess::None); + if (success) { + if (found) { + std::tie(success, found) = db.table().delete_row(oi_key(key)); + } else { + std::tie(success, found) = db.table().insert_row(oi_key(key), *Sto::tx_alloc()); + } + } + } RETRY_E(true); + } + + // returns the total number of transactions committed + size_t run() { + std::vector thrs; + std::vector txn_cnts((size_t)num_runners, 0); + for (int i = 0; i < num_runners; ++i) + thrs.emplace_back( + &tart_runner::runner_thread, this, i, std::ref(txn_cnts[i])); + for (auto& t : thrs) + t.join(); + size_t combined_txn_count = 0; + for (auto c : txn_cnts) + combined_txn_count += c; + return combined_txn_count; + } + + tart_runner(int nthreads, double time_limit, tart_db& database) + : num_runners(nthreads), tsc_elapse_limit(), db(database) { + using db_params::constants; + tsc_elapse_limit = (uint64_t)(time_limit * constants::processor_tsc_frequency * constants::billion); + } + +private: + void runner_thread(int runner_id, size_t& committed_txns) { + ::TThread::set_id(runner_id); + //db.table().thread_init(); + std::mt19937 gen(runner_id); + std::uniform_int_distribution dist(0, db.size() - 1); + + size_t thread_txn_count = 0; + auto tsc_begin = read_tsc(); + size_t key = dist(gen) % db.size(); + while (true) { + run_txn(key); + key = (key + 1) % db.size(); + ++thread_txn_count; + if ((thread_txn_count & 0xfful) == 0) { + if (read_tsc() - tsc_begin >= tsc_elapse_limit) + break; + } + } + + committed_txns = thread_txn_count; + } + + int num_runners; + uint64_t tsc_elapse_limit; + tart_db db; +}; + +}; diff --git a/benchmark/TART_index.hh b/benchmark/TART_index.hh new file mode 100644 index 00000000..b2ae80d7 --- /dev/null +++ b/benchmark/TART_index.hh @@ -0,0 +1,107 @@ +#pragma once +#include "config.h" +#include "compiler.hh" + +#include "Sto.hh" + +#include "string.hh" +#include "TART.hh" +#include + +#include +#include "VersionSelector.hh" + +namespace bench { +template +class tart_index : public TObject { +public: + typedef K key_type; + typedef V value_type; + + typedef std::tuple sel_return_type; + typedef std::tuple ins_return_type; + typedef std::tuple del_return_type; + typedef typename value_type::NamedColumn NamedColumn; + + uint64_t key_gen_; + uint64_t gen_key() { + return fetch_and_add(&key_gen_, 1); + } + + struct column_access_t { + int col_id; + bool update; + + column_access_t(NamedColumn column, bool for_update) + : col_id(static_cast(column)), update(for_update) {} + }; + + struct cell_access_t { + int cell_id; + bool update; + + cell_access_t(int cid, bool for_update) + : cell_id(cid), update(for_update) {} + }; + + tart_index() { + art = new TART(); + key_gen_ = 0; + // static_assert(std::is_base_of::value, "key must be std::string"); + } + + tart_index(size_t init_size) { + (void)init_size; + tart_index(); + } + + ~tart_index() {} + + void thread_init() {} + + // DB operations + sel_return_type select_row(const key_type& k, RowAccess acc) { + auto ret = (const value_type*) art->transGet(k); + if (!ret) { + return sel_return_type(true, false, 0, 0); + } + return sel_return_type(true, true, 0, ret); + } + ins_return_type insert_row(const key_type& k, value_type* v, bool overwrite = false) { + (void)overwrite; + art->transPut(k, (uintptr_t) v); + return ins_return_type(true, false); + } + // void update_row(const key_type& k, value_type& v) { + // lcdf::Str s = k; + // art->insert({s.data(), s.length()}, (uintptr_t) &v); + // } + void update_row(uintptr_t rid, value_type* v) { } + del_return_type delete_row(const key_type& k) { + art->transRemove(k); + return del_return_type(true, true); + } + + value_type nontrans_get(const key_type& k) { + return art->nonTransGet(k); + } + void nontrans_put(const key_type& k, const value_type& v) { + art->nonTransPut(k, (uintptr_t) &v); + } + + bool lock(TransItem& item, Transaction& txn) { + return art->lock(item, txn); + } + bool check(TransItem& item, Transaction& txn) { + return art->check(item, txn); + } + void install(TransItem& item, Transaction& txn) { + art->install(item, txn); + } + void unlock(TransItem& item) { + art->unlock(item); + } +private: + TART* art; +}; +} diff --git a/benchmark/TPCC_bench.hh b/benchmark/TPCC_bench.hh index 79ee6c87..49ba2c99 100644 --- a/benchmark/TPCC_bench.hh +++ b/benchmark/TPCC_bench.hh @@ -18,6 +18,7 @@ #include "DB_index.hh" #include "DB_params.hh" +#include "TART_index.hh" #define A_GEN_CUSTOMER_ID 1023 #define A_GEN_ITEM_ID 8191 @@ -116,6 +117,7 @@ public: template using OIndex = ordered_index; + // using OIndex = bench::tart_index; // partitioned according to warehouse id typedef std::vector wh_table_type; diff --git a/benchmark/Voter_bench.hh b/benchmark/Voter_bench.hh index 9fda3487..9a0a7286 100644 --- a/benchmark/Voter_bench.hh +++ b/benchmark/Voter_bench.hh @@ -6,6 +6,7 @@ #include "Voter_structs.hh" #include "DB_index.hh" #include "DB_params.hh" +#include "TART_index.hh" namespace voter { @@ -19,6 +20,7 @@ class voter_db { public: template using OIndex = ordered_index; + // using OIndex = bench::tart_index; typedef OIndex contestant_tbl_type; typedef OIndex areacodestate_tbl_type; diff --git a/benchmark/YCSB_bench.cc b/benchmark/YCSB_bench.cc index 33bda70b..c8af1410 100644 --- a/benchmark/YCSB_bench.cc +++ b/benchmark/YCSB_bench.cc @@ -195,6 +195,10 @@ class ycsb_access { workload_generation(runners, mode); std::cout << "Done." << std::endl; + pthread_t advancer; + pthread_create(&advancer, NULL, Transaction::epoch_advancer, NULL); + pthread_detach(advancer); + prof.start(profiler_mode); auto num_trans = run_benchmark(db, prof, runners, time_limit); prof.finish(num_trans); diff --git a/benchmark/YCSB_bench.hh b/benchmark/YCSB_bench.hh index eaaa8ad7..44f1b269 100644 --- a/benchmark/YCSB_bench.hh +++ b/benchmark/YCSB_bench.hh @@ -22,11 +22,13 @@ template class ycsb_db { public: template - using OIndex = ordered_index; + using OIndex = bench::ordered_index; + template + using AIndex = bench::art_index; template using UIndex = unordered_index; - typedef UIndex> ycsb_table_type; + typedef AIndex> ycsb_table_type; explicit ycsb_db() : ycsb_table_(ycsb_table_size) {} @@ -35,7 +37,7 @@ public: } void table_thread_init() { - //ycsb_table_.thread_init(); + ycsb_table_.thread_init(); } void prepopulate(); diff --git a/benchmark/YCSB_structs.hh b/benchmark/YCSB_structs.hh index 63ecb6d6..0908da21 100644 --- a/benchmark/YCSB_structs.hh +++ b/benchmark/YCSB_structs.hh @@ -45,6 +45,7 @@ struct ycsb_value { template class ycsb_value : TObject { public: + enum class NamedColumn : int { cols = 0 }; static constexpr size_t col_width = 10; static constexpr size_t num_cols = 10; typedef fix_string col_type; diff --git a/datatype/TART.hh b/datatype/TART.hh new file mode 100644 index 00000000..7f4a2c11 --- /dev/null +++ b/datatype/TART.hh @@ -0,0 +1,486 @@ +#pragma once +#include "config.h" +#include "compiler.hh" +#include +#include "Interface.hh" +#include "Transaction.hh" +#include "TWrapped.hh" +#include "simple_str.hh" +#include "print_value.hh" +#include "../ART/Tree.h" +#include "str.hh" + +class TART : public TObject { +public: + typedef std::pair TKey; + typedef uintptr_t TVal; + typedef ART_OLC::N Node; + + struct Element { + TKey key; + TVal val; + TVersion vers; + bool poisoned; + }; + + static void loadKey(TID tid, Key &key) { + Element* e = (Element*) tid; + auto ek = e->key; + auto len = ek.second; + key.setKeyLen(len); + if (len > 8) { + key.set(ek.first, len); + } else { + memcpy(&key[0], &ek.first, len); + } + } + + static void make_key(const char* tkey, Key &key, int len) { + key.setKeyLen(len); + if (len > 8) { + key.set(tkey, len); + } else { + memcpy(&key[0], tkey, len); + } + } + + // static void freeLeaf(TID t) { + // Element* e = (Element*) t; + // if (e->key.second > 8) { + // Transaction::rcu_free((char*) e->key.first); + // } + // Transaction::rcu_delete(e); + // } + + typedef std::tuple lookup_return_type; // (success, found, ret) + typedef std::tuple ins_return_type; // (success, found) + typedef std::tuple del_return_type; // (success, found) + + static constexpr TransItem::flags_type parent_bit = TransItem::user0_bit; + static constexpr TransItem::flags_type deleted_bit = TransItem::user0_bit<<1; + static constexpr TransItem::flags_type new_insert_bit = TransItem::user0_bit<<2; + static constexpr TransItem::flags_type self_upgrade_bit = TransItem::user0_bit<<3; + + TART() { + root_.setLoadKey(TART::loadKey); + } + + lookup_return_type lookup(lcdf::Str k) { + Key key; + make_key(k.data(), key, k.length()); + auto r = root_.lookup(key); + Element* e = (Element*) r.first; + if (e) { + auto item = Sto::item(this, e); + if (item.has_write()) { + return lookup_return_type(true, true, item.template write_value()); + } + e->vers.lock_exclusive(); + if (e->poisoned) { + e->vers.unlock_exclusive(); + return lookup_return_type(false, false, 0); + } + uintptr_t ret = e->val; + e->vers.unlock_exclusive(); + if (!item.observe(e->vers)) { + return lookup_return_type(false, false, 0); + } + // e->vers.observe_read(item); + return lookup_return_type(true, true, ret); + } + assert(r.second); + auto item = Sto::item(this, r.second); + item.add_flags(parent_bit); + if (!item.observe(r.second->vers)) { + return lookup_return_type(false, false, 0); + } + // r.second->vers.observe_read(item); + return lookup_return_type(true, false, 0); + } + lookup_return_type lookup(const char* k) { + return lookup({k, (int) strlen(k)+1}); + } + + TVal transGet(lcdf::Str k) { + auto r = lookup(k); + if (!std::get<0>(r)) { + throw Transaction::Abort(); + } + return std::get<2>(r); + } + TVal transGet(uint64_t k) { + return transGet({(const char*) &k, sizeof(uint64_t)}); + } + TVal transGet(const char* k) { + return transGet({k, (int) strlen(k)+1}); + } + + TVal nonTransGet(lcdf::Str k) { + Key key; + make_key(k.data(), key, k.length()); + auto r = root_.lookup(key); + Element* e = (Element*) r.first; + if (e) { + return e->val; + } + return 0; + } + TVal nonTransGet(const char* k) { + return nonTransGet({k, (int) strlen(k)+1}); + } + + ins_return_type insert(lcdf::Str k, TVal v, bool overwrite) { + Key art_key; + make_key(k.data(), art_key, k.length()); + + auto r = root_.insert(art_key, [k, v]{ + Element* e = new Element(); + if (k.length() > 8) { + char* p = (char*) malloc(k.length()); + memcpy(p, k.data(), k.length()); + e->key.first = p; + } else { + memcpy(&e->key.first, k.data(), k.length()); + } + e->val = v; + e->poisoned = true; + e->key.second = k.length(); + return (TID) e; + }); + + Element* e = (Element*) std::get<0>(r); + if (!std::get<1>(r)) { + auto item = Sto::item(this, e); + e->vers.lock_exclusive(); + if (!item.has_write() && e->poisoned) { + e->vers.unlock_exclusive(); + return ins_return_type(false, false); + } + e->vers.unlock_exclusive(); + item.observe(e->vers); + // e->vers.observe_read(item); + if (item.has_flag(deleted_bit)) { + item.add_write(v); + item.clear_flags(deleted_bit); + return ins_return_type(true, false); + } + if (overwrite) { + item.add_write(v); + } + return ins_return_type(true, true); + } + + auto item_el = Sto::item(this, e); + auto item_parent = Sto::item(this, std::get<1>(r)); + item_parent.add_write(v); + item_el.add_write(v); + item_el.add_flags(new_insert_bit); + item_parent.add_flags(parent_bit); + + Node* old = std::get<2>(r); + if (old) { + old->vers.lock_exclusive(); + old->valid = false; + old->vers.unlock_exclusive(); + } + return ins_return_type(true, false); + } + ins_return_type insert(const char* k, TVal v, bool overwrite) { + return insert({k, (int) strlen(k)+1}, v, overwrite); + } + + void transPut(lcdf::Str k, TVal v) { + Key art_key; + make_key(k.data(), art_key, k.length()); + + auto r = root_.insert(art_key, [k, v]{ + Element* e = new Element(); + if (k.length() > 8) { + char* p = (char*) malloc(k.length()); + memcpy(p, k.data(), k.length()); + e->key.first = p; + } else { + memcpy(&e->key.first, k.data(), k.length()); + } + e->val = v; + e->poisoned = true; + e->key.second = k.length(); + return (TID) e; + }); + + Element* e = (Element*) std::get<0>(r); + if (!std::get<1>(r)) { + auto item = Sto::item(this, e); + e->vers.lock_exclusive(); + if (!item.has_write() && e->poisoned) { + e->vers.unlock_exclusive(); + throw Transaction::Abort(); + } + e->vers.unlock_exclusive(); + item.add_write(v); + item.clear_flags(deleted_bit); + return; + } + + auto item_el = Sto::item(this, e); + auto item_parent = Sto::item(this, std::get<1>(r)); + item_parent.add_write(v); + item_el.add_write(v); + item_el.add_flags(new_insert_bit); + item_parent.add_flags(parent_bit); + + Node* old = std::get<2>(r); + if (old) { + old->vers.lock_exclusive(); + old->valid = false; + old->vers.unlock_exclusive(); + Sto::item(this, old).add_flags(self_upgrade_bit); + item_parent.observe(std::get<1>(r)->vers); + // std::get<1>(r)->vers.observe_read(item_parent); + } + } + void transPut(uint64_t k, TVal v) { + transPut({(const char*) &k, sizeof(uint64_t)}, v); + } + void transPut(const char* k, TVal v) { + transPut({k, (int) strlen(k)+1}, v); + } + + void nonTransPut(lcdf::Str k, TVal v) { + Key art_key; + make_key(k.data(), art_key, k.length()); + auto r = root_.insert(art_key, [k, v]{ + Element* e = new Element(); + if (k.length() > 8) { + char* p = (char*) malloc(k.length()); + memcpy(p, k.data(), k.length()); + e->key.first = p; + } else { + memcpy(&e->key.first, k.data(), k.length()); + } + e->val = v; + e->key.second = k.length(); + return (TID) e; + }); + Element* e = (Element*) std::get<0>(r); + if (!std::get<1>(r)) { + e->val = v; + return; + } + } + void nonTransPut(const char* k, TVal v) { + return nonTransPut({k, (int) strlen(k)+1}, v); + } + + del_return_type remove(lcdf::Str k) { + Key art_key; + make_key(k.data(), art_key, k.length()); + auto r = root_.lookup(art_key); + Element* e = (Element*) r.first; + if (e) { + auto item = Sto::item(this, e); + e->vers.lock_exclusive(); + if (!item.has_write() && e->poisoned) { + e->vers.unlock_exclusive(); + return del_return_type(false, false); + } + e->poisoned = true; + e->vers.unlock_exclusive(); + item.add_write(0); + item.add_flags(deleted_bit); + // e->vers.observe_read(item); + item.observe(e->vers); + return del_return_type(true, true); + } + + auto item_parent = Sto::item(this, r.second); + item_parent.add_flags(parent_bit); + // r.second->vers.observe_read(item_parent); + item_parent.observe(r.second->vers); + return del_return_type(true, false); + } + del_return_type remove(const char* k) { + return remove({k, (int) strlen(k)+1}); + } + + void transRemove(lcdf::Str k) { + Key art_key; + make_key(k.data(), art_key, k.length()); + auto r = root_.lookup(art_key); + Element* e = (Element*) r.first; + if (e) { + auto item = Sto::item(this, e); + e->vers.lock_exclusive(); + if (!item.has_write() && e->poisoned) { + e->vers.unlock_exclusive(); + throw Transaction::Abort(); + } + e->poisoned = true; + e->vers.unlock_exclusive(); + item.add_write(0); + item.add_flags(deleted_bit); + return; + } + + auto item_parent = Sto::item(this, r.second); + item_parent.add_flags(parent_bit); + // r.second->vers.observe_read(item_parent); + item_parent.observe(r.second->vers); + } + void transRemove(uint64_t k) { + transRemove({(const char*) &k, sizeof(uint64_t)}); + } + void transRemove(const char* k) { + transRemove({k, (int) strlen(k)+1}); + } + + bool lookupRange(TKey start, TKey end, TKey continueKey, TVal result[], + std::size_t resultSize, std::size_t &resultsFound) const { + Key art_start; + Key art_end; + Key art_continue; + + art_start.set(start.first, start.second); + art_end.set(end.first, end.second); + // art_continue.set(continueKey.first, continueKey.second); + + TID* art_result = new TID[resultSize]; + + bool success = root_.lookupRange(art_start, art_end, art_continue, art_result, resultSize, resultsFound, [this](Node* node){ + auto item = Sto::item(this, node); + // node->vers.observe_read(item); + item.observe(node->vers); + }); + int validResults = resultsFound; + + int j = 0; + for (uint64_t i = 0; i < resultsFound; i++) { + Element* e = (Element*) art_result[i]; + auto item = Sto::item(this, e); + if (item.has_flag(deleted_bit)) { + validResults--; + } else if (item.has_write()) { + result[j] = item.template write_value(); + j++; + } else if (e->poisoned) { + throw Transaction::Abort(); + } else { + item.observe(e->vers); + // e->vers.observe_read(item); + result[j] = e->val; + j++; + } + } + resultsFound = validResults; + + return success; + } + + bool lock(TransItem& item, Transaction& txn) override { + if (item.has_flag(parent_bit)) { + Node* parent = item.template key(); + return parent->vers.is_locked_here() || txn.try_lock(item, parent->vers); + } else { + Element* e = item.template key(); + bool locked = txn.try_lock(item, e->vers); + if (!locked) { + // printf("abort lock\n"); + return false; + } + if (e->poisoned && !(item.has_flag(new_insert_bit) || item.has_flag(deleted_bit))) { + // printf("abort lock 2\n"); + e->vers.cp_unlock(item); + return false; + } + return true; + } + } + bool check(TransItem& item, Transaction& txn) override { + if (item.has_flag(parent_bit)) { + Node* parent = item.template key(); + auto r = (item.has_flag(self_upgrade_bit) || parent->valid) && parent->vers.cp_check_version(txn, item); + // if (!r) printf("abort check\n"); + return r; + } else { + Element* e = item.template key(); + auto r = (!e->poisoned || item.has_flag(new_insert_bit) || item.has_flag(deleted_bit)) && e->vers.cp_check_version(txn, item); + // if (!r) printf("abort check 2\n"); + return r; + } + } + void install(TransItem& item, Transaction& txn) override { + if (item.has_flag(parent_bit)) { + Node* parent = item.template key(); + txn.set_version_unlock(parent->vers, item); + } else { + Element* e = item.template key(); + if (item.has_flag(deleted_bit)) { + Key art_key; + // art_key.set(e->key.first.c_str(), e->key.first.size()+1); + if (e->key.second > 8) { + make_key(e->key.first, art_key, e->key.second); + } else { + make_key((const char*) &e->key.first, art_key, e->key.second); + } + Node* old = root_.remove(art_key, (TID) e); + if (old) { + old->valid = false; + Transaction::rcu_delete(old); + } + if (e->key.second > 8) { + Transaction::rcu_free((char*) e->key.first); + } + Transaction::rcu_delete(e); + return; + } + e->poisoned = false; + e->val = item.template write_value(); + txn.set_version_unlock(e->vers, item); + } + } + void unlock(TransItem& item) override { + if (item.has_flag(parent_bit)) { + Node* parent = item.template key(); + if (parent->vers.is_locked_here()) parent->vers.cp_unlock(item); + } else { + Element* e = item.template key(); + e->vers.cp_unlock(item); + } + } + void cleanup(TransItem& item, bool committed) override { + if (committed || item.has_flag(parent_bit) || !item.has_write()) { + return; + } + Element* e = item.template key(); + if (item.has_flag(deleted_bit)) { + e->poisoned = false; + return; + } + if (e->poisoned && item.has_flag(new_insert_bit)) { + Key art_key; + if (e->key.second > 8) { + make_key(e->key.first, art_key, e->key.second); + } else { + make_key((const char*) &e->key.first, art_key, e->key.second); + } + Node* old = root_.remove(art_key, (TID) e); + if (old) { + old->valid = false; + Transaction::rcu_delete(old); + } + if (e->key.second > 8) { + Transaction::rcu_free((char*) e->key.first); + } + Transaction::rcu_delete(e); + } + } + void print(std::ostream& w, const TransItem& item) const override { + root_.print(); + } + + void print() const { + root_.print(); + } +protected: + ART_OLC::Tree root_; +}; diff --git a/datatype/TInt.hh b/datatype/TInt.hh new file mode 100644 index 00000000..669654f6 --- /dev/null +++ b/datatype/TInt.hh @@ -0,0 +1,138 @@ +#pragma once + +#include "Sto.hh" + +// template > +class TInt : public TObject { +public: + typedef typename TOpaqueWrapped::read_type read_type; + typedef typename TOpaqueWrapped::version_type version_type; + static constexpr TransItem::flags_type assigned_bit = TransItem::user0_bit; + + TInt() { + } + template + explicit TInt(Args&&... args) + : v_(std::forward(args)...) { + } + + std::pair read_nothrow() const { + auto item = Sto::item(this, 0); + + if (!item.has_flag(assigned_bit)) { + auto x = v_.read(item, vers_); + if (item.has_write()) { + auto delta = item.template write_value(); + x = {x.first, x.second + delta}; + // write(x.second); + } + return x; + } else { + if (item.has_write()) + return {true, item.template write_value()}; + else + return v_.read(item, vers_); + } + } + + read_type read() const { + auto result = read_nothrow(); + if (!result.first) + throw Transaction::Abort(); + else + return result.second; + } + + void write(const int& x) { + auto item = Sto::item(this, 0); + item.acquire_write(vers_, x); + item.add_flags(assigned_bit); + } + void write(int&& x) { + auto item = Sto::item(this, 0); + item.acquire_write(vers_, std::move(x)); + item.add_flags(assigned_bit); + } + template + void write(Args&&... args) { + auto item = Sto::item(this, 0); + item.template acquire_write(vers_, std::forward(args)...); + item.add_flags(assigned_bit); + } + + operator read_type() const { + return read(); + } + TInt& operator=(const int& x) { + write(x); + return *this; + } + TInt& operator=(int&& x) { + write(std::move(x)); + return *this; + } + //template + //TInt>& operator=(V&& x) { + // write(std::forward(x)); + // return *this; + //} + TInt& operator=(const TInt& x) { + write(x.read()); + return *this; + } + + void inc(int n) { + // write(this->read() + x); + auto item = Sto::item(this, 0); + if (item.has_read()) { + write(this->read() + n); + } else { + // write(item.template write_value() + n); + if (item.has_write()) { + item.acquire_write(vers_, item.template write_value() + n); + } else { + item.acquire_write(vers_, n); + } + } + } + + const int& nontrans_read() const { + return v_.access(); + } + int& nontrans_access() { + return v_.access(); + } + void nontrans_write(const int& x) { + v_.access() = x; + } + void nontrans_write(int&& x) { + v_.access() = std::move(x); + } + + // transactional methods + bool lock(TransItem& item, Transaction& txn) override { + return txn.try_lock(item, vers_); + } + bool check(TransItem& item, Transaction& txn) override { + return vers_.cp_check_version(txn, item); + } + void install(TransItem& item, Transaction& txn) override { + v_.write(std::move(item.template write_value())); + txn.set_version_unlock(vers_, item); + } + void unlock(TransItem& item) override { + vers_.cp_unlock(item); + } + void print(std::ostream& w, const TransItem& item) const override { + w << "{TInt<" << typeid(int).name() << "> " << (void*) this; + if (item.has_read()) + w << " R" << item.read_value(); + if (item.has_write()) + w << " =" << item.write_value(); + w << "}"; + } + +protected: + version_type vers_; + TOpaqueWrapped v_; +}; diff --git a/notes.txt b/notes.txt new file mode 100644 index 00000000..9de2feb6 --- /dev/null +++ b/notes.txt @@ -0,0 +1,8 @@ +things to do: + +version number on each node +install must change per node version number +only change big version in unlock if new node was created + +change lookup to return node as well as value +read must observe version in node diff --git a/sto-core/Transaction.hh b/sto-core/Transaction.hh index b7b6d8c5..ccbbff50 100644 --- a/sto-core/Transaction.hh +++ b/sto-core/Transaction.hh @@ -19,7 +19,7 @@ //#include #ifndef STO_PROFILE_COUNTERS -#define STO_PROFILE_COUNTERS 0 +#define STO_PROFILE_COUNTERS 1 #endif #ifndef STO_TSC_PROFILE #define STO_TSC_PROFILE 0 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 468f9940..baeaace7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,11 +3,15 @@ add_executable(unit-tflexarray unit-tflexarray.cc) add_executable(unit-swisstarray unit-swisstarray.cc) add_executable(unit-tarray unit-tarray.cc) add_executable(unit-tbox unit-tbox.cc) +add_executable(unit-tint unit-tint.cc) +add_executable(unit-tart unit-tart.cc) add_executable(unit-dboindex unit-dboindex.cc) target_link_libraries(unit-swisstarray sto dprint) target_link_libraries(unit-tflexarray sto dprint) target_link_libraries(unit-tbox sto dprint) +target_link_libraries(unit-tint sto dprint) +target_link_libraries(unit-tart sto dprint) target_link_libraries(unit-tarray sto dprint) target_link_libraries(concurrent sto rd clp dprint ${PLATFORM_LIBRARIES}) target_link_libraries(unit-dboindex sto dprint db_index masstree json) diff --git a/test/bench-masstree.cc b/test/bench-masstree.cc new file mode 100644 index 00000000..18c21355 --- /dev/null +++ b/test/bench-masstree.cc @@ -0,0 +1,213 @@ +#include +#include +#include +#include +#include + +#include + +#include "config.h" +#include "compiler.hh" + +#include "masstree.hh" +#include "kvthread.hh" +#include "masstree_tcursor.hh" +#include "masstree_insert.hh" +#include "masstree_print.hh" +#include "masstree_remove.hh" +#include "masstree_scan.hh" +#include "string.hh" + +int nthread = 1; +int rand_keys = 0; + +#define NVALS 10000000 + +uint64_t* keys; + +class MasstreeWrapper { +public: + struct table_params : public Masstree::nodeparams<15,15> { + typedef uint64_t value_type; + typedef Masstree::value_print value_print_type; + typedef threadinfo threadinfo_type; + }; + + typedef Masstree::Str Str; + typedef Masstree::basic_table table_type; + typedef Masstree::unlocked_tcursor unlocked_cursor_type; + typedef Masstree::tcursor cursor_type; + typedef Masstree::leaf leaf_type; + + typedef typename table_type::node_type node_type; + typedef typename unlocked_cursor_type::nodeversion_value_type nodeversion_value_type; + + static __thread typename table_params::threadinfo_type *ti; + + MasstreeWrapper() { + this->table_init(); + } + + void table_init() { + if (ti == nullptr) + ti = threadinfo::make(threadinfo::TI_MAIN, -1); + table_.initialize(*ti); + key_gen_ = 0; + } + + void keygen_reset() { + key_gen_ = 0; + } + + static void thread_init(int thread_id) { + if (ti == nullptr) + ti = threadinfo::make(threadinfo::TI_PROCESS, thread_id); + } + + void insert(int int_key) { + uint64_t key_buf; + Str key = make_key(int_key, key_buf); + + cursor_type lp(table_, key); + bool found = lp.find_insert(*ti); + // always_assert(!found, "keys should all be unique"); + + lp.value() = int_key; + + fence(); + lp.finish(!found, *ti); + } + + int lookup(int int_key) { + uint64_t key_buf; + Str key = make_key(int_key, key_buf); + + unlocked_cursor_type lp(table_, key); + bool found = lp.find_unlocked(*ti); + always_assert(found, "keys must all exist"); + return lp.value(); + } + + void remove(int int_key) { + uint64_t key_buf; + Str key = make_key(int_key, key_buf); + + cursor_type lp(table_, key); + bool found = lp.find_locked(*ti); + // always_assert(found, "keys must all exist"); + lp.finish(-1, *ti); + } + +private: + table_type table_; + uint64_t key_gen_; + + static inline Str make_key(uint64_t int_key, uint64_t& key_buf) { + key_buf = __bswap_64(int_key); + return Str((const char *)&key_buf, sizeof(key_buf)); + } +}; + +pthread_barrier_t barrier; +__thread typename MasstreeWrapper::table_params::threadinfo_type* MasstreeWrapper::ti = nullptr; + +volatile mrcu_epoch_type active_epoch = 1; +volatile uint64_t globalepoch = 1; +volatile bool recovering = false; + + +void insertKey(MasstreeWrapper* mt, int thread_id) { + mt->thread_init(thread_id); + + for (int i = thread_id*(NVALS/nthread); i < (thread_id+1)*NVALS/nthread; i++) { + mt->insert(keys[i]); + } +} + +void lookupKey(MasstreeWrapper* mt, int thread_id) { + mt->thread_init(thread_id); + + for (int i = thread_id*(NVALS/nthread); i < (thread_id+1)*NVALS/nthread; i++) { + int v = mt->lookup(keys[i]); + assert(v == (int) keys[i]); + } +} + +void removeKey(MasstreeWrapper* mt, int thread_id) { + mt->thread_init(thread_id); + + for (int i = thread_id*(NVALS/nthread); i < (thread_id+1)*NVALS/nthread; i++) { + mt->remove(keys[i]); + } +} + +int main(int argc, char *argv[]) { + if (argc > 1) { + nthread = atoi(argv[1]); + } + if (argc > 2) { + rand_keys = atoi(argv[2]); + } + + pthread_barrier_init(&barrier, nullptr, nthread); + auto mt = new MasstreeWrapper(); + + keys = new uint64_t[NVALS]; + std::mt19937 rng; + rng.seed(std::random_device()()); + std::uniform_int_distribution dist(0,(unsigned) -1); + + for (uint64_t i = 0; i < NVALS; i++) { + if (rand_keys) { + keys[i] = dist(rng); + } else { + keys[i] = i; + } + } + + { + auto starttime = std::chrono::system_clock::now(); + std::thread threads[nthread]; + for (int i = 0; i < nthread; i++) { + threads[i] = std::thread(insertKey, mt, i); + } + + for (int i = 0; i < nthread; i++) { + threads[i].join(); + } + auto duration = std::chrono::duration_cast( + std::chrono::system_clock::now() - starttime); + printf("insert,%d,%f\n", NVALS, (NVALS * 1.0) / duration.count()); + } + { + auto starttime = std::chrono::system_clock::now(); + std::thread threads[nthread]; + for (int i = 0; i < nthread; i++) { + threads[i] = std::thread(lookupKey, mt, i); + } + + for (int i = 0; i < nthread; i++) { + threads[i].join(); + } + auto duration = std::chrono::duration_cast( + std::chrono::system_clock::now() - starttime); + printf("lookup,%d,%f\n", NVALS, (NVALS * 1.0) / duration.count()); + } + { + auto starttime = std::chrono::system_clock::now(); + std::thread threads[nthread]; + for (int i = 0; i < nthread; i++) { + threads[i] = std::thread(removeKey, mt, i); + } + + for (int i = 0; i < nthread; i++) { + threads[i].join(); + } + auto duration = std::chrono::duration_cast( + std::chrono::system_clock::now() - starttime); + printf("erase,%d,%f\n", NVALS, (NVALS * 1.0) / duration.count()); + } + + pthread_barrier_destroy(&barrier); + return 0; +} diff --git a/test/bench-tart.cc b/test/bench-tart.cc new file mode 100644 index 00000000..bd2e1457 --- /dev/null +++ b/test/bench-tart.cc @@ -0,0 +1,325 @@ +#undef NDEBUG +#include +#include +#include +#include +#include +#include +#include "Sto.hh" +#include "TART.hh" +#include "Transaction.hh" +#include +#include +#include "DB_index.hh" +#include "DB_params.hh" + +class keyval_db { +public: + virtual void insert(uint64_t int_key, uintptr_t val) = 0; + virtual uintptr_t lookup(uint64_t int_key) = 0; + virtual void erase(uint64_t int_key) = 0; +}; + +class oindex_wrapper : public keyval_db { +public: + struct oi_value { + enum class NamedColumn : int { val = 0 }; + uintptr_t val; + }; + + struct oi_key { + uint64_t key; + oi_key(uint64_t k) { + key = k; + } + bool operator==(const oi_key& other) const { + return (key == other.key); + } + bool operator!=(const oi_key& other) const { + return !(*this == other); + } + operator lcdf::Str() const { + return lcdf::Str((const char *)this, sizeof(*this)); + } + }; + + typedef bench::ordered_index index_type; + index_type oi; + + oindex_wrapper() { + } + + void insert(uint64_t key, uintptr_t val) override { + TRANSACTION_E{ + oi.insert_row(oi_key(key), new oi_value{val}); + } RETRY_E(true); + } + + uintptr_t lookup(uint64_t key) override { + uintptr_t ret; + TRANSACTION_E{ + const oi_value* val; + std::tie(std::ignore, std::ignore, std::ignore, val) = oi.select_row(oi_key(key), bench::RowAccess::None); + if (!val) { + ret = 0; + } else { + ret = val->val; + } + } RETRY_E(true); + return ret; + } + + void erase(uint64_t key) override { + TRANSACTION_E{ + oi.delete_row(oi_key(key)); + } RETRY_E(true); + } +}; + +class tart_wrapper : public keyval_db { +public: + TART* t; + + tart_wrapper() { + t = new TART(); + } + + void insert(uint64_t int_key, uintptr_t val) override { + TRANSACTION_E { + t->transPut(int_key, val); + } RETRY_E(true); + } + + uintptr_t lookup(uint64_t int_key) override { + int ret; + TRANSACTION_E { + ret = t->transGet(int_key); + } RETRY_E(true); + return ret; + } + + void erase(uint64_t int_key) override { + TRANSACTION_E { + t->transRemove(int_key); + } RETRY_E(true); + } +}; + +class art_wrapper : public keyval_db { +public: + ART_OLC::Tree t; + + struct Element { + const char* key; + int len; + uintptr_t val; + }; + + static void loadKey(TID tid, Key &key) { + Element* e = (Element*) tid; + key.setKeyLen(e->len); + if (e->len > 8) { + key.set(e->key, e->len); + } else { + memcpy(&key[0], &e->key, e->len); + } + } + + static void make_key(const char* tkey, Key &key, int len) { + key.setKeyLen(len); + if (len > 8) { + key.set(tkey, len); + } else { + memcpy(&key[0], tkey, len); + } + } + + art_wrapper() { + t.setLoadKey(art_wrapper::loadKey); + } + + void insert(uint64_t int_key, uintptr_t val) override { + Key art_key; + make_key((const char*) &int_key, art_key, 8); + + t.insert(art_key, [int_key, val] { + Element* e = new Element(); + e->key = (const char*) int_key; + e->len = 8; + e->val = val; + return (TID) e; + }); + } + + uintptr_t lookup(uint64_t int_key) override { + Key art_key; + make_key((const char*) &int_key, art_key, 8); + auto r = t.lookup(art_key); + Element* e = (Element*) r.first; + if (e) { + return e->val; + } + return 0; + } + + void erase(uint64_t int_key) override { + Key art_key; + make_key((const char*) &int_key, art_key, 8); + auto r = t.lookup(art_key); + if (!r.first) { + return; + } + t.remove(art_key, (TID) r.first); + } +}; + +class masstree_wrapper : public keyval_db { +public: +}; + +void bench1(int nthread, int rand_keys, int nvals) { + keyval_db* art = new tart_wrapper(); + uint64_t* keys = new uint64_t[nvals]; + + std::mt19937 rng; + rng.seed(std::random_device()()); + std::uniform_int_distribution dist(0, (size_t) -1); + + for (int i = 0; i < nvals; i++) { + if (rand_keys) { + keys[i] = dist(rng); + } else { + keys[i] = i; + } + } + + // Build tree + { + auto starttime = std::chrono::system_clock::now(); + std::thread threads[nthread]; + for (int i = 0; i < nthread; i++) { + threads[i] = std::thread([&](int thread_id) { + TThread::set_id(thread_id); + oindex_wrapper::index_type::thread_init(); + + for (int i = thread_id*(nvals/nthread); i < (thread_id+1)*nvals/nthread; i++) { + art->insert(keys[i], keys[i]); + } + }, i); + } + + for (int i = 0; i < nthread; i++) { + threads[i].join(); + } + auto duration = std::chrono::duration_cast( + std::chrono::system_clock::now() - starttime); + printf("insert time %f\n", duration.count() / 1000000.0); + printf("insert,%d,%f\n", nvals, (nvals * 1.0) / duration.count()); + } + + { + auto starttime = std::chrono::system_clock::now(); + std::thread threads[nthread]; + for (int i = 0; i < nthread; i++) { + threads[i] = std::thread([&](int thread_id) { + TThread::set_id(thread_id); + oindex_wrapper::index_type::thread_init(); + + for (int i = thread_id*(nvals/nthread); i < (thread_id+1)*nvals/nthread; i++) { + int val = art->lookup(keys[i]); + assert((int) val == (int) keys[i]); + } + }, i); + } + + for (int i = 0; i < nthread; i++) { + threads[i].join(); + } + auto duration = std::chrono::duration_cast( + std::chrono::system_clock::now() - starttime); + printf("lookup,%d,%f\n", nvals, (nvals * 1.0) / duration.count()); + } + + { + auto starttime = std::chrono::system_clock::now(); + std::thread threads[nthread]; + for (int i = 0; i < nthread; i++) { + threads[i] = std::thread([&](int thread_id) { + TThread::set_id(thread_id); + oindex_wrapper::index_type::thread_init(); + + for (int i = thread_id*(nvals/nthread); i < (thread_id+1)*nvals/nthread; i++) { + art->erase(keys[i]); + } + }, i); + } + + for (int i = 0; i < nthread; i++) { + threads[i].join(); + } + auto duration = std::chrono::duration_cast( + std::chrono::system_clock::now() - starttime); + printf("erase,%d,%f\n", nvals, (nvals * 1.0) / duration.count()); + } +} + +void bench2(int nthread, int nvals) { + + keyval_db* art = new oindex_wrapper(); + uint64_t* keys = new uint64_t[nvals]; + + std::mt19937 rng; + rng.seed(std::random_device()()); + // std::uniform_int_distribution dist(0, (size_t) -1); + std::normal_distribution dist(nvals/2,nvals/4); + + for (int i = 0; i < nvals; i++) { + keys[i] = dist(rng); + art->insert(i, keys[i]); + } + printf("Prepopulation complete\n"); + + { + auto starttime = std::chrono::system_clock::now(); + std::thread threads[nthread]; + for (int i = 0; i < nthread; i++) { + threads[i] = std::thread([&](int thread_id) { + TThread::set_id(thread_id); + + for (int i = thread_id*(nvals/nthread); i < (thread_id+1)*nvals/nthread; i++) { + uint64_t k = dist(rng); + auto val = art->lookup(k); + if (k >= (uint64_t) nvals) { + assert(val == 0); + } else { + assert(val == keys[k]); + } + } + }, i); + } + + for (int i = 0; i < nthread; i++) { + threads[i].join(); + } + auto duration = std::chrono::duration_cast( + std::chrono::system_clock::now() - starttime); + printf("lookup,%d,%f\n", nvals, (nvals * 1.0) / duration.count()); + } +} + +int main(int argc, char *argv[]) { + int nthread = 1; + int rand_keys = 0; + int nvals = 10000; + if (argc > 1) { + nthread = atoi(argv[1]); + } + if (argc > 2) { + nvals = atoi(argv[2]); + } + if (argc > 3) { + rand_keys = atoi(argv[3]); + } + bench1(nthread, rand_keys, nvals); + // bench2(nthread, nvals); +} diff --git a/test/bench-tart2.cc b/test/bench-tart2.cc new file mode 100644 index 00000000..e4451616 --- /dev/null +++ b/test/bench-tart2.cc @@ -0,0 +1,90 @@ +#undef NDEBUG +#include +#include +#include +#include +#include +#include "Sto.hh" +#include "TART.hh" +#include "Transaction.hh" +#include + +#define NTHREAD 10 +#define NVALS 1000000 +#define KEYSIZE 5 + +TART art; +std::string keys[NVALS]; +unsigned vals[NVALS]; + +std::string rand_string() { + auto randchar = []() -> char + { + const char charset[] = "abcdefghijklmnopqrstuvwxyz"; + const size_t max_index = (sizeof(charset) - 1); + return charset[ rand() % max_index ]; + }; + std::string str(KEYSIZE, 0); + std::generate_n(str.begin(), KEYSIZE, randchar); + return str; +} + +int rand_int() { + return std::rand(); +} + +void doBenchInsert(int i) { + TThread::set_id(i); + for (int i = 0; i < NVALS/10; i++) { + auto keyI = rand_int() % NVALS; + auto valI = rand_int() % NVALS; + TRANSACTION_E { + art.insert(keys[keyI], vals[valI]); + } RETRY_E(true); + } +} + +void doBenchErase(int i) { + TThread::set_id(i); + for (int i = 0; i < NVALS/10; i++) { + auto eraseI = rand_int() % NVALS; + TRANSACTION_E { + art.erase(keys[eraseI]); + } RETRY_E(true); + TRANSACTION_E { + assert(art.lookup(keys[eraseI]) == 0); + } RETRY_E(true); + } +} + +int main() { + srand(time(NULL)); + art = TART(); + + for (int i = 0; i < NVALS; i++) { + keys[i] = rand_string(); + vals[i] = rand_int(); + printf("%d: key: %s, val: %d\n", i, keys[i].c_str(), vals[i]); + } + + std::thread threads[NTHREAD]; + + std::clock_t start; + start = std::clock(); + for (int i = 0; i < NTHREAD; i++) { + threads[i] = std::thread(doBenchInsert, i); + } + + for (int i = 0; i < NTHREAD; i++) { + threads[i].join(); + } + + for (int i = 0; i < NTHREAD; i++) { + threads[i] = std::thread(doBenchErase, i); + } + + for (int i = 0; i < NTHREAD; i++) { + threads[i].join(); + } + std::cout << "Time: " << (std::clock() - start) / (double)(CLOCKS_PER_SEC / 1000) << " ms" << std::endl; +} diff --git a/test/tart-bank.cc b/test/tart-bank.cc new file mode 100644 index 00000000..fc647695 --- /dev/null +++ b/test/tart-bank.cc @@ -0,0 +1,286 @@ +#undef NDEBUG +#include +#include +#include +#include +#include +#include +#include "Sto.hh" +#include "TART.hh" +#include "Transaction.hh" +#include +#include +#include +#include "DB_index.hh" +#include "DB_params.hh" + +enum Txns {Deposit = 1, View = 0, PayMulti = 3, Transfer = 2}; +std::atomic total_txns; + +std::atomic total_views; +std::atomic total_deposits; +std::atomic total_multis; +std::atomic total_transfers; + +class RandomSequenceOfUnique +{ +private: + unsigned int m_index; + unsigned int m_intermediateOffset; + + static unsigned int permuteQPR(unsigned int x) + { + static const unsigned int prime = 4294967291u; + if (x >= prime) + return x; // The 5 integers out of range are mapped to themselves. + unsigned int residue = ((unsigned long long) x * x) % prime; + return (x <= prime / 2) ? residue : prime - residue; + } + +public: + RandomSequenceOfUnique(unsigned int seedBase, unsigned int seedOffset) + { + m_index = permuteQPR(permuteQPR(seedBase) + 0x682f0161); + m_intermediateOffset = permuteQPR(permuteQPR(seedOffset) + 0x46790905); + } + + unsigned int next() + { + return permuteQPR((permuteQPR(m_index++) + m_intermediateOffset) ^ 0x5bf03635); + } +}; + + +class keyval_db { +public: + virtual void insert(uint64_t int_key, uintptr_t val) = 0; + virtual void update(uint64_t int_key, uintptr_t val) = 0; + virtual uintptr_t lookup(uint64_t int_key) = 0; + virtual void erase(uint64_t int_key) = 0; +}; + +class oindex_wrapper : public keyval_db { +public: + struct oi_value { + enum class NamedColumn : int { val = 0 }; + uintptr_t val; + }; + + struct oi_key { + uint64_t key; + oi_key(uint64_t k) { + key = k; + } + bool operator==(const oi_key& other) const { + return (key == other.key); + } + bool operator!=(const oi_key& other) const { + return !(*this == other); + } + operator lcdf::Str() const { + return lcdf::Str((const char *)this, sizeof(*this)); + } + }; + + typedef bench::ordered_index index_type; + index_type oi; + + oindex_wrapper() { + } + + void insert(uint64_t key, uintptr_t val) override { + bool success; + std::tie(success, std::ignore) = oi.insert_row(oi_key(key), new oi_value{val}); + if (!success) throw Transaction::Abort(); + } + + uintptr_t lookup(uint64_t key) override { + uintptr_t ret; + bool success; + const oi_value* val; + std::tie(success, std::ignore, std::ignore, val) = oi.select_row(oi_key(key), bench::RowAccess::None); + if (!success) throw Transaction::Abort(); + if (!val) { + ret = 0; + } else { + ret = val->val; + } + return ret; + } + + void update(uint64_t key, uintptr_t val) override { + bool success; + uintptr_t row; + const oi_value* value; + std::tie(success, std::ignore, row, value) = oi.select_row(oi_key(key), bench::RowAccess::UpdateValue); + if (!success) throw Transaction::Abort(); + auto new_oiv = Sto::tx_alloc(value); + new_oiv->val = val; + oi.update_row(row, new_oiv); + } + + void erase(uint64_t key) override { + oi.delete_row(oi_key(key)); + } +}; + +class tart_wrapper : public keyval_db { +public: + TART* t; + + tart_wrapper() { + t = new TART(); + } + + void insert(uint64_t int_key, uintptr_t val) override { + t->transPut(int_key, val); + } + + void update(uint64_t int_key, uintptr_t val) override { + t->transPut(int_key, val); + } + + uintptr_t lookup(uint64_t int_key) override { + int ret; + ret = t->transGet(int_key); + return ret; + } + + void erase(uint64_t int_key) override { + t->transRemove(int_key); + } +}; + +void bank_bench(int nthread, int npeople, int art, unsigned int seed) { + keyval_db* db; + if (art) { + db = new tart_wrapper(); + } else { + db = new oindex_wrapper(); + } + uint64_t* people = new uint64_t[npeople]; + + printf("Setup seed: %d\n", seed); + RandomSequenceOfUnique rsu(seed, seed + 1); + + for (int i = 0; i < npeople; i++) { + people[i] = rsu.next(); + TRANSACTION_E { + db->insert(people[i], 100000); + } RETRY_E(false); + } + + printf("Setup complete\n"); + + std::thread threads[nthread]; + auto starttime = std::chrono::system_clock::now(); + for (int i = 0; i < nthread; i++) { + threads[i] = std::thread([people, npeople, db](int thread_id) { + oindex_wrapper::index_type::thread_init(); + std::mt19937 rng; + rng.seed(thread_id); + std::uniform_int_distribution dist(0, 2); + + std::mt19937 acnt_rng; + acnt_rng.seed(thread_id+100); + std::uniform_int_distribution acnt_dist(0, npeople-1); + + uint64_t txns = 0; + + uint64_t views = 0; + uint64_t deposits = 0; + uint64_t transfers = 0; + uint64_t multis = 0; + TThread::set_id(thread_id); + time_t seconds = 20*1000000; // end loop after this time has elapsed + auto starttime = std::chrono::system_clock::now(); + auto duration = std::chrono::duration_cast( std::chrono::system_clock::now() - starttime); + + while (duration.count() < seconds) { + uint64_t account = people[acnt_dist(acnt_rng)]; + Txns op = static_cast(dist(rng)); + // Txns op = static_cast(1); + if (op == View) { + TRANSACTION_E { + db->lookup(account); + } RETRY_E(true); + views++; + } else if (op == Deposit) { + TRANSACTION_E { + uintptr_t balance = db->lookup(account); + balance += 1; + db->update(account, balance); + } RETRY_E(true); + deposits++; + } else if (op == Transfer) { + uint64_t account2 = people[acnt_dist(acnt_rng)]; + TRANSACTION_E { + uintptr_t balance1 = db->lookup(account); + uintptr_t balance2 = db->lookup(account2); + + balance2 += 1; + balance1 -= 1; + + db->update(account, balance1); + db->update(account2, balance2); + } RETRY_E(true); + transfers++; + } else if (op == PayMulti) { + TRANSACTION_E { + for (int i = 0; i < 100; i++) { + uintptr_t balance = db->lookup(account); + balance += 1; + db->insert(people[i], balance); + } + } RETRY_E(true); + multis++; + } else { + printf("invalid op\n"); + continue; + } + txns++; + duration = std::chrono::duration_cast( std::chrono::system_clock::now() - starttime); + } + total_txns += txns; + + total_views += views; + total_deposits += deposits; + total_transfers += transfers; + total_multis += multis; + }, i); + } + + for (int i = 0; i < nthread; i++) { + threads[i].join(); + } + auto duration = std::chrono::duration_cast( + std::chrono::system_clock::now() - starttime); + uint64_t txns = total_txns.load(); + printf("Total time: %f\n", duration.count() / 1000000.0); + printf("Transactions completed: %lu\n", txns); + printf("Throughput (txns/sec): %f\n", (double) txns / (duration.count() / 1000000.0)); + + txp_counters tc = Transaction::txp_counters_combined(); + printf("Aborts: %llu, Abort percentage: %.10f\n", tc.p(txp_total_aborts), tc.p(txp_total_aborts) / (double) (txns + tc.p(txp_total_aborts))); + printf("Views: %f, Deposits: %f, Transfers: %f, Multis: %f\n", total_views.load() / (double) txns, total_deposits.load() / (double) txns, total_transfers.load() / (double) txns, total_multis.load() / (double) txns); +} + +int main(int argc, char *argv[]) { + int nthread = 1; + int npeople = 10000000; + int art = 1; + unsigned int seed = (unsigned int) time(NULL); + if (argc > 1) { + nthread = atoi(argv[1]); + } + if (argc > 2) { + npeople = atoi(argv[2]); + } + if (argc > 3) { + art = atoi(argv[3]); + } + if (argc > 4) { + seed = atoi(argv[4]); + } + bank_bench(nthread, npeople, art, seed); +} diff --git a/test/unit-artindex.cc b/test/unit-artindex.cc new file mode 100644 index 00000000..8d945f99 --- /dev/null +++ b/test/unit-artindex.cc @@ -0,0 +1,604 @@ +#undef NDEBUG +#include +#include +#include +#include +#include +#include +#include "Sto.hh" +#include "TART.hh" +#include "Transaction.hh" +#include +#include +#include +#include "DB_index.hh" +#include "DB_params.hh" + +const char* absentkey1 = "hello"; +const char* absentkey2 = "1234"; +const char* absentkey2_1 = "1245"; +const char* absentkey2_2 = "1256"; +const char* absentkey2_3 = "1267"; +const char* absentkey2_4 = "1278"; +const char* absentkey2_5 = "1289"; + +const char* checkkey = "check1"; +const char* checkkey2 = "check2"; +const char* checkkey3 = "check3"; + +class keyval_db { +public: + virtual void insert(lcdf::Str int_key, uintptr_t val) = 0; + virtual void update(lcdf::Str int_key, uintptr_t val) = 0; + virtual uintptr_t lookup(lcdf::Str int_key) = 0; + virtual void erase(lcdf::Str int_key) = 0; +}; + +class masstree_wrapper : public keyval_db { +public: + struct oi_value { + enum class NamedColumn : int { val = 0 }; + uintptr_t val; + }; + + typedef bench::ordered_index index_type; + index_type oi; + + masstree_wrapper() { + } + + void insert(lcdf::Str key, uintptr_t val) override { + bool success; + std::tie(success, std::ignore) = oi.insert_row(key, new oi_value{val}); + if (!success) throw Transaction::Abort(); + } + + uintptr_t lookup(lcdf::Str key) override { + uintptr_t ret; + bool success; + const oi_value* val; + std::tie(success, std::ignore, std::ignore, val) = oi.select_row(key, bench::RowAccess::None); + if (!success) throw Transaction::Abort(); + if (!val) { + ret = 0; + } else { + ret = val->val; + } + return ret; + } + + void update(lcdf::Str key, uintptr_t val) override { + bool success; + uintptr_t row; + const oi_value* value; + std::tie(success, std::ignore, row, value) = oi.select_row(key, bench::RowAccess::UpdateValue); + if (!success) throw Transaction::Abort(); + auto new_oiv = Sto::tx_alloc(value); + new_oiv->val = val; + oi.update_row(row, new_oiv); + } + + void erase(lcdf::Str key) override { + oi.delete_row(key); + } +}; + +class tart_wrapper : public keyval_db { +public: + struct oi_value { + enum class NamedColumn : int { val = 0 }; + uintptr_t val; + }; + + typedef bench::art_index index_type; + index_type oi; + + tart_wrapper() { + } + + void insert(const char* key, uintptr_t val) { + insert({key, sizeof(key)}, val); + } + + void insert(lcdf::Str key, uintptr_t val) override { + bool success; + std::tie(success, std::ignore) = oi.insert_row(key, new oi_value{val}); + if (!success) throw Transaction::Abort(); + } + + uintptr_t lookup(const char* key) { + return lookup({key, sizeof(key)}); + } + + uintptr_t lookup(lcdf::Str key) override { + uintptr_t ret; + bool success; + bool found; + const oi_value* val; + std::tie(success, std::ignore, std::ignore, val) = oi.select_row(key, bench::RowAccess::None); + if (!success) throw Transaction::Abort(); + if (!val) { + ret = 0; + } else { + ret = val->val; + } + return ret; + } + + void update(const char* key, uintptr_t val) { + update({key, sizeof(key)}, val); + } + + void update(lcdf::Str key, uintptr_t val) override { + bool success; + uintptr_t row; + const oi_value* value; + std::tie(success, std::ignore, row, value) = oi.select_row(key, bench::RowAccess::UpdateValue); + if (!success) throw Transaction::Abort(); + auto new_oiv = Sto::tx_alloc(value); + new_oiv->val = val; + oi.update_row(row, new_oiv); + } + + void erase(const char* key) { + erase({key, sizeof(key)}); + } + + void erase(lcdf::Str key) override { + oi.delete_row(key); + } +}; + +typedef tart_wrapper wrapper_type; + +void testSimple() { + wrapper_type a; + + const char* key1 = "hello world"; + const char* key2 = "1234"; + { + TransactionGuard t; + a.insert(key1, 123); + a.insert(key2, 321); + } + + { + TransactionGuard t; + volatile auto x = a.lookup(key1); + volatile auto y = a.lookup(key2); + assert(x == 123); + assert(y == 321); + } + + // { + // TransactionGuard t; + // a.insert("foo", 1); + // a.insert("foobar", 2); + // } + // + // { + // TransactionGuard t; + // assert(a.lookup("foobar") == 2); + // } + + + printf("PASS: %s\n", __FUNCTION__); +} + +void testSimpleErase() { + wrapper_type a; + + const char* key1 = "hello world"; + const char* key2 = "1234"; + { + TransactionGuard t; + a.insert(key1, 123); + a.insert(key2, 321); + } + + { + TransactionGuard t; + volatile auto x = a.lookup(key1); + volatile auto y = a.lookup(key2); + a.insert(checkkey, 100); + assert(x == 123); + assert(y == 321); + } + + { + TransactionGuard t; + a.erase(key1); + volatile auto x = a.lookup(key1); + a.insert(checkkey, 100); + assert(x == 0); + } + + { + TransactionGuard t; + volatile auto x = a.lookup(key1); + assert(x == 0); + a.insert(key1, 567); + } + + { + TransactionGuard t; + volatile auto x = a.lookup(key1); + a.insert(checkkey, 100); + assert(x == 567); + } + + printf("PASS: %s\n", __FUNCTION__); +} + +void testAbsentErase() { + wrapper_type a; + + TestTransaction t1(0); + a.erase("foo"); + a.insert("bar", 1); + + TestTransaction t2(1); + a.insert("foo", 123); + assert(t2.try_commit()); + + assert(!t1.try_commit()); + printf("PASS: %s\n", __FUNCTION__); +} + +void multiWrite() { + wrapper_type aTART; + { + TransactionGuard t; + aTART.insert(absentkey2, 456); + } + + { + TransactionGuard t; + aTART.update(absentkey2, 123); + } + { + TransactionGuard t; + volatile auto x = aTART.lookup(absentkey2); + assert(x == 123); + } + printf("PASS: %s\n", __FUNCTION__); +} + +void testEmptyErase() { + wrapper_type a; + + const char* key1 = "hello world"; + + // deleting non-existent node + { + TransactionGuard t; + a.erase(key1); + volatile auto x = a.lookup(key1); + a.insert(checkkey, 100); + assert(x == 0); + } + + { + TransactionGuard t; + a.erase(key1); + volatile auto x = a.lookup(key1); + assert(x == 0); + a.insert(key1, 123); + a.erase(key1); + x = a.lookup(key1); + assert(x == 0); + } + + printf("PASS: %s\n", __FUNCTION__); +} + +void testUpgradeNode() { + wrapper_type a; + { + TransactionGuard t; + a.insert("1", 1); + a.insert("10", 1); + a.insert("11", 1); + a.insert("12", 1); + a.insert("15", 1); + } + + TestTransaction t0(0); + a.lookup("13"); + a.insert("14", 1); + + TestTransaction t1(1); + a.insert("13", 1); + + assert(t1.try_commit()); + assert(!t0.try_commit()); + + printf("PASS: %s\n", __FUNCTION__); +} + +void testUpgradeNode2() { + TART a; + { + TransactionGuard t; + a.transPut("1", 1); + a.transPut("10", 1); + // a.transPut("11", 1); + } + + TestTransaction t0(0); + a.transGet("13"); + a.transPut("14", 1); + a.transPut("15",1); + a.transPut("16", 1); + assert(t0.try_commit()); + + TestTransaction t1(1); + a.transGet("13"); + a.transPut("14", 1); + a.transPut("15",1); + a.transPut("16", 1); + + assert(t1.try_commit()); + printf("PASS: %s\n", __FUNCTION__); +} + + +void testUpgradeNode3() { + wrapper_type a; + { + TransactionGuard t; + a.insert("10", 1); + a.insert("11", 1); + } + + TestTransaction t0(0); + a.lookup("13"); + + TestTransaction t1(1); + a.insert("13", 1); + + t0.use(); + a.insert("14", 1); + + assert(t1.try_commit()); + assert(!t0.try_commit()); + + printf("PASS: %s\n", __FUNCTION__); +} + +void testUpgradeNode4() { + wrapper_type a; + { + TransactionGuard t; + a.insert("10", 1); + a.insert("11", 1); + a.insert("12", 1); + } + + TestTransaction t0(0); + a.lookup("14"); + + TestTransaction t1(1); + a.insert("13", 1); + a.erase("10"); + a.erase("11"); + a.erase("11"); + a.erase("13"); + a.insert("14", 1); + a.insert("15", 1); + a.insert("16", 1); + a.insert("17", 1); + + assert(t1.try_commit()); + + t0.use(); + a.erase("14"); + a.erase("15"); + a.erase("16"); + a.erase("17"); + + assert(!t0.try_commit()); + + printf("PASS: %s\n", __FUNCTION__); +} + +void testDowngradeNode() { + wrapper_type a; + + { + TransactionGuard t; + a.insert("1", 1); + a.insert("10", 1); + a.insert("11", 1); + a.insert("12", 1); + a.insert("13", 1); + a.insert("14", 1); + } + + TestTransaction t0(0); + a.lookup("15"); + a.insert("random", 1); + + TestTransaction t1(1); + a.lookup("10"); + a.insert("hummus", 1); + + TestTransaction t2(2); + a.erase("15"); + a.insert("linux", 1); + + TestTransaction t3(1); + a.erase("14"); + a.erase("13"); + a.erase("12"); + a.erase("11"); + assert(t3.try_commit()); + + assert(!t0.try_commit()); + assert(t1.try_commit()); + assert(!t2.try_commit()); + printf("PASS: %s\n", __FUNCTION__); +} + +void testSplitNode() { + TART a; + { + TransactionGuard t; + a.transPut("ab", 0); + a.transPut("1", 0); + } + + TestTransaction t0(0); + a.transGet("ad"); + a.transPut("12", 1); + + TestTransaction t1(1); + a.transGet("abc"); + a.transPut("13", 1); + + TestTransaction t2(2); + a.transPut("ad", 1); + a.transPut("abc", 1); + + assert(t2.try_commit()); + assert(!t0.try_commit()); + assert(!t1.try_commit()); + + printf("PASS: %s\n", __FUNCTION__); +} + +void testSplitNode2() { + TART a; + { + TransactionGuard t; + a.transPut("aaa", 0); + a.transPut("aab", 0); + } + + TestTransaction t0(0); + a.transGet("ab"); + a.transPut("1", 0); + + TestTransaction t1(1); + a.transPut("ab", 0); + + assert(t1.try_commit()); + assert(!t0.try_commit()); + + printf("PASS: %s\n", __FUNCTION__); +} + +void testEmptySplit() { + TART a; + { + TransactionGuard t; + a.transPut("aaa", 1); + a.transPut("aab", 1); + } + + TestTransaction t0(0); + a.transGet("aac"); + a.transPut("1", 0); + + TestTransaction t1(1); + a.transRemove("aaa"); + a.transRemove("aab"); + assert(t1.try_commit()); + + TestTransaction t2(2); + a.transPut("aac", 0); + assert(t2.try_commit()); + + assert(!t0.try_commit()); + + printf("PASS: %s\n", __FUNCTION__); +} + +int main(int argc, char *argv[]) { + testSimple(); + testSimpleErase(); + testAbsentErase(); + testEmptyErase(); + multiWrite(); + testUpgradeNode(); + testUpgradeNode2(); + testUpgradeNode3(); + testUpgradeNode4(); + testDowngradeNode(); + testSplitNode(); + testSplitNode2(); + testEmptySplit(); + + // keyval_db* db; + + // bool use_art = 1; + // if (argc > 1) { + // use_art = atoi(argv[1]); + // } + // + // uint64_t key1 = 1; + // uint64_t key2 = 2; + // { + // TransactionGuard t; + // db->insert(key1, 123); + // db->insert(key2, 321); + // } + // + // { + // TransactionGuard t; + // volatile auto x = db->lookup(key1); + // volatile auto y = db->lookup(key2); + // assert(x == 123); + // assert(y == 321); + // } + + // if (use_art) { + // printf("ART\n"); + // db = new tart_wrapper(); + // } else { + // printf("Masstree\n"); + // db = new masstree_wrapper(); + // } + + // uint64_t checkkey = 256; + // + // { + // TransactionGuard t; + // db->insert(key1, 123); + // db->insert(key2, 321); + // } + // + // { + // TransactionGuard t; + // volatile auto x = db->lookup(key1); + // volatile auto y = db->lookup(key2); + // db->insert(checkkey, 100); + // assert(x == 123); + // assert(y == 321); + // } + // + // { + // TransactionGuard t; + // db->erase(key1); + // volatile auto x = db->lookup(key1); + // db->insert(checkkey, 100); + // printf("%d\n", x); + // assert(x == 0); + // } + // + // { + // TransactionGuard t; + // volatile auto x = db->lookup(key1); + // assert(x == 0); + // db->insert(key1, 567); + // } + // + // { + // TransactionGuard t; + // volatile auto x = db->lookup(key1); + // db->insert(checkkey, 100); + // assert(x == 567); + // } + + printf("Tests pass\n"); +} diff --git a/test/unit-dboindex.cc b/test/unit-dboindex.cc index 65aca7e6..09948831 100644 --- a/test/unit-dboindex.cc +++ b/test/unit-dboindex.cc @@ -26,8 +26,8 @@ struct key_type { // using example_row from VersionSelector.hh -using CoarseIndex = bench::ordered_index; -using FineIndex = bench::ordered_index; +using CoarseIndex = bench::art_index; +using FineIndex = bench::art_index; void init_cindex(CoarseIndex& ci) { for (uint64_t i = 1; i <= 10; ++i) diff --git a/test/unit-tart.cc b/test/unit-tart.cc new file mode 100644 index 00000000..3fa73046 --- /dev/null +++ b/test/unit-tart.cc @@ -0,0 +1,1138 @@ +#undef NDEBUG +#include +#include +#include +#include +#include +#include "Sto.hh" +#include "TART.hh" +#include "Transaction.hh" +#include + +struct Element { + const char* key; + int len; + uintptr_t val; + TVersion vers; + bool poisoned; +}; + +#define GUARDED if (TransactionGuard tguard{}) + +// TODO these cause simple 2 to fail +// const char* absentkey1 = "he"; +// const char* absentkey2 = "hello"; + +const char* absentkey1 = "hello"; +const char* absentkey2 = "1234"; +const char* absentkey2_1 = "1245"; +const char* absentkey2_2 = "1256"; +const char* absentkey2_3 = "1267"; +const char* absentkey2_4 = "1278"; +const char* absentkey2_5 = "1289"; + +const char* checkkey = "check1"; +const char* checkkey2 = "check2"; +const char* checkkey3 = "check3"; + +void process_mem_usage(double& vm_usage, double& resident_set) { + vm_usage = 0.0; + resident_set = 0.0; + + // the two fields we want + unsigned long vsize; + long rss; + { + std::string ignore; + std::ifstream ifs("/proc/self/stat", std::ios_base::in); + ifs >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore + >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore >> ignore + >> ignore >> ignore >> vsize >> rss; + } + + long page_size_kb = sysconf(_SC_PAGE_SIZE) / 1024; // in case x86-64 is configured to use 2MB pages + vm_usage = vsize / 1024.0; + resident_set = rss * page_size_kb; +} + +void NoChecks() { + TART aTART; + { + TransactionGuard t; + aTART.transPut(absentkey1, 10); + } + { + TransactionGuard t; + aTART.transGet(absentkey1); + } + + { + TransactionGuard t; + aTART.transRemove(absentkey1); + } + + { + TransactionGuard t; + aTART.transPut(absentkey1, 10); + aTART.transGet(absentkey1); + aTART.transRemove(absentkey1); + } + // Insert check print statement, no check should occur +} + +void Checks() { + TART aTART; + { + TransactionGuard t; + aTART.transPut(absentkey1, 10); + } + printf("1. "); + { + TransactionGuard t; + auto x = aTART.transGet(absentkey1); + aTART.transPut(checkkey, 100); + if(x == 0) { + printf("wtf\n"); + } + } + printf("\n2."); + { + TransactionGuard t; + aTART.transGet(absentkey1); + aTART.transPut(absentkey2, 12); + } + printf("\n3."); + { + TransactionGuard t; + aTART.transGet(absentkey1); + aTART.transRemove(absentkey2); + } + printf("\n4."); + { + TransactionGuard t; + volatile auto x = aTART.transGet(absentkey1); + aTART.transPut(checkkey, 100); + + if (x == 0) { + printf("wtf\n"); + } + } + printf("\n"); + printf("PASS: %s\n", __FUNCTION__); + +} + +void testSimple() { + TART a; + + const char* key1 = "hello world"; + const char* key2 = "1234"; + { + TransactionGuard t; + a.transPut(key1, 123); + a.transPut(key2, 321); + } + + { + TransactionGuard t; + volatile auto x = a.transGet(key1); + volatile auto y = a.transGet(key2); + assert(x == 123); + assert(y == 321); + } + + { + TransactionGuard t; + a.transPut("foo", 1); + a.transPut("foobar", 2); + } + + { + TransactionGuard t; + assert(a.transGet("foobar") == 2); + } + + + printf("PASS: %s\n", __FUNCTION__); +} + +void testSimple2() { + TART aTART; + + { + TransactionGuard t; + aTART.transPut(absentkey1, 10); + aTART.transPut(absentkey2, 10); + } + + TestTransaction t1(0); + aTART.transGet(absentkey1); + aTART.transPut(absentkey2, 123); + + TestTransaction t2(0); + aTART.transPut(absentkey2, 456); + + assert(t2.try_commit()); + assert(t1.try_commit()); + + { + TransactionGuard t; + volatile auto x = aTART.transGet(absentkey2); + assert(x == 123); + } + printf("PASS: %s\n", __FUNCTION__); +} + +void testSimpleErase() { + TART a; + + const char* key1 = "hello world"; + const char* key2 = "1234"; + { + TransactionGuard t; + a.transPut(key1, 123); + a.transPut(key2, 321); + } + + { + TransactionGuard t; + volatile auto x = a.transGet(key1); + volatile auto y = a.transGet(key2); + a.transPut(checkkey, 100); + assert(x == 123); + assert(y == 321); + } + + { + TransactionGuard t; + a.transRemove(key1); + volatile auto x = a.transGet(key1); + a.transPut(checkkey, 100); + assert(x == 0); + } + + { + TransactionGuard t; + volatile auto x = a.transGet(key1); + assert(x == 0); + a.transPut(key1, 567); + } + + { + TransactionGuard t; + volatile auto x = a.transGet(key1); + a.transPut(checkkey, 100); + assert(x == 567); + } + + printf("PASS: %s\n", __FUNCTION__); +} + +void testEmptyErase() { + TART a; + + const char* key1 = "hello world"; + + // deleting non-existent node + { + TransactionGuard t; + a.transRemove(key1); + volatile auto x = a.transGet(key1); + a.transPut(checkkey, 100); + assert(x == 0); + } + + { + TransactionGuard t; + a.transRemove(key1); + volatile auto x = a.transGet(key1); + assert(x == 0); + a.transPut(key1, 123); + a.transRemove(key1); + x = a.transGet(key1); + assert(x == 0); + } + + printf("PASS: %s\n", __FUNCTION__); + +} + +void testAbsentErase() { + TART a; + + TestTransaction t1(0); + a.transRemove("foo"); + a.transPut("bar", 1); + + TestTransaction t2(1); + a.transPut("foo", 123); + assert(t2.try_commit()); + + assert(!t1.try_commit()); + printf("PASS: %s\n", __FUNCTION__); +} + +void multiWrite() { + TART aTART; + { + TransactionGuard t; + aTART.transPut(absentkey2, 456); + } + + { + TransactionGuard t; + aTART.transPut(absentkey2, 123); + } + { + TransactionGuard t; + volatile auto x = aTART.transGet(absentkey2); + assert(x == 123); + } + printf("PASS: %s\n", __FUNCTION__); +} + +void multiThreadWrites() { + TART aTART; + { + TransactionGuard t; + aTART.transPut(absentkey2, 456); + } + + TestTransaction t1(0); + aTART.transPut(absentkey2, 123); + + TestTransaction t2(0); + aTART.transPut(absentkey2, 456); + + assert(t1.try_commit()); + assert(t2.try_commit()); + + { + TransactionGuard t; + // printf("to transGet\n"); + volatile auto x = aTART.transGet(absentkey2); + // printf("looked\n"); + assert(x == 456); + } + printf("PASS: %s\n", __FUNCTION__); + +} + +void testReadDelete() { + TART aTART; + TestTransaction t0(0); + aTART.transPut(absentkey1, 10); + aTART.transPut(absentkey2, 10); + assert(t0.try_commit()); + + TestTransaction t1(0); + aTART.transGet(absentkey1); + aTART.transPut(absentkey2, 10); + + TestTransaction t2(0); + aTART.transRemove(absentkey1); + + assert(t2.try_commit()); + assert(!t1.try_commit()); + + { + TransactionGuard t; + volatile auto x = aTART.transGet(absentkey1); + volatile auto y = aTART.transGet(absentkey2); + assert(x == 0); + assert(y == 10); + } + + printf("PASS: %s\n", __FUNCTION__); +} + +void testReadWriteDelete() { + TART aTART; + TestTransaction t0(0); + aTART.transPut(absentkey1, 10); + aTART.transPut(absentkey2, 10); + assert(t0.try_commit()); + + TestTransaction t1(0); + aTART.transGet(absentkey1); + aTART.transPut(absentkey2, 123); + + TestTransaction t2(0); + aTART.transRemove(absentkey1); + + assert(t2.try_commit()); + assert(!t1.try_commit()); + + { + TransactionGuard t; + volatile auto x = aTART.transGet(absentkey1); + volatile auto y = aTART.transGet(absentkey2); + assert(x == 0); + assert(y == 10); + } + + printf("PASS: %s\n", __FUNCTION__); +} + +void testReadDeleteInsert() { + TART aTART; + TestTransaction t0(0); + aTART.transPut(absentkey1, 10); + aTART.transPut(absentkey2, 10); + assert(t0.try_commit()); + + TestTransaction t1(0); + aTART.transGet(absentkey1); + aTART.transPut(absentkey2, 123); + + TestTransaction t2(0); + aTART.transRemove(absentkey1); + assert(t2.try_commit()); + + TestTransaction t3(0); + aTART.transPut(absentkey1, 10); + assert(t3.try_commit()); + assert(!t1.try_commit()); + + { + TransactionGuard t; + volatile auto x = aTART.transGet(absentkey1); + volatile auto y = aTART.transGet(absentkey2); + assert(x == 10); + assert(y == 10); + } + + printf("PASS: %s\n", __FUNCTION__); +} + + +void testInsertDelete() { + TART aTART; + TestTransaction t0(0); + aTART.transPut(absentkey1, 10); + aTART.transPut(absentkey2, 10); + assert(t0.try_commit()); + + TestTransaction t1(0); + aTART.transPut(absentkey1, 123); + aTART.transPut(absentkey2, 456); + + TestTransaction t2(0); + aTART.transRemove(absentkey1); + assert(t2.try_commit()); + + assert(!t1.try_commit()); + + { + TransactionGuard t; + volatile auto x = aTART.transGet(absentkey1); + volatile auto y = aTART.transGet(absentkey2); + assert(x == 0); + assert(y == 10); + } + + printf("PASS: %s\n", __FUNCTION__); +} + +// test that reading poisoned val aborts +void testAbsent1_1() { + TART aTART; + + TestTransaction t1(0); + aTART.transGet(absentkey1); + // a new transPut + TestTransaction t2(0); + aTART.transPut(absentkey1, 456); + aTART.transPut(absentkey2, 123); + + t1.use(); + try { + aTART.transGet(absentkey2); + } catch (Transaction::Abort e) { + assert(t2.try_commit()); + { + TransactionGuard t; + volatile auto x = aTART.transGet(absentkey1); + assert(x == 456); + } + printf("PASS: %s\n", __FUNCTION__); + return; + } + assert(false); + + +} + +// test you can write to a key after absent reading it +void testAbsent1_2() { + TART aTART; + + TestTransaction t1(0); + aTART.transGet(absentkey1); + aTART.transPut(absentkey1, 123); + + // a new transPut + TestTransaction t2(0); + try { + aTART.transPut(absentkey1, 456); + } catch (Transaction::Abort e) {} + + assert(t1.try_commit()); + + { + TransactionGuard t; + volatile auto x = aTART.transGet(absentkey1); + assert(x == 123); + } + + printf("PASS: %s\n", __FUNCTION__); +} + +// test that absent read detects changes made by other threads +void testAbsent1_3() { + TART aTART; + + TestTransaction t1(0); + aTART.transGet(absentkey1); + aTART.transPut(absentkey2, 123); + + // a new transPut + TestTransaction t2(0); + aTART.transPut(absentkey1, 456); + + assert(t2.try_commit()); + + TestTransaction t3(0); + aTART.transRemove(absentkey1); + + assert(t3.try_commit()); + assert(!t1.try_commit()); + + { + TransactionGuard t; + volatile auto x = aTART.transGet(absentkey1); + assert(x == 0); + } + + printf("PASS: %s\n", __FUNCTION__); +} + +// +void testAbsent2_2() { + TART aTART; + + TestTransaction t1(0); + aTART.transGet(absentkey1); + aTART.transPut(absentkey1, 123); + aTART.transGet(absentkey2); + aTART.transPut(absentkey2, 123); + + // a new transPut + TestTransaction t2(0); + try { + aTART.transPut(absentkey1, 456); + } catch (Transaction::Abort e) {} + + assert(t1.try_commit()); + + { + TransactionGuard t; + volatile auto x = aTART.transGet(absentkey1); + assert(x == 123); + } + + printf("PASS: %s\n", __FUNCTION__); + +} + +void testAbsent3() { + TART aTART; + + TestTransaction t0(0); + aTART.transPut(absentkey2, 123); + aTART.transPut(absentkey2_1, 456); + + assert(t0.try_commit()); + + TestTransaction t1(0); + aTART.transGet(absentkey1); + aTART.transPut(absentkey2, 123); + + // an update + TestTransaction t2(0); + aTART.transPut(absentkey2_2, 456); + + assert(t2.try_commit()); + assert(t1.try_commit()); + + { + TransactionGuard t; + volatile auto x = aTART.transGet(absentkey1); + volatile auto y = aTART.transGet(absentkey2); + assert(y == 123); + assert(x == 0); + } + + printf("PASS: %s\n", __FUNCTION__); +} + +void testAbsent3_2() { + TART aTART; + + TestTransaction t0(0); + aTART.transPut(absentkey2, 123); + aTART.transPut(absentkey2_1, 123); + aTART.transPut(absentkey2_2, 123); + aTART.transPut(absentkey2_3, 123); + assert(t0.try_commit()); + + TestTransaction t1(0); + aTART.transGet(absentkey2_4); + aTART.transPut(absentkey2, 123); + + // an update + TestTransaction t2(0); + aTART.transPut(absentkey2_5, 456); + + assert(t2.try_commit()); + assert(!t1.try_commit()); + + { + TransactionGuard t; + volatile auto x = aTART.transGet(absentkey1); + volatile auto y = aTART.transGet(absentkey2); + assert(y == 123); + assert(x == 0); + } + + printf("PASS: %s\n", __FUNCTION__); +} + +void testABA1() { + TART aTART; + { + TransactionGuard t; + aTART.transPut(absentkey2, 456); + } + + TestTransaction t1(0); + aTART.transGet(absentkey2); + aTART.transPut(absentkey1, 123); + + TestTransaction t2(0); + aTART.transRemove(absentkey2); + assert(t2.try_commit()); + + TestTransaction t3(0); + aTART.transPut(absentkey2, 456); + + assert(t3.try_commit()); + assert(!t1.try_commit()); + + + { + TransactionGuard t; + volatile auto x = aTART.transGet(absentkey1); + volatile auto y = aTART.transGet(absentkey2); + assert(y == 456); + assert(x == 0); + } + + printf("PASS: %s\n", __FUNCTION__); + + // ABA1 should fail due to key2 value changing +} + +void testMultiRead() { + TART art; + { + TransactionGuard t; + art.transPut("hello", 4); + } + + TestTransaction t1(0); + volatile auto x = art.transGet("hello"); + + TestTransaction t2(1); + volatile auto y = art.transGet("hello"); + assert(t2.try_commit()); + + t1.use(); + assert(t1.try_commit()); + assert(x == y); + assert(x == 4); + printf("PASS: %s\n", __FUNCTION__); +} + +void testReadWrite() { + TART art; + { + TransactionGuard t; + art.transPut("hello", 4); + } + + TestTransaction t1(0); + art.transGet("hello"); + art.transPut("world", 1); + + TestTransaction t2(1); + art.transPut("hello", 6); + assert(t2.try_commit()); + assert(!t1.try_commit()); + + printf("PASS: %s\n", __FUNCTION__); +} + +void testPerNodeV() { + TART art; + { + TransactionGuard t; + art.transPut("x", 1); + art.transPut("y", 2); + art.transPut("z", 3); + } + + { + TransactionGuard t; + volatile auto x = art.transGet("x"); + volatile auto y = art.transGet("y"); + volatile auto z = art.transGet("z"); + assert(x == 1); + assert(y == 2); + assert(z == 3); + } + + TestTransaction t1(0); + art.transGet("x"); + art.transPut("z", 13); + + TestTransaction t2(1); + art.transPut("y", 12); + + assert(t2.try_commit()); + assert(t1.try_commit()); + + { + TransactionGuard t; + volatile auto x = art.transGet("x"); + volatile auto y = art.transGet("y"); + volatile auto z = art.transGet("z"); + assert(x == 1); + assert(y == 12); + assert(z == 13); + } + printf("PASS: %s\n", __FUNCTION__); +} + +void testLookupRange() { + TART art; + + TestTransaction t1(0); + uintptr_t* result = new uintptr_t[10]; + size_t resultsFound; + art.lookupRange({"a", 2}, {"z", 2}, {"", 0}, result, 10, resultsFound); + art.transPut("foo", 1); + + TestTransaction t2(1); + art.transPut("b", 1); + assert(t2.try_commit()); + + assert(!t1.try_commit()); + printf("PASS: %s\n", __FUNCTION__); +} + +void testLookupRangeSplit() { + TART art; + + { + TransactionGuard t; + art.transPut("rail", 1); + } + + TestTransaction t1(0); + uintptr_t* result = new uintptr_t[10]; + size_t resultsFound; + art.lookupRange({"a", 2}, {"z", 2}, {"", 0}, result, 10, resultsFound); + art.transPut("foo", 1); + + TestTransaction t2(1); + art.transPut("rain", 1); + assert(t2.try_commit()); + + assert(!t1.try_commit()); + printf("PASS: %s\n", __FUNCTION__); +} + +void testLookupRangeUpdate() { + TART art; + + { + TransactionGuard t; + art.transPut("rail", 1); + } + + TestTransaction t1(0); + uintptr_t* result = new uintptr_t[10]; + size_t resultsFound; + art.lookupRange({"a", 2}, {"z", 2}, {"", 0}, result, 10, resultsFound); + art.transPut("foo", 1); + + TestTransaction t2(1); + art.transPut("rail", 2); + assert(t2.try_commit()); + + assert(!t1.try_commit()); + printf("PASS: %s\n", __FUNCTION__); +} + +void testSplitNode() { + TART a; + { + TransactionGuard t; + a.transPut("ab", 0); + a.transPut("1", 0); + } + + TestTransaction t0(0); + a.transGet("ad"); + a.transPut("12", 1); + + TestTransaction t1(1); + a.transGet("abc"); + a.transPut("13", 1); + + TestTransaction t2(2); + a.transPut("ad", 1); + a.transPut("abc", 1); + + assert(t2.try_commit()); + assert(!t0.try_commit()); + assert(!t1.try_commit()); + + printf("PASS: %s\n", __FUNCTION__); +} + +void testSplitNode2() { + TART a; + { + TransactionGuard t; + a.transPut("aaa", 0); + a.transPut("aab", 0); + } + + TestTransaction t0(0); + a.transGet("ab"); + a.transPut("1", 0); + + TestTransaction t1(1); + a.transPut("ab", 0); + + assert(t1.try_commit()); + assert(!t0.try_commit()); + + printf("PASS: %s\n", __FUNCTION__); +} + +void testEmptySplit() { + TART a; + { + TransactionGuard t; + a.transPut("aaa", 1); + a.transPut("aab", 1); + } + + TestTransaction t0(0); + a.transGet("aac"); + a.transPut("1", 0); + + TestTransaction t1(1); + a.transRemove("aaa"); + a.transRemove("aab"); + assert(t1.try_commit()); + + TestTransaction t2(2); + a.transPut("aac", 0); + assert(t2.try_commit()); + + assert(!t0.try_commit()); + + printf("PASS: %s\n", __FUNCTION__); +} + +void testUpgradeNode2() { + TART a; + { + TransactionGuard t; + a.transPut("1", 1); + a.transPut("10", 1); + // a.transPut("11", 1); + } + + TestTransaction t0(0); + a.transGet("13"); + a.transPut("14", 1); + a.transPut("15",1); + a.transPut("16", 1); + assert(t0.try_commit()); + + TestTransaction t1(1); + a.transGet("13"); + a.transPut("14", 1); + a.transPut("15",1); + a.transPut("16", 1); + + assert(t1.try_commit()); + printf("PASS: %s\n", __FUNCTION__); +} + +void testUpgradeNode() { + TART a; + { + TransactionGuard t; + a.transPut("1", 1); + a.transPut("10", 1); + a.transPut("11", 1); + a.transPut("12", 1); + } + + TestTransaction t0(0); + a.transGet("13"); + a.transPut("14", 1); + + TestTransaction t1(1); + a.transPut("13", 1); + + assert(t1.try_commit()); + assert(!t0.try_commit()); + + printf("PASS: %s\n", __FUNCTION__); +} + +void testUpgradeNode3() { + TART a; + { + TransactionGuard t; + a.transPut("10", 1); + a.transPut("11", 1); + } + + TestTransaction t0(0); + a.transGet("13"); + + TestTransaction t1(1); + a.transPut("13", 1); + + t0.use(); + a.transPut("14", 1); + + assert(t1.try_commit()); + assert(!t0.try_commit()); + + printf("PASS: %s\n", __FUNCTION__); +} + +void testUpgradeNode4() { + TART a; + { + TransactionGuard t; + a.transPut("10", 1); + a.transPut("11", 1); + a.transPut("12", 1); + } + + TestTransaction t0(0); + a.transGet("14"); + + TestTransaction t1(1); + a.transPut("13", 1); + a.transRemove("10"); + a.transRemove("11"); + a.transRemove("11"); + a.transRemove("13"); + a.transPut("14", 1); + a.transPut("15", 1); + a.transPut("16", 1); + a.transPut("17", 1); + + assert(t1.try_commit()); + + t0.use(); + a.transRemove("14"); + a.transRemove("15"); + a.transRemove("16"); + a.transRemove("17"); + + assert(!t0.try_commit()); + + printf("PASS: %s\n", __FUNCTION__); +} + +void testDowngradeNode() { + TART a; + + { + TransactionGuard t; + a.transPut("1", 1); + a.transPut("10", 1); + a.transPut("11", 1); + a.transPut("12", 1); + a.transPut("13", 1); + a.transPut("14", 1); + } + + TestTransaction t0(0); + a.transGet("15"); + a.transPut("random", 1); + + TestTransaction t1(1); + a.transGet("10"); + a.transPut("hummus", 1); + + TestTransaction t2(2); + a.transRemove("15"); + a.transPut("linux", 1); + + TestTransaction t3(1); + a.transRemove("14"); + a.transRemove("13"); + a.transRemove("12"); + a.transRemove("11"); + assert(t3.try_commit()); + + assert(!t0.try_commit()); + assert(t1.try_commit()); + assert(!t2.try_commit()); + printf("PASS: %s\n", __FUNCTION__); +} + +void testDoubleRead() { + TART a; + + TestTransaction t0(0); + auto r = a.transGet(1); + + TestTransaction t1(1); + a.transPut(1, 50); + assert(t1.try_commit()); + + TestTransaction t2(2); + a.transPut(1, 100); + a.transPut(2, 50); + assert(t2.try_commit()); + + t0.use(); + try { + auto s = a.transGet(2); + assert(false); + } catch (Transaction::Abort e) { + } + + printf("PASS: %s\n", __FUNCTION__); +} + +void testRCU(TART* a) { + double vm_usage; double resident_set; + process_mem_usage(vm_usage, resident_set); + printf("Before transPut\n"); + printf("RSS: %f, VM: %f\n", resident_set, vm_usage); + + for (int i = 0; i < 1000000; i++) { + TRANSACTION_E { + a->transPut(i, i); + } RETRY_E(true); + } + + process_mem_usage(vm_usage, resident_set); + printf("After transPut\n"); + printf("RSS: %f, VM: %f\n", resident_set, vm_usage); + + for (int i = 0; i < 1000000; i++) { + TRANSACTION_E { + a->transRemove(i); + } RETRY_E(true); + } + + process_mem_usage(vm_usage, resident_set); + printf("After transRemove\n"); + printf("RSS: %f, VM: %f\n", resident_set, vm_usage); + + for (int i = 0; i < 1000000; i++) { + TRANSACTION_E { + a->transPut(i, i); + } RETRY_E(true); + } + + process_mem_usage(vm_usage, resident_set); + printf("After re-transPut\n"); + printf("RSS: %f, VM: %f\n", resident_set, vm_usage); +} + +int main() { + pthread_t advancer; + pthread_create(&advancer, NULL, Transaction::epoch_advancer, NULL); + pthread_detach(advancer); + + // TART* a = new TART(); + // + // uintptr_t* result = new uintptr_t[10]; + // size_t resultsFound; + // { + // TransactionGuard t; + // a->transPut("romane", 1); + // a->transPut("romanus", 2); + // a->transPut("romulus", 3); + // a->transPut("rubens", 4); + // a->transPut("ruber", 5); + // a->transPut("rubicon", 6); + // a->transPut("rubicundus", 7); + // + // a->transRemove("romanus"); + // + // bool success = a->lookupRange({"romane", 7}, {"ruber", 6}, {"", 0}, result, 10, resultsFound); + // printf("success: %d\n", success); + // for (int i = 0; i < resultsFound; i++) { + // printf("%d: %d\n", resultsFound, result[i]); + // } + // } + + // a->print(); + + testSimple(); + testSimple2(); + testSimpleErase(); + testEmptyErase(); + testAbsentErase(); + multiWrite(); + multiThreadWrites(); + testReadDelete(); // problem w/ lacking implementation of transRemove + testReadWriteDelete(); + testReadDeleteInsert(); + testAbsent1_1(); + testInsertDelete(); + testAbsent1_2(); + testAbsent1_3(); // ABA read transPut delete detection no longer exists + testAbsent2_2(); + testAbsent3(); + testAbsent3_2(); + testABA1(); // ABA doesn't work + testMultiRead(); + testReadWrite(); + testPerNodeV(); + testReadWrite(); + testLookupRange(); + testLookupRangeSplit(); + testLookupRangeUpdate(); + testSplitNode(); + testSplitNode2(); + testEmptySplit(); + testUpgradeNode(); + testUpgradeNode2(); + testUpgradeNode3(); + testUpgradeNode4(); + testDowngradeNode(); + testDoubleRead(); + + printf("TART tests pass\n"); + + return 0; +} diff --git a/test/unit-tint.cc b/test/unit-tint.cc new file mode 100644 index 00000000..4027bd50 --- /dev/null +++ b/test/unit-tint.cc @@ -0,0 +1,175 @@ +#undef NDEBUG +#include +#include +#include +#include +#include "Sto.hh" +#include "TInt.hh" +#include "Transaction.hh" +//XXX disabled string wrapper due to unknown compiler issue +//#include "StringWrapper.hh" + +#define GUARDED if (TransactionGuard tguard{}) + +void testSimpleInt() { + TInt f; + + { + TransactionGuard t; + f = 100; + } + + { + TransactionGuard t2; + int f_read = f; + assert(f_read == 100); + } + + printf("PASS: %s\n", __FUNCTION__); +} + +void testIncrement() { + TInt f; + + { + TransactionGuard t; + f = 100; + f.inc(1); + int f_read = f; + assert(f_read == 101); + f = 10; + assert(f == 10); + + f.inc(1); + f = f + 1; + assert(f == 12); + f.inc(1); + f.inc(1); + } + + { + TransactionGuard t2; + int f_read = f; + assert(f_read == 14); + } + + printf("PASS: %s\n", __FUNCTION__); +} + +void test1() { + TInt x; + + { + TransactionGuard t; + x = 0; + } + + { + TransactionGuard t; + int tmp = x; + x.inc(1); + assert(tmp == 0); + } + + { + TransactionGuard t; + assert(x == 1); + x = 0; + } + + { + TransactionGuard t; + x.inc(1); + int tmp = x; + assert(tmp == 1); + } + + { + TransactionGuard t; + assert(x == 1); + x = 0; + } + + { + TransactionGuard t; + x.inc(1); + } + + { + TransactionGuard t; + assert(x == 1); + x = 0; + } + + { + TransactionGuard t; + x = 5; + x.inc(1); + } + + { + TransactionGuard t; + assert(x == 6); + x = 0; + } + + { + TransactionGuard t; + x.inc(1); + x.inc(1); + x.inc(1); + } + + { + TransactionGuard t; + assert(x == 3); + x = 0; + } + + { + TransactionGuard t; + x = 5; + x.inc(1); + int tmp = x; + assert(tmp == 6); + } + + { + TransactionGuard t; + assert(x == 6); + x = 0; + } + + { + TransactionGuard t; + x.inc(1); + x = 5; + } + + { + TransactionGuard t; + assert(x == 5); + x = 0; + } + + { + TransactionGuard t; + x.inc(1); + x = 5; + x.inc(1); + } + + { + TransactionGuard t; + assert(x == 6); + } + + printf("PASS: %s\n", __FUNCTION__); +} + +int main() { + testSimpleInt(); + testIncrement(); + test1(); + return 0; +}