memecache

A minimal, no pain, in-memory caching library for general use

View on GitHub

Memecache

A minimal, no pain, in-memory caching library for general use

This is minimal, thread-safe caching library, with the support for multiple cache eviction policies and can also extend custom cache eviction policies.

The currently supported cache eviction policies include:

An exhaustive list of cache algorithms can be found here - Wikipedia

Usage

To use this library, it is necessary to include the header containing the cache implementation (cache.hpp file) and the corresponding appropriate headers containing the required cache eviction policy as per the requirement. If a policy is not mentioned explicitly, then NoCachePolicy is be implemented whereby the replacement candidate for removal is chosen to be the first key that was added in the internal key_storage container.

Currently there are three cache eviction policies supported:

An example usage of the LRU policy:

#include "cache.hpp"
#include "lru_cache_policy.hpp"

// alias for easy class typing
template <typename Key, typename Value>
using lru_cache_t = typename caches::fixed_sized_cache<Key, Value, caches::LRUCachePolicy>;

void memecache(...) {
  constexpr std::size_t CACHE_SIZE = 256;
  lru_cache_t<char, int> cache(CACHE_SIZE);

  lru_cache.Put('M', 24);
  lru_cache.Put('P', 5);
  lru_cache.Put('B', 2001);

  std::cout << "Using LRU Eviction Policy\n"
            << "Value for key '"
            << "M"
            << "': " << lru_cache.Get('M') << '\n';
  std::cout << "Value for key '"
            << "P"
            << "': " << lru_cache.Get('P') << '\n';
  std::cout << "Value for key '"
            << "B"
            << "': " << lru_cache.Get('B') << '\n';
  /*
  Output:

    Using LRU Eviction Policy
    Value for key 'M': 24
    Value for key 'P': 5
    Value for key 'B': 2001

  */
}

An example usage of memecache demonstrating its thread-safety:

#include "include/cache.hpp"
#include "include/fifo_cache_policy.hpp"
#include "include/lifo_cache_policy.hpp"
#include <iostream>
#include <mutex>
#include <thread>

// mutex to synchronize access to std::cout
std::mutex cout_mutex;

// alias for easy class typing
template <typename Key, typename Value>
using fifo_cache_t = caches::fixed_sized_cache<Key, Value, caches::FIFOCachePolicy>;
template <typename Key, typename Value>
using lifo_cache_t = caches::fixed_sized_cache<Key, Value, caches::LIFOCachePolicy>;

void printLine() { std::cout << "==============================================================================\n"; }

void cache_operations_fifo(fifo_cache_t<std::string, int>& cache, const std::string& key, int value, bool insert)
{
    if (insert)
    {
        cache.Put(key, value);
    }
    else
    {
        try
        {
            // lock cout access
            std::lock_guard<std::mutex> lock(cout_mutex);
            printLine();
            std::cout << "Using FIFO Eviction Policy\n"
                      << "Value for key '" << key << "': " << cache.Get(key) << '\n';
        }
        catch (const std::range_error& e)
        {
            std::cerr << e.what() << '\n';
        }
    }
}

void cache_operations_lifo(lifo_cache_t<std::string, int>& cache, const std::string& key, int value, bool insert)
{
    if (insert)
    {
        cache.Put(key, value);
    }
    else
    {
        try
        {
            // lock cout access
            std::lock_guard<std::mutex> lock(cout_mutex);
            printLine();
            std::cout << "Using LIFO Eviction Policy\n"
                      << "Value for key '" << key << "': " << cache.Get(key) << '\n';
        }
        catch (const std::range_error& e)
        {
            std::cerr << e.what() << '\n';
        }
    }
}

int main()
{
    std::cout << "Hello Enterpret!\n";

    constexpr std::size_t CACHE_SIZE = 256;
    fifo_cache_t<std::string, int> fifo_cache(CACHE_SIZE);
    lifo_cache_t<std::string, int> lifo_cache(CACHE_SIZE);

    // creating multiple threads to perform cache operations concurrently
    std::thread t1(cache_operations_fifo, std::ref(fifo_cache), "Hello", 100, true);
    std::thread t2(cache_operations_fifo, std::ref(fifo_cache), "world", 6996, true);
    std::thread t3(cache_operations_fifo, std::ref(fifo_cache), "Hello", 0, false);
    std::thread t4(cache_operations_fifo, std::ref(fifo_cache), "world", 0, false);

    std::thread t5(cache_operations_lifo, std::ref(lifo_cache), "Hello", 200, true);
    std::thread t6(cache_operations_lifo, std::ref(lifo_cache), "world", 7997, true);
    std::thread t7(cache_operations_lifo, std::ref(lifo_cache), "Hello", 0, false);
    std::thread t8(cache_operations_lifo, std::ref(lifo_cache), "world", 0, false);



    // joining the threads and waiting for their completion
    t1.join();
    t2.join();
    t3.join();
    t4.join();

    t5.join();
    t6.join();
    t7.join();
    t8.join();

    return 0;
}


/*
Output

Hello Enterpret!
==============================================================================
Using LIFO Eviction Policy
Value for key 'Hello': 200
==============================================================================
Using LIFO Eviction Policy
Value for key 'world': 7997
==============================================================================
Using FIFO Eviction Policy
Value for key 'Hello': 100
==============================================================================
Using FIFO Eviction Policy
Value for key 'world': 6996
==============================================================================
*/

A more exhaustive usage and demonstration of the library is shown in the main.cpp and mainthread.cpp files. Run the ./main and ./mainthread executables after building the project for demonstration purpose.

Creating Custom Cache Eviction Policies

To implement a custom cache eviction or cache replacement policy, include the cache_policy.hpp header file containing the cache policy interface and subsequently override the Insert(...), Touch(...), Erase(...) and ReplacementCandidate(...) methods as per the requirements.

    /*
     * Cache policy abstract base class
     * Key - Type of a key a policy works with
     */
    template <typename Key> class ICachePolicy
    {
    public:
        virtual ~ICachePolicy() = default;

        /*
         * Handles element insertion in the cache
         * key - Key that should be used by the policy
         */
        virtual void Insert(const Key& key) = 0;

        /*
         * Handles request to the key-value in the cache
         * key - Key that should be used by the policy
         */
        virtual void Touch(const Key& key) = 0;

        /*
         * Handles deletion of key-value from the cache
         * key - Key that should be used by the policy
         */

        virtual void Erase(const Key& key) = 0;

        /*
         * Returns the key of the replacement candidate
         * Key - Replacement candidate's key according to selected eviction policy
         */
        virtual const Key& ReplacementCandidate() const = 0;
    };

Requirements

Cloning, building and running locally

    git clone git@github.com:sanam2405/memecache.git
    brew install cmake
    mkdir build
    cd build
    cmake ..

For building, use make if available

    make

Otherwise use the below command for building

    cmake --build .
    ./main
    ./mainthread

Built with ❤️ by Manas