Skip to content
This repository was archived by the owner on Jul 25, 2022. It is now read-only.

Commit

Permalink
feat(lib): lru cache
Browse files Browse the repository at this point in the history
  • Loading branch information
Alan-Liang committed Jun 10, 2022
1 parent 478c001 commit ba0132b
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 18 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ else()
lib/datetime_test.cpp
lib/file/bptree_test.cpp
lib/hashmap_test.cpp
lib/lru-cache_test.cpp
lib/map_test.cpp
lib/result_test.cpp
lib/utility_test.cpp
Expand Down
27 changes: 9 additions & 18 deletions lib/file/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <fstream>

#include "hashmap.h"
#include "lru-cache.h"
#include "utility.h"
#include "exception.h"

Expand Down Expand Up @@ -46,22 +47,19 @@ class File {

/// read n bytes at index into buf.
auto get (void *buf, size_t index, size_t n) -> void {
if (index != -1 && cache_.count(index) > 0) {
memcpy(buf, cache_[index], n);
if (auto cached = cache_.get(index)) {
memcpy(buf, *cached, n);
return;
}
file_.seekg(offset_(index));
file_.read((char *) buf, n);
TICKET_ASSERT(file_.good());
if (index != -1) putCache_(buf, index, n);
// TODO(perf): memcmp overhead
cache_.upsert(index, buf, n);
}
/// write n bytes at index from buf.
auto set (const void *buf, size_t index, size_t n) -> void {
if (index != -1) {
// dirty check
if (cache_.count(index) > 0 && memcmp(buf, cache_[index], n) == 0) return;
putCache_(buf, index, n);
}
if (!cache_.upsert(index, buf, n)) return;
file_.seekp(offset_(index));
file_.write((const char *) buf, n);
TICKET_ASSERT(file_.good());
Expand All @@ -86,8 +84,7 @@ class File {
set(&meta, index, sizeof(meta));
Metadata newMeta(index, true);
set(&newMeta, -1, sizeof(newMeta));
if (cache_.count(index) > 0) delete[] cache_[index];
cache_.erase(cache_.find(index));
cache_.remove(index);
}

/// gets user-provided metadata.
Expand All @@ -103,7 +100,6 @@ class File {

/// clears the cache.
auto clearCache () -> void {
for (const auto &[ _, ptr ] : cache_) delete[] ptr;
cache_.clear();
}

Expand Down Expand Up @@ -163,13 +159,8 @@ class File {
return (index + 1) * szChunk;
}
std::fstream file_;
HashMap<size_t, char *> cache_;
auto putCache_ (const void *buf, size_t index, size_t n) -> void {
char *cache = new char[n];
memcpy(cache, buf, n);
if (cache_.count(index) > 0) delete[] cache_[index];
cache_[index] = cache;
}
constexpr static int kSzCache_ = 1024;
LruCache<size_t, kSzCache_> cache_;
};

/**
Expand Down
126 changes: 126 additions & 0 deletions lib/lru-cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#ifndef TICKET_LIB_LRU_CACHE_H_
#define TICKET_LIB_LRU_CACHE_H_

#include <cstring>

#include "hashmap.h"
#include "map.h"
#include "optional.h"
#include "utility.h"

namespace ticket {

/// A fixed-size cache with a least recently used policy.
template <typename Key, int kSize>
class LruCache {
private:
struct WeightedKey;
public:
~LruCache () {
clear();
}
/// tries to obtain the value at the designated key.
auto get (const Key &key) -> Optional<void *> {
auto it = storage_.find(key);
if (it == storage_.end()) return unit;
touch_(it);
return it->second.value;
}
/**
* @brief upserts an cache entry.
* @returns true if the cache state has changed.
*
* performs an insert if the key is not in the cache, or
* an update if the key is in the cache.
*/
auto upsert (const Key &key, const void *buf, int length)
-> bool {
auto it = storage_.find(key);
if (it == storage_.end()) {
// the key is not in the cache; insert.

// is there enough space for a new entry?
TICKET_ASSERT(index_.size() <= kSize);
TICKET_ASSERT(index_.size() == storage_.size());
if (index_.size() == kSize) {
// not enough space; clean the lru entry.
auto willDelete = index_.begin();
auto &key = willDelete->second;

auto willDeleteStorage = storage_.find(key);
auto value = willDeleteStorage->second.value;
delete[] value;

storage_.erase(willDeleteStorage);
index_.erase(willDelete);
}
TICKET_ASSERT(index_.size() < kSize);
TICKET_ASSERT(index_.size() == storage_.size());

// okay, we must have enough space here.
++currentTime_;
index_[currentTime_] = key;
storage_[key] =
{ currentTime_, (char *) allocate_(buf, length) };

// the cache has changed.
return true;
} // if (it == storage_.end())

// the key is in the cache. check, then update if needed
touch_(it);
auto &value = it->second.value;
if (memcmp(buf, value, length) == 0) {
// content is identical, no update needed.
return false;
}
// content is different, update needed.
delete[] value;
value = (char *) allocate_(buf, length);
return true;
}

/// removes the key from the cache.
auto remove (const Key &key) -> void {
auto it = storage_.find(key);
if (it == storage_.end()) return;
delete[] it->second.value;
index_.erase(index_.find(it->second.accessTime));
storage_.erase(it);
}

/// clears the cache.
auto clear () -> void {
for (auto &pair : storage_) delete[] pair.second.value;
index_.clear();
storage_.clear();
}
private:
static_assert(kSize >= 2);
struct WeightedValue {
int accessTime;
char *value;
};
int currentTime_ = 0;
Map<int, Key> index_;
HashMap<Key, WeightedValue> storage_;

template <typename Iterator>
auto touch_ (Iterator it) -> void {
const auto &key = it->first;
auto &value = it->second;
index_.erase(index_.find(value.accessTime));
value.accessTime = ++currentTime_;
index_[value.accessTime] = key;
}

auto allocate_ (const void *buf, int length) -> void * {
char *copy = new char[length];
memcpy(copy, buf, length);
return copy;
}
};

} // namespace ticket

#endif // TICKET_LIB_LRU_CACHE_H_
34 changes: 34 additions & 0 deletions lib/lru-cache_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include "lru-cache.h"

#include <assert.h>
#include <string.h>

template <typename Key, int kSize>
using Cache = ticket::LruCache<Key, kSize>;

auto main () -> int {
Cache<int, 4> c;
assert(!c.get(0));
char tmp[10];
strncpy(tmp, "hello", sizeof(tmp));
assert(c.upsert(0, tmp, 6));
assert(!c.upsert(0, tmp, 6));
assert(!c.upsert(0, tmp, 6));
assert(c.upsert(1, tmp, 6));
assert(c.upsert(2, tmp, 6));
assert(strncmp((char *) *c.get(0), "hello", 6) == 0);
strncpy(tmp, "world", sizeof(tmp));
assert(strncmp((char *) *c.get(0), "hello", 6) == 0);
assert(c.upsert(0, tmp, 6));
assert(strncmp((char *) *c.get(0), "world", 6) == 0);
assert(strncmp((char *) *c.get(1), "hello", 6) == 0);
assert(!c.upsert(0, tmp, 6));
assert(c.upsert(3, tmp, 6));
assert(c.upsert(4, tmp, 6));
assert(!c.get(2));
c.remove(4);
assert(!c.get(4));
assert(c.upsert(5, tmp, 6));
assert(c.get(0) && c.get(1) && c.get(3));
return 0;
}

0 comments on commit ba0132b

Please sign in to comment.