Skip to content
Open
Show file tree
Hide file tree
Changes from 10 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
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)
21 changes: 21 additions & 0 deletions cmake/modules/Findboost.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
_userver_module_begin(
NAME
Boost
DEBIAN_NAMES
libboost-dev
FORMULA_NAMES
boost
PACMAN_NAMES
boost
PKG_CONFIG_NAMES
boost
)

_userver_module_find_include(NAMES boost/version.hpp)

_userver_module_end()

if(NOT TARGET Boost::boost AND Boost_FOUND)
add_library(Boost::boost INTERFACE)
target_include_directories(Boost::boost INTERFACE ${Boost_INCLUDE_DIRS})
endif()
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
32 changes: 32 additions & 0 deletions libraries/multi_index_lru/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
project(userver-multi-index-lru CXX)

find_package(Boost REQUIRED)


if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/multi_index_lru.cpp")
file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/src/multi_index_lru.cpp"
"// MultiIndex LRU header-only lib\n"
"// This file exists only to satisfy CMake target requirements\n"
"namespace userver::lru_boost_list { const char* multi_index_lru_version = \"1.0\"; }\n")
endif()

userver_module(
multi-index-lru
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}"
UTEST_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/tests/main.cpp"
DEPENDS core
)

target_include_directories(userver-multi-index-lru
PUBLIC ${Boost_INCLUDE_DIRS}
)

_userver_directory_install(
COMPONENT multi-index-lru
FILES "${USERVER_ROOT_DIR}/cmake/modules/Findboost.cmake"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/userver/modules"
)

# if(USERVER_FEATURE_UTEST)
add_subdirectory(tests)
# endif()
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
#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>

#include <list>
#include <functional>
#include <unordered_set>
#include <iostream>

USERVER_NAMESPACE_BEGIN

namespace lru_boost_list {
using namespace boost::multi_index;

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 {};

template<
typename Value,
typename IndexSpecifierList,
typename Allocator = std::allocator<ValueWithHook<Value>>
>
class LRUCacheContainer {
private:
using CacheItem = 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<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<internal_ptr_tag>().erase(ptr_to_erase);
}
}
};
}

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
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 @@
// MultiIndex LRU header-only lib
// This file exists only to satisfy CMake target requirements
namespace userver::lru_boost_list { const char* multi_index_lru_version = "1.0"; }
35 changes: 35 additions & 0 deletions libraries/multi_index_lru/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
project(multi-index-lru-tests)

find_package(Boost REQUIRED)
if(NOT TARGET benchmark::benchmark)
find_package(benchmark REQUIRED)
endif()

file(GLOB_RECURSE MULTIINDEX_LRU_LIB_TESTS_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/*.h
)

add_library(${PROJECT_NAME} STATIC ${MULTIINDEX_LRU_LIB_TESTS_SOURCES})
target_link_libraries(${PROJECT_NAME}
PUBLIC
userver::multi-index-lru
benchmark::benchmark
)
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20)
add_executable(multi-index-lru-test-runner
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
)

target_link_libraries(multi-index-lru-test-runner
PRIVATE
${PROJECT_NAME}
)

_userver_directory_install(
COMPONENT multi-index-lru
DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)

_userver_install_targets(COMPONENT multi-index-lru TARGETS ${PROJECT_NAME})
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
Loading