Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ name: Ubuntu
- master
- develop
- feature/**
- lru_miltiindex_prototype

env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
Expand Down
11 changes: 11 additions & 0 deletions cmake/install/userver-multi-index-lru-config.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
include_guard(GLOBAL)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi! Can you please add some documentation on the feature? The description says it is "from" boost - is it a copy-paste of implementation? Or maybe some adapted version of it? Or a wrapper?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll add some docs later. Prototype with proper support of CMake goes first.

I'm discussing the PR with the author in Telegram


if(userver_multiindex_lru_FOUND)
return()
endif()

find_package(userver REQUIRED COMPONENTS core)

find_package(Boost REQUIRED)

set(userver_multiindex_lru_FOUND TRUE)
5 changes: 5 additions & 0 deletions libraries/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ option(USERVER_FEATURE_EASY "Build easy HTTP server library" "${USERVER_LIB_ENAB
option(USERVER_FEATURE_S3API "Build S3 api client library" "${USERVER_LIB_ENABLED_DEFAULT}")
option(USERVER_FEATURE_GRPC_REFLECTION "Build grpc reflection library" "${USERVER_LIB_ENABLED_DEFAULT}")
option(USERVER_FEATURE_GRPC_PROTOVALIDATE "Build grpc protovalidate library" "OFF")
option(USERVER_FEATURE_MULTI_INDEX_LRU "Build multi index lru library" "ON")

if(USERVER_FEATURE_S3API)
add_subdirectory(s3api)
endif()

if (USERVER_FEATURE_MULTI_INDEX_LRU)
add_subdirectory(multi_index_lru)
endif()

if(USERVER_FEATURE_EASY)
if(NOT USERVER_FEATURE_POSTGRESQL)
message(FATAL_ERROR "'USERVER_FEATURE_EASY' requires 'USERVER_FEATURE_POSTGRESQL=ON'")
Expand Down
23 changes: 23 additions & 0 deletions libraries/multi_index_lru/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
project(userver-multi-index-lru CXX)

find_package(Boost REQUIRED)

userver_module(
multi-index-lru
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}"
UTEST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/*_test.cpp"
UBENCH_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/*_benchmark.cpp"
DEPENDS core
)

target_include_directories(userver-multi-index-lru
PUBLIC
${Boost_INCLUDE_DIRS}
"${CMAKE_CURRENT_SOURCE_DIR}/tests/"
)

_userver_directory_install(
COMPONENT multi-index-lru
FILES "${USERVER_ROOT_DIR}/cmake/modules/Findboost.cmake"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/userver/modules"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
#pragma once

#include <boost/intrusive/link_mode.hpp>
#include <boost/intrusive/list.hpp>
#include <boost/intrusive/list_hook.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/mem_fun.hpp>

USERVER_NAMESPACE_BEGIN

namespace multi_index_lru {
using namespace boost::multi_index;

namespace impl {
template<typename Value>
struct ValueWithHook
{
Value value;
mutable boost::intrusive::list_member_hook<> list_hook;
const ValueWithHook *GetPointerToSelf() const { return this; };

explicit ValueWithHook(const Value& val) : value(val) {}

explicit ValueWithHook(Value&& val) : value(std::move(val)) {}

ValueWithHook() = delete;
ValueWithHook(const ValueWithHook&) = delete;
ValueWithHook(ValueWithHook &&) = delete;

ValueWithHook &operator=(const ValueWithHook&) = delete;
ValueWithHook &operator=(ValueWithHook&&) = delete;

operator Value&() { return value; }
operator const Value&() const { return value; }

Value* operator->() { return &value; }
const Value* operator->() const { return &value; }

Value& get() { return value; }
const Value& get() const { return value; }

using boost_list = boost::intrusive::list<
ValueWithHook,
boost::intrusive::member_hook<
ValueWithHook,
boost::intrusive::list_member_hook<>,
&ValueWithHook::list_hook
>
>;

void push_back_to_list(boost_list &lst) const {
lst.push_back(const_cast<ValueWithHook&>(*this));
}

void splice_in_list(boost_list &lst) const {
lst.splice(lst.end(), lst, lst.iterator_to(const_cast<ValueWithHook&>(*this)));
}
};

struct internal_ptr_tag {};
} // namespace impl

template<
typename Value,
typename IndexSpecifierList,
typename Allocator = std::allocator<impl::ValueWithHook<Value>>
>
class LRUCacheContainer {
private:
using CacheItem = impl::ValueWithHook<Value>;
using List = boost::intrusive::list<
CacheItem,
boost::intrusive::member_hook<
CacheItem,
boost::intrusive::list_member_hook<>,
&CacheItem::list_hook
>
>;

using ExtendedIndexSpecifierList = typename boost::mpl::push_back<
IndexSpecifierList,
hashed_unique<
tag<impl::internal_ptr_tag>,
const_mem_fun<CacheItem, const CacheItem*, &CacheItem::GetPointerToSelf>
>
>::type;

using Container = multi_index_container<
CacheItem,
ExtendedIndexSpecifierList,
Allocator
>;

Container container;
size_t max_size;

List usage_list;

public:
using value_type = Value;
using cache_item_type = CacheItem;

LRUCacheContainer(size_t max_size) : max_size(max_size) {}

template<typename... Args>
bool emplace(Args&&... args) {
if (container.size() >= max_size) {
evict_lru();
}

auto result = container.emplace(std::forward<Args>(args)...);

auto &value = *result.first;
if (result.second) {
value.push_back_to_list(usage_list);
} else {
value.splice_in_list(usage_list);
}
return result.second;
}

bool insert(const Value& value) {
return emplace(value);
}

bool insert(Value&& value) {
return emplace(std::move(value));
}

template<typename Tag, typename Key>
typename Container::template index<Tag>::type::iterator find(const Key& key) {
auto& primary_index = container.template get<Tag>();
auto it = primary_index.find(key);

if (it != primary_index.end()) {
it->splice_in_list(usage_list);
}

return it;
}

template<typename Tag, typename Key>
bool contains(const Key& key) {
return this->template find<Tag, Key>(key) != container.template get<Tag>().end();
}

template<typename Tag, typename Key>
bool erase(const Key& key) {
auto& primary_index = container.template get<Tag>();
auto it = primary_index.find(key);
if (it != primary_index.end()) {
usage_list.erase(usage_list.iterator_to(*it));
}
return container.template get<Tag>().erase(key) > 0;
}

template<typename Tag>
auto& get() {
return container.template get<Tag>();
}

template<typename Tag>
const auto& get() const {
return container.template get<Tag>();
}

size_t size() const { return container.size(); }
bool empty() const { return container.empty(); }
size_t capacity() const { return max_size; }

void set_capacity(size_t new_capacity) {
max_size = new_capacity;
while (container.size() > max_size) {
evict_lru();
}
}

void clear() {
container.clear();
}

private:
void evict_lru() {
if (!usage_list.empty()) {
CacheItem *ptr_to_erase = &*usage_list.begin();
usage_list.erase(usage_list.begin());
container.template get<impl::internal_ptr_tag>().erase(ptr_to_erase);
}
}
};
} // namespace multi_index_lru

USERVER_NAMESPACE_END
9 changes: 9 additions & 0 deletions libraries/multi_index_lru/library.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
project-name: userver-lib-multi-index-lru

description: multi index lru cache

maintainers:
- Common components

libraries:
- userver-core
16 changes: 16 additions & 0 deletions libraries/multi_index_lru/src/main_benchmark.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "benchmarks/lru_basic_benchmarks.h"
#include "benchmarks/lru_google_benchmarks.h"

// #define LRU_CONTAINER_DEBUG__
#include <userver/multi_index_lru/lru_boost_list_container.h>

using namespace USERVER_NAMESPACE;

int main() {
benchmarks::simple_benchmark<multi_index_lru::LRUCacheContainer>("boost_list_output.txt");
benchmarks::google_benchmark<multi_index_lru::LRUCacheContainer>();

benchmarks::google_benchmark_init("google_output.txt");
benchmarks::google_benchmark_run();
return 0;
}
13 changes: 13 additions & 0 deletions libraries/multi_index_lru/src/main_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include "tests/lru_basic_tests.h"

// #define LRU_CONTAINER_DEBUG__
#include <userver/multi_index_lru/lru_boost_list_container.h>

using namespace USERVER_NAMESPACE;

int main() {
test_lru_users<multi_index_lru::LRUCacheContainer>();
test_lru_products<multi_index_lru::LRUCacheContainer>();
std::cout << "all tests success" << std::endl;
return 0;
}
3 changes: 3 additions & 0 deletions libraries/multi_index_lru/src/multi_index_lru.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include <userver/multi_index_lru/lru_boost_list_container.h>

namespace userver::multi_index_lru { const char* multi_index_lru_version = "1.0"; }
60 changes: 60 additions & 0 deletions libraries/multi_index_lru/tests/benchmarks/benchmarks_resourses.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#pragma once

#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <random>
#include <chrono>
#include <iomanip>

#include "../lru_container_concept.h"

USERVER_NAMESPACE_BEGIN

namespace benchmarks {

const std::vector<long long> CACHE_SIZES = {1000, 10000, 100000};
const size_t OPERATIONS_NUMBER = 100000;
const int MAX_ID_SIZE = 50000;

struct id_tag {};
struct email_tag {};
struct name_tag {};

struct User {
int id;
std::string email;
std::string name;

bool operator==(const User& other) const {
return id == other.id && email == other.email && name == other.name;
}
};

namespace generator {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<double> action_dist(0.0, 1.0);
std::uniform_int_distribution<int> id_dist(0, MAX_ID_SIZE);

User generate_user() {
std::string email = "email" + std::to_string(id_dist(gen));
std::string name = "name" + std::to_string(id_dist(gen));
return User{id_dist(gen), email, name};
}

int generate_id() {
return id_dist(gen);
}

std::string generate_name() {
return "name" + std::to_string(id_dist(gen));
}

std::string generate_email() {
return "email" + std::to_string(id_dist(gen));
}
} // generator
} // benchmarks
USERVER_NAMESPACE_END
Loading