Switch to a QueueMap implementation with less indirections (directly store elements instead of pointers)

This commit is contained in:
Sebastian Messmer 2015-04-27 01:22:39 +02:00
parent b60b119985
commit e177c6f45c
4 changed files with 190 additions and 31 deletions

View File

@ -27,7 +27,7 @@ Cache::~Cache() {
unique_ptr<Block> Cache::pop(const Key &key) { unique_ptr<Block> Cache::pop(const Key &key) {
lock_guard<mutex> lock(_mutex); lock_guard<mutex> lock(_mutex);
auto found = _cachedBlocks.pop(key); auto found = _cachedBlocks.pop(key);
if (found.get() == nullptr) { if (!found) {
return nullptr; return nullptr;
} }
auto block = found->releaseBlock(); auto block = found->releaseBlock();
@ -42,7 +42,7 @@ void Cache::push(unique_ptr<Block> block) {
assert(_cachedBlocks.size() == MAX_ENTRIES-1); assert(_cachedBlocks.size() == MAX_ENTRIES-1);
} }
Key key = block->key(); Key key = block->key();
_cachedBlocks.push(key, make_unique<CacheEntry>(std::move(block))); _cachedBlocks.push(key, std::move(block));
} }
void Cache::_popOldEntries() { void Cache::_popOldEntries() {

View File

@ -31,7 +31,6 @@ public:
private: private:
void _popOldEntries(); void _popOldEntries();
mutable std::mutex _mutex; mutable std::mutex _mutex;
QueueMap<Key, CacheEntry> _cachedBlocks; QueueMap<Key, CacheEntry> _cachedBlocks;
std::unique_ptr<PeriodicTask> _timeoutFlusher; std::unique_ptr<PeriodicTask> _timeoutFlusher;

View File

@ -5,6 +5,8 @@
#include <memory> #include <memory>
#include <unordered_map> #include <unordered_map>
#include <cassert> #include <cassert>
#include <boost/optional.hpp>
#include <messmer/cpp-utils/macros.h>
namespace blockstore { namespace blockstore {
namespace caching { namespace caching {
@ -14,35 +16,43 @@ namespace caching {
template<class Key, class Value> template<class Key, class Value>
class QueueMap { class QueueMap {
public: public:
QueueMap(): _entries(), _sentinel(nullptr, nullptr, &_sentinel, &_sentinel) { QueueMap(): _entries(), _sentinel(&_sentinel, &_sentinel) {
} }
virtual ~QueueMap() {} virtual ~QueueMap() {
for (auto &entry : _entries) {
void push(const Key &key, std::unique_ptr<Value> value) { entry.second.release();
auto newEntry = std::make_unique<Entry>(&key, std::move(value), _sentinel.prev, &_sentinel); }
_sentinel.prev->next = newEntry.get();
_sentinel.prev = newEntry.get();
auto insertResult = _entries.emplace(key, std::move(newEntry));
assert(insertResult.second == true);
} }
std::unique_ptr<Value> pop(const Key &key) { void push(const Key &key, Value value) {
auto newEntry = _entries.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(_sentinel.prev, &_sentinel));
assert(newEntry.second == true);
newEntry.first->second.init(&newEntry.first->first, std::move(value));
//The following is ok, because std::unordered_map never invalidates pointers to its entries
_sentinel.prev->next = &newEntry.first->second;
_sentinel.prev = &newEntry.first->second;
}
boost::optional<Value> pop(const Key &key) {
auto found = _entries.find(key); auto found = _entries.find(key);
if (found == _entries.end()) { if (found == _entries.end()) {
return nullptr; return boost::none;
} }
_removeFromQueue(found->second.get()); _removeFromQueue(found->second);
auto value = std::move(found->second->value); auto value = found->second.release();
_entries.erase(found); _entries.erase(found);
return value; return std::move(value);
} }
std::unique_ptr<Value> pop() { boost::optional<Value> pop() {
if(_sentinel.next == &_sentinel) {
return boost::none;
}
return pop(*_sentinel.next->key); return pop(*_sentinel.next->key);
} }
const Value &peek() { const Value &peek() {
return *_sentinel.next->value; return _sentinel.next->value();
} }
uint32_t size() { uint32_t size() {
@ -50,26 +60,39 @@ public:
} }
private: private:
struct Entry { class Entry {
Entry(const Key *key_, std::unique_ptr<Value> value_, Entry *prev_, Entry *next_): key(nullptr), value(std::move(value_)), prev(prev_), next(next_) { public:
if (key_ != nullptr) { Entry(Entry *prev_, Entry *next_): prev(prev_), next(next_), key(nullptr), _value() {
key = std::make_unique<Key>(*key_); }
} void init(const Key *key_, Value value_) {
key = key_;
new(_value) Value(std::move(value_));
}
Value release() {
Value value = std::move(*reinterpret_cast<Value*>(_value));
reinterpret_cast<Value*>(_value)->~Value();
return value;
}
const Value &value() {
return *reinterpret_cast<Value*>(_value);
} }
std::unique_ptr<Key> key;
std::unique_ptr<Value> value;
Entry *prev; Entry *prev;
Entry *next; Entry *next;
const Key *key;
private:
alignas(Value) char _value[sizeof(Value)];
DISALLOW_COPY_AND_ASSIGN(Entry);
}; };
void _removeFromQueue(Entry *entry) { void _removeFromQueue(const Entry &entry) {
entry->prev->next = entry->next; entry.prev->next = entry.next;
entry->next->prev = entry->prev; entry.next->prev = entry.prev;
} }
//TODO Double indirection unique_ptr<Entry> and Entry has unique_ptr<Value>. Necessary? std::unordered_map<Key, Entry> _entries;
std::unordered_map<Key, std::unique_ptr<Entry>> _entries;
Entry _sentinel; Entry _sentinel;
DISALLOW_COPY_AND_ASSIGN(QueueMap);
}; };
} }

View File

@ -0,0 +1,137 @@
#include <google/gtest/gtest.h>
#include <messmer/cpp-utils/macros.h>
#include "../../../implementations/caching/QueueMap.h"
using ::testing::Test;
using namespace blockstore::caching;
// This is a not-default-constructible Key type
class MinimalKeyType {
public:
static MinimalKeyType create() {
return MinimalKeyType();
}
bool operator==(const MinimalKeyType &rhs) const {
return true;
}
private:
MinimalKeyType() {
}
};
namespace std {
template <> struct hash<MinimalKeyType> {
size_t operator()(const MinimalKeyType &obj) const {
return 0;
}
};
}
// This is a not-default-constructible non-copyable but moveable Value type
class MinimalValueType {
public:
static MinimalValueType create() {
return MinimalValueType();
}
MinimalValueType(MinimalValueType &&rhs) = default;
private:
MinimalValueType() {
}
DISALLOW_COPY_AND_ASSIGN(MinimalValueType);
};
class QueueMapTest: public Test {
public:
QueueMap<int, int> map;
};
TEST_F(QueueMapTest, TypeConstraints) {
QueueMap<MinimalKeyType, MinimalValueType> obj;
//Call all functions to ensure they still work
obj.push(MinimalKeyType::create(), MinimalValueType::create());
obj.peek();
obj.pop(MinimalKeyType::create());
obj.push(MinimalKeyType::create(), MinimalValueType::create());
obj.pop();
obj.size();
}
TEST_F(QueueMapTest, Size_Empty) {
EXPECT_EQ(0, map.size());
}
TEST_F(QueueMapTest, Size_AfterPushingOne) {
map.push(2, 3);
EXPECT_EQ(1, map.size());
}
TEST_F(QueueMapTest, Size_AfterPushingTwo) {
map.push(2, 3);
map.push(3, 4);
EXPECT_EQ(2, map.size());
}
TEST_F(QueueMapTest, Size_AfterPushingTwoAndPoppingOldest) {
map.push(2, 3);
map.push(3, 4);
map.pop();
EXPECT_EQ(1, map.size());
}
TEST_F(QueueMapTest, Size_AfterPushingTwoAndPoppingFirst) {
map.push(2, 3);
map.push(3, 4);
map.pop(2);
EXPECT_EQ(1, map.size());
}
TEST_F(QueueMapTest, Size_AfterPushingTwoAndPoppingLast) {
map.push(2, 3);
map.push(3, 4);
map.pop(3);
EXPECT_EQ(1, map.size());
}
TEST_F(QueueMapTest, Size_AfterPushingOnePoppingOne) {
map.push(2, 3);
map.pop();
EXPECT_EQ(0, map.size());
}
TEST_F(QueueMapTest, Size_AfterPushingOnePoppingOnePerKey) {
map.push(2, 3);
map.pop(2);
EXPECT_EQ(0, map.size());
}
TEST_F(QueueMapTest, Size_AfterPushingOnePoppingOnePushingOne) {
map.push(2, 3);
map.pop();
map.push(3, 4);
EXPECT_EQ(1, map.size());
}
TEST_F(QueueMapTest, Size_AfterPushingOnePoppingOnePerKeyPushingOne) {
map.push(2, 3);
map.pop(2);
map.push(3, 4);
EXPECT_EQ(1, map.size());
}
TEST_F(QueueMapTest, Size_AfterPushingOnePoppingOnePushingSame) {
map.push(2, 3);
map.pop();
map.push(2, 3);
EXPECT_EQ(1, map.size());
}
TEST_F(QueueMapTest, Size_AfterPushingOnePoppingOnePerKeyPushingSame) {
map.push(2, 3);
map.pop(2);
map.push(2, 3);
EXPECT_EQ(1, map.size());
}
//TODO Pushing the same key twice
//TODO Popping from empty
//TODO Popping invalid key
//TODO Test that in all cases, destructors of Value are called correctly in QueueMap when [a] pop() [b] pop(key) [c] ~QueueMap()