From b6d6656ccabe4a65b18706fb13495b20ef4e6566 Mon Sep 17 00:00:00 2001 From: "Vinogradov, Sergei" Date: Tue, 13 Sep 2022 18:38:33 -0400 Subject: [PATCH 1/4] Implement NUMA binding support for SysVShmSegment --- cachelib/allocator/CacheAllocator-inl.h | 21 +++++--- cachelib/allocator/CacheAllocator.h | 1 + cachelib/allocator/CacheAllocatorConfig.h | 4 +- cachelib/allocator/MemoryTierCacheConfig.h | 17 +++++-- cachelib/shm/CMakeLists.txt | 1 + cachelib/shm/ShmCommon.h | 58 ++++++++++++++++++++++ cachelib/shm/SysVShmSegment.cpp | 25 ++++++++++ cachelib/shm/SysVShmSegment.h | 1 + contrib/prerequisites-centos8.sh | 3 +- contrib/prerequisites-debian10.sh | 3 +- contrib/prerequisites-ubuntu18.sh | 3 +- 11 files changed, 123 insertions(+), 14 deletions(-) diff --git a/cachelib/allocator/CacheAllocator-inl.h b/cachelib/allocator/CacheAllocator-inl.h index d14bcfa789..baa8d93d19 100644 --- a/cachelib/allocator/CacheAllocator-inl.h +++ b/cachelib/allocator/CacheAllocator-inl.h @@ -105,15 +105,25 @@ CacheAllocator::~CacheAllocator() { } template -std::unique_ptr -CacheAllocator::createNewMemoryAllocator() { +ShmSegmentOpts CacheAllocator::createShmCacheOpts() { ShmSegmentOpts opts; opts.alignment = sizeof(Slab); + auto memoryTierConfigs = config_.getMemoryTierConfigs(); + // TODO: we support single tier so far + XDCHECK_EQ(memoryTierConfigs.size(), 1ul); + opts.memBindNumaNodes = memoryTierConfigs[0].getMemBind(); + + return opts; +} + +template +std::unique_ptr +CacheAllocator::createNewMemoryAllocator() { return std::make_unique( getAllocatorConfig(config_), shmManager_ ->createShm(detail::kShmCacheName, config_.size, - config_.slabMemoryBaseAddr, opts) + config_.slabMemoryBaseAddr, createShmCacheOpts()) .addr, config_.size); } @@ -121,12 +131,11 @@ CacheAllocator::createNewMemoryAllocator() { template std::unique_ptr CacheAllocator::restoreMemoryAllocator() { - ShmSegmentOpts opts; - opts.alignment = sizeof(Slab); return std::make_unique( deserializer_->deserialize(), shmManager_ - ->attachShm(detail::kShmCacheName, config_.slabMemoryBaseAddr, opts) + ->attachShm(detail::kShmCacheName, config_.slabMemoryBaseAddr, + createShmCacheOpts()) .addr, config_.size, config_.disableFullCoredump); diff --git a/cachelib/allocator/CacheAllocator.h b/cachelib/allocator/CacheAllocator.h index 612f6d2185..c5819d734c 100644 --- a/cachelib/allocator/CacheAllocator.h +++ b/cachelib/allocator/CacheAllocator.h @@ -1868,6 +1868,7 @@ class CacheAllocator : public CacheBase { std::unique_ptr& worker, std::chrono::seconds timeout = std::chrono::seconds{0}); + ShmSegmentOpts createShmCacheOpts(); std::unique_ptr createNewMemoryAllocator(); std::unique_ptr restoreMemoryAllocator(); std::unique_ptr restoreCCacheManager(); diff --git a/cachelib/allocator/CacheAllocatorConfig.h b/cachelib/allocator/CacheAllocatorConfig.h index a92c1da3c5..ec44ff8467 100644 --- a/cachelib/allocator/CacheAllocatorConfig.h +++ b/cachelib/allocator/CacheAllocatorConfig.h @@ -210,7 +210,7 @@ class CacheAllocatorConfig { CacheAllocatorConfig& configureMemoryTiers(const MemoryTierConfigs& configs); // Return reference to MemoryTierCacheConfigs. - const MemoryTierConfigs& getMemoryTierConfigs(); + const MemoryTierConfigs& getMemoryTierConfigs() const noexcept; // This turns on a background worker that periodically scans through the // access container and look for expired items and remove them. @@ -877,7 +877,7 @@ CacheAllocatorConfig& CacheAllocatorConfig::configureMemoryTiers( template const typename CacheAllocatorConfig::MemoryTierConfigs& -CacheAllocatorConfig::getMemoryTierConfigs() { +CacheAllocatorConfig::getMemoryTierConfigs() const noexcept { return memoryTierConfigs; } diff --git a/cachelib/allocator/MemoryTierCacheConfig.h b/cachelib/allocator/MemoryTierCacheConfig.h index 5d3c9e3921..ae033ce5b5 100644 --- a/cachelib/allocator/MemoryTierCacheConfig.h +++ b/cachelib/allocator/MemoryTierCacheConfig.h @@ -16,8 +16,6 @@ #pragma once -#include - #include "cachelib/shm/ShmCommon.h" namespace facebook { @@ -43,6 +41,16 @@ class MemoryTierCacheConfig { size_t getRatio() const noexcept { return ratio; } + // Allocate memory only from specified NUMA nodes + MemoryTierCacheConfig& setMemBind(const NumaBitMask& _numaNodes) { + numaNodes = _numaNodes; + return *this; + } + + const NumaBitMask& getMemBind() const noexcept { + return numaNodes; + } + size_t calculateTierSize(size_t totalCacheSize, size_t partitionNum) { // TODO: Call this method when tiers are enabled in allocator // to calculate tier sizes in bytes. @@ -59,6 +67,7 @@ class MemoryTierCacheConfig { return getRatio() * (totalCacheSize / partitionNum); } + private: // Ratio is a number of parts of the total cache size to be allocated for this // tier. E.g. if X is a total cache size, Yi are ratios specified for memory // tiers, and Y is the sum of all Yi, then size of the i-th tier @@ -66,7 +75,9 @@ class MemoryTierCacheConfig { // tier is a half of the total cache size, set both tiers' ratios to 1. size_t ratio{1}; - private: + // Numa node(s) to bind the tier + NumaBitMask numaNodes; + // TODO: introduce a container for tier settings when adding support for // file-mapped memory MemoryTierCacheConfig() = default; diff --git a/cachelib/shm/CMakeLists.txt b/cachelib/shm/CMakeLists.txt index 2d6f5d4f37..cbc437dcb2 100644 --- a/cachelib/shm/CMakeLists.txt +++ b/cachelib/shm/CMakeLists.txt @@ -24,6 +24,7 @@ add_library (cachelib_shm add_dependencies(cachelib_shm thrift_generated_files) target_link_libraries(cachelib_shm PUBLIC cachelib_common + numa ) install(TARGETS cachelib_shm diff --git a/cachelib/shm/ShmCommon.h b/cachelib/shm/ShmCommon.h index 2b6964777d..009b569aa7 100644 --- a/cachelib/shm/ShmCommon.h +++ b/cachelib/shm/ShmCommon.h @@ -20,6 +20,9 @@ #include #include +#include +#include + #include #pragma GCC diagnostic push @@ -70,10 +73,65 @@ enum PageSizeT { ONE_GB, }; +class NumaBitMask { +public: + using native_bitmask_type = struct bitmask*; + + NumaBitMask() { + nodesMask = numa_allocate_nodemask(); + } + + NumaBitMask(const NumaBitMask& other) { + nodesMask = numa_allocate_nodemask(); + copy_bitmask_to_bitmask(other.nodesMask, nodesMask); + } + + NumaBitMask(NumaBitMask&& other) { + nodesMask = other.nodesMask; + other.nodesMask = nullptr; + } + + NumaBitMask(const std::string& str) { + nodesMask = numa_parse_nodestring_all(str.c_str()); + } + + ~NumaBitMask() { + if (nodesMask) { + numa_bitmask_free(nodesMask); + } + } + + constexpr NumaBitMask& operator=(const NumaBitMask& other) { + if (this != &other) { + if (!nodesMask) { + nodesMask = numa_allocate_nodemask(); + } + copy_bitmask_to_bitmask(other.nodesMask, nodesMask); + } + return *this; + } + + native_bitmask_type getNativeBitmask() const noexcept { + return nodesMask; + } + + NumaBitMask& setBit(unsigned int n) { + numa_bitmask_setbit(nodesMask, n); + return *this; + } + + bool empty() const noexcept{ + return numa_bitmask_equal(numa_no_nodes_ptr, nodesMask) == 1; + } +protected: + native_bitmask_type nodesMask = nullptr; +}; + struct ShmSegmentOpts { PageSizeT pageSize{PageSizeT::NORMAL}; bool readOnly{false}; size_t alignment{1}; // alignment for mapping. + NumaBitMask memBindNumaNodes; explicit ShmSegmentOpts(PageSizeT p) : pageSize(p) {} explicit ShmSegmentOpts(PageSizeT p, bool ro) : pageSize(p), readOnly(ro) {} diff --git a/cachelib/shm/SysVShmSegment.cpp b/cachelib/shm/SysVShmSegment.cpp index a4042402e7..49da9e9327 100644 --- a/cachelib/shm/SysVShmSegment.cpp +++ b/cachelib/shm/SysVShmSegment.cpp @@ -16,10 +16,15 @@ #include "cachelib/shm/SysVShmSegment.h" +#include + #include #include +#include #include #include +#include +#include #include "cachelib/common/Utils.h" @@ -184,6 +189,18 @@ void shmCtlImpl(int shmid, int cmd, shmid_ds* buf) { } } +void mbindImpl(void *addr, unsigned long len, int mode, + const NumaBitMask& memBindNumaNodes, + unsigned int flags) { + auto nodesMask = memBindNumaNodes.getNativeBitmask(); + + long ret = mbind(addr, len, mode, nodesMask->maskp, nodesMask->size, flags); + if (ret != 0) { + util::throwSystemError(errno, folly::sformat("mbind() failed: {}", + std::strerror(errno))); + } +} + } // namespace detail void ensureSizeforHugePage(size_t size) { @@ -270,11 +287,19 @@ void* SysVShmSegment::mapAddress(void* addr) const { void* retAddr = detail::shmAttachImpl(shmid_, addr, shmFlags); XDCHECK(retAddr == addr || addr == nullptr); + memBind(retAddr); return retAddr; } void SysVShmSegment::unMap(void* addr) const { detail::shmDtImpl(addr); } +void SysVShmSegment::memBind(void* addr) const { + if (opts_.memBindNumaNodes.empty()) { + return; + } + detail::mbindImpl(addr, getSize(), MPOL_BIND, opts_.memBindNumaNodes, 0); +} + void SysVShmSegment::markForRemoval() { if (isMarkedForRemoval()) { return; diff --git a/cachelib/shm/SysVShmSegment.h b/cachelib/shm/SysVShmSegment.h index 4428b2f7c7..f97096de5c 100644 --- a/cachelib/shm/SysVShmSegment.h +++ b/cachelib/shm/SysVShmSegment.h @@ -99,6 +99,7 @@ class SysVShmSegment : public ShmBase { void lockPagesInMemory() const; void createReferenceMapping(); void deleteReferenceMapping() const; + void memBind(void* addr) const; // the key identifier for the shared memory KeyType key_{kInvalidKey}; diff --git a/contrib/prerequisites-centos8.sh b/contrib/prerequisites-centos8.sh index 7d6d9cae0e..ae6f795ef4 100755 --- a/contrib/prerequisites-centos8.sh +++ b/contrib/prerequisites-centos8.sh @@ -57,7 +57,8 @@ sudo dnf --enablerepo="$POWERTOOLS_REPO" install -y \ libsodium-static \ libdwarf-static \ boost-static \ - double-conversion-static + double-conversion-static \ + numactl-devel #Do not install these from OS packages - they are typically outdated. #gflags-devel \ diff --git a/contrib/prerequisites-debian10.sh b/contrib/prerequisites-debian10.sh index 4763c841c5..42517c1e47 100755 --- a/contrib/prerequisites-debian10.sh +++ b/contrib/prerequisites-debian10.sh @@ -41,4 +41,5 @@ sudo apt-get install -y \ libdwarf-dev \ libsodium-dev \ libgmock-dev \ - libgtest-dev googletest + libgtest-dev googletest \ + libnuma-dev diff --git a/contrib/prerequisites-ubuntu18.sh b/contrib/prerequisites-ubuntu18.sh index fb6b70f833..3b7faf3ae2 100755 --- a/contrib/prerequisites-ubuntu18.sh +++ b/contrib/prerequisites-ubuntu18.sh @@ -39,7 +39,8 @@ sudo apt-get install -y \ libunwind-dev \ libelf-dev \ libdwarf-dev \ - libsodium-dev + libsodium-dev \ + libnuma-dev # NOTE: # GoogleTest/GoogleMock libraries are available in Ubuntu as From 8a3cc53c8d93fb2a82ef288b68b778799f583f1f Mon Sep 17 00:00:00 2001 From: "Vinogradov, Sergei" Date: Tue, 13 Sep 2022 18:52:42 -0400 Subject: [PATCH 2/4] Implement NUMA binding support for PosixShmSegment --- cachelib/shm/PosixShmSegment.cpp | 64 ++++++++++++++++++++++++++++++++ cachelib/shm/PosixShmSegment.h | 2 + 2 files changed, 66 insertions(+) diff --git a/cachelib/shm/PosixShmSegment.cpp b/cachelib/shm/PosixShmSegment.cpp index ac713d8b15..61bbfc0441 100644 --- a/cachelib/shm/PosixShmSegment.cpp +++ b/cachelib/shm/PosixShmSegment.cpp @@ -16,11 +16,15 @@ #include "cachelib/shm/PosixShmSegment.h" +#include + #include #include #include #include #include +#include +#include #include "cachelib/common/Utils.h" @@ -166,6 +170,29 @@ void munmapImpl(void* addr, size_t length) { } } +void getMempolicyImpl(int &oldMode, NumaBitMask &memBindNumaNodes) { + auto nodeMask = memBindNumaNodes.getNativeBitmask(); + + long ret = get_mempolicy(&oldMode, nodeMask->maskp, nodeMask->size, + nullptr, 0); + + if (ret != 0) { + util::throwSystemError(errno, folly::sformat("get_mempolicy() failed: {}", + std::strerror(errno))); + } +} + +void setMempolicyImpl(int oldMode, const NumaBitMask &memBindNumaNodes) { + auto nodeMask = memBindNumaNodes.getNativeBitmask(); + + long ret = set_mempolicy(oldMode, nodeMask->maskp, nodeMask->size); + + if (ret != 0) { + util::throwSystemError(errno, folly::sformat("set_mempolicy() failed: {}", + std::strerror(errno))); + } +} + } // namespace detail PosixShmSegment::PosixShmSegment(ShmAttachT, @@ -312,6 +339,7 @@ void* PosixShmSegment::mapAddress(void* addr) const { util::throwSystemError(EINVAL, "Address already mapped"); } XDCHECK(retAddr == addr || addr == nullptr); + memBind(addr); return retAddr; } @@ -319,6 +347,42 @@ void PosixShmSegment::unMap(void* addr) const { detail::munmapImpl(addr, getSize()); } +static void forcePageAllocation(void* addr, size_t size, size_t pageSize) { + char* startAddr = reinterpret_cast(addr); + char* endAddr = startAddr + size; + for (volatile char* curAddr = startAddr; curAddr < endAddr; curAddr += pageSize) { + *curAddr = *curAddr; + } +} + +void PosixShmSegment::memBind(void* addr) const { + if (opts_.memBindNumaNodes.empty()) { + return; + } + + NumaBitMask oldMemBindNumaNodes; + int oldMode = 0; + + // mbind() cannot be used because mmap was called with MAP_SHARED flag + // But we can set memory policy for current thread and force page allocation. + // The following logic is used: + // 1. Remember current memory policy for the current thread + // 2. Set new memory policy as specified by config + // 3. Force page allocation by touching every page in the segment + // 4. Restore memory policy + + // Remember current memory policy + detail::getMempolicyImpl(oldMode, oldMemBindNumaNodes); + + // Set memory bindings + detail::setMempolicyImpl(MPOL_BIND, opts_.memBindNumaNodes); + + forcePageAllocation(addr, getSize(), detail::getPageSize(opts_.pageSize)); + + // Restore memory policy for the thread + detail::setMempolicyImpl(oldMode, oldMemBindNumaNodes); +} + std::string PosixShmSegment::createKeyForName( const std::string& name) noexcept { // ensure that the slash is always there in the head. repetitive diff --git a/cachelib/shm/PosixShmSegment.h b/cachelib/shm/PosixShmSegment.h index 711fbf4b87..851edf4eb6 100644 --- a/cachelib/shm/PosixShmSegment.h +++ b/cachelib/shm/PosixShmSegment.h @@ -110,6 +110,8 @@ class PosixShmSegment : public ShmBase { void createReferenceMapping(); void deleteReferenceMapping() const; + void memBind(void* addr) const; + // file descriptor associated with the shm. This has FD_CLOEXEC set // and once opened, we close this only on destruction of this object int fd_{kInvalidFD}; From c97d07a0057387631d601a56a01d06b1c96c2fd8 Mon Sep 17 00:00:00 2001 From: "Vinogradov, Sergei" Date: Thu, 15 Sep 2022 06:45:58 -0400 Subject: [PATCH 3/4] Enabled memory tier config API in cachebench --- cachelib/cachebench/cache/Cache-inl.h | 15 ++++++-- .../test_configs/simple_tiers_test.json | 36 +++++++++++++++++++ cachelib/cachebench/util/CacheConfig.cpp | 16 ++++++++- cachelib/cachebench/util/CacheConfig.h | 26 ++++++++++++++ 4 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 cachelib/cachebench/test_configs/simple_tiers_test.json diff --git a/cachelib/cachebench/cache/Cache-inl.h b/cachelib/cachebench/cache/Cache-inl.h index ed5369facf..a22a342840 100644 --- a/cachelib/cachebench/cache/Cache-inl.h +++ b/cachelib/cachebench/cache/Cache-inl.h @@ -80,6 +80,18 @@ Cache::Cache(const CacheConfig& config, allocatorConfig_.setCacheSize(config_.cacheSizeMB * (MB)); + if (!cacheDir.empty()) { + allocatorConfig_.cacheDir = cacheDir; + } + + if (config_.usePosixShm) { + allocatorConfig_.usePosixForShm(); + } + + if (!config_.memoryTierConfigs.empty()) { + allocatorConfig_.configureMemoryTiers(config_.memoryTierConfigs); + } + auto cleanupGuard = folly::makeGuard([&] { if (!nvmCacheFilePath_.empty()) { util::removePath(nvmCacheFilePath_); @@ -222,8 +234,7 @@ Cache::Cache(const CacheConfig& config, allocatorConfig_.cacheName = "cachebench"; bool isRecovered = false; - if (!cacheDir.empty()) { - allocatorConfig_.cacheDir = cacheDir; + if (!allocatorConfig_.cacheDir.empty()) { try { cache_ = std::make_unique(Allocator::SharedMemAttach, allocatorConfig_); diff --git a/cachelib/cachebench/test_configs/simple_tiers_test.json b/cachelib/cachebench/test_configs/simple_tiers_test.json new file mode 100644 index 0000000000..182bb514cb --- /dev/null +++ b/cachelib/cachebench/test_configs/simple_tiers_test.json @@ -0,0 +1,36 @@ +// @nolint instantiates a small cache and runs a quick run of basic operations. +{ + "cache_config" : { + "cacheSizeMB" : 512, + "usePosixShm" : false, + "cacheDir" : "/tmp/mem-tiers", + "memoryTiers" : [ + { + "ratio": 1, + "memBindNodes": "0" + } + ], + "poolRebalanceIntervalSec" : 1, + "moveOnSlabRelease" : false, + + "numPools" : 2, + "poolSizes" : [0.3, 0.7] + }, + "test_config" : { + "numOps" : 100000, + "numThreads" : 32, + "numKeys" : 1000000, + + "keySizeRange" : [1, 8, 64], + "keySizeRangeProbability" : [0.3, 0.7], + + "valSizeRange" : [1, 32, 10240, 409200], + "valSizeRangeProbability" : [0.1, 0.2, 0.7], + + "getRatio" : 0.15, + "setRatio" : 0.8, + "delRatio" : 0.05, + "keyPoolDistribution": [0.4, 0.6], + "opPoolDistribution" : [0.5, 0.5] + } + } \ No newline at end of file diff --git a/cachelib/cachebench/util/CacheConfig.cpp b/cachelib/cachebench/util/CacheConfig.cpp index 50297955d2..a3a742d255 100644 --- a/cachelib/cachebench/util/CacheConfig.cpp +++ b/cachelib/cachebench/util/CacheConfig.cpp @@ -84,6 +84,13 @@ CacheConfig::CacheConfig(const folly::dynamic& configJson) { JSONSetVal(configJson, memoryOnlyTTL); + JSONSetVal(configJson, usePosixShm); + if (configJson.count("memoryTiers")) { + for (auto& it : configJson["memoryTiers"]) { + memoryTierConfigs.push_back(MemoryTierConfig(it).getMemoryTierCacheConfig()); + } + } + JSONSetVal(configJson, useTraceTimeStamp); JSONSetVal(configJson, printNvmCounters); JSONSetVal(configJson, tickerSynchingSeconds); @@ -95,7 +102,7 @@ CacheConfig::CacheConfig(const folly::dynamic& configJson) { // if you added new fields to the configuration, update the JSONSetVal // to make them available for the json configs and increment the size // below - checkCorrectSize(); + checkCorrectSize(); if (numPools != poolSizes.size()) { throw std::invalid_argument(folly::sformat( @@ -124,6 +131,13 @@ std::shared_ptr CacheConfig::getRebalanceStrategy() const { RandomStrategy::Config{static_cast(rebalanceMinSlabs)}); } } + +MemoryTierConfig::MemoryTierConfig(const folly::dynamic& configJson) { + JSONSetVal(configJson, ratio); + JSONSetVal(configJson, memBindNodes); + + checkCorrectSize(); +} } // namespace cachebench } // namespace cachelib } // namespace facebook diff --git a/cachelib/cachebench/util/CacheConfig.h b/cachelib/cachebench/util/CacheConfig.h index ea863407cf..b19804bf95 100644 --- a/cachelib/cachebench/util/CacheConfig.h +++ b/cachelib/cachebench/util/CacheConfig.h @@ -41,6 +41,26 @@ class CacheMonitorFactory { virtual std::unique_ptr create(Lru2QAllocator& cache) = 0; }; +// Parse memory tiers configuration from JSON config +struct MemoryTierConfig : public JSONConfig { + MemoryTierConfig() {} + + explicit MemoryTierConfig(const folly::dynamic& configJson); + + // Returns MemoryTierCacheConfig parsed from JSON config + MemoryTierCacheConfig getMemoryTierCacheConfig() { + MemoryTierCacheConfig config = MemoryTierCacheConfig::fromShm(); + config.setRatio(ratio); + config.setMemBind(NumaBitMask(memBindNodes)); + return config; + } + + // Specifies ratio of this memory tier to other tiers + size_t ratio{0}; + // Allocate memory only from specified NUMA nodes + std::string memBindNodes{""}; +}; + struct CacheConfig : public JSONConfig { // by defaullt, lru allocator. can be set to LRU-2Q. std::string allocator{"LRU"}; @@ -194,6 +214,12 @@ struct CacheConfig : public JSONConfig { // Not used when its value is 0. In seconds. uint32_t memoryOnlyTTL{0}; + // Use Posix Shm instead of SysVShm + bool usePosixShm{false}; + + // Memory tiers configs + std::vector memoryTierConfigs{}; + // If enabled, we will use the timestamps from the trace file in the ticker // so that the cachebench will observe time based on timestamps from the trace // instead of the system time. From b5422cfc7aa5e3060ef21c58b3009205cd6f1775 Mon Sep 17 00:00:00 2001 From: "Vinogradov, Sergei" Date: Mon, 24 Oct 2022 12:48:44 -0400 Subject: [PATCH 4/4] Single tier cache example with NUMA bindings --- examples/single_tier_cache/CMakeLists.txt | 23 +++++ examples/single_tier_cache/build.sh | 40 ++++++++ examples/single_tier_cache/main.cpp | 109 ++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 examples/single_tier_cache/CMakeLists.txt create mode 100755 examples/single_tier_cache/build.sh create mode 100644 examples/single_tier_cache/main.cpp diff --git a/examples/single_tier_cache/CMakeLists.txt b/examples/single_tier_cache/CMakeLists.txt new file mode 100644 index 0000000000..d89e0b465e --- /dev/null +++ b/examples/single_tier_cache/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required (VERSION 3.12) + +project (cachelib-cmake-test-project VERSION 0.1) + +find_package(cachelib CONFIG REQUIRED) + +add_executable(single-tier-cache-example main.cpp) + +target_link_libraries(single-tier-cache-example cachelib) diff --git a/examples/single_tier_cache/build.sh b/examples/single_tier_cache/build.sh new file mode 100755 index 0000000000..2fc1b49cfb --- /dev/null +++ b/examples/single_tier_cache/build.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e + +# Root directory for the CacheLib project +CLBASE="$PWD/../.." + +# Additional "FindXXX.cmake" files are here (e.g. FindSodium.cmake) +CLCMAKE="$CLBASE/cachelib/cmake" + +# After ensuring we are in the correct directory, set the installation prefix" +PREFIX="$CLBASE/opt/cachelib/" + +CMAKE_PARAMS="-DCMAKE_INSTALL_PREFIX='$PREFIX' -DCMAKE_MODULE_PATH='$CLCMAKE'" + +CMAKE_PREFIX_PATH="$PREFIX/lib/cmake:$PREFIX/lib64/cmake:$PREFIX/lib:$PREFIX/lib64:$PREFIX:${CMAKE_PREFIX_PATH:-}" +export CMAKE_PREFIX_PATH +PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig:$PREFIX/lib64/pkgconfig:${PKG_CONFIG_PATH:-}" +export PKG_CONFIG_PATH +LD_LIBRARY_PATH="$PREFIX/lib:$PREFIX/lib64:${LD_LIBRARY_PATH:-}" +export LD_LIBRARY_PATH + +mkdir -p build +cd build +cmake $CMAKE_PARAMS .. +make diff --git a/examples/single_tier_cache/main.cpp b/examples/single_tier_cache/main.cpp new file mode 100644 index 0000000000..27aa01b0d8 --- /dev/null +++ b/examples/single_tier_cache/main.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cachelib/allocator/CacheAllocator.h" +#include "cachelib/allocator/MemoryTierCacheConfig.h" +#include "folly/init/Init.h" + +namespace facebook { +namespace cachelib_examples { +using Cache = cachelib::LruAllocator; // or Lru2QAllocator, or TinyLFUAllocator +using CacheConfig = typename Cache::Config; +using CacheKey = typename Cache::Key; +using CacheReadHandle = typename Cache::ReadHandle; +using MemoryTierCacheConfig = typename cachelib::MemoryTierCacheConfig; +using NumaBitMask = typename cachelib::NumaBitMask; + +// Global cache object and a default cache pool +std::unique_ptr gCache_; +cachelib::PoolId defaultPool_; + +void initializeCache() { + CacheConfig config; + config + .setCacheSize(48 * 1024 * 1024) // 48 MB + .setCacheName("SingleTier Cache") + .enableCachePersistence("/tmp/simple-tier-cache") + .setAccessConfig( + {25 /* bucket power */, 10 /* lock power */}) // assuming caching 20 + // million items + .configureMemoryTiers({ + MemoryTierCacheConfig::fromShm() + .setRatio(1) + .setMemBind(NumaBitMask().setBit(0))}) // allocate only from NUMA node 0 + .validate(); // will throw if bad config + gCache_ = std::make_unique(Cache::SharedMemNew, config); + defaultPool_ = + gCache_->addPool("default", gCache_->getCacheMemoryStats().cacheSize); +} + +void destroyCache() { gCache_.reset(); } + +CacheReadHandle get(CacheKey key) { return gCache_->find(key); } + +bool put(CacheKey key, const std::string& value) { + auto handle = gCache_->allocate(defaultPool_, key, value.size()); + if (!handle) { + return false; // cache may fail to evict due to too many pending writes + } + std::memcpy(handle->getMemory(), value.data(), value.size()); + gCache_->insertOrReplace(handle); + return true; +} +} // namespace cachelib_examples +} // namespace facebook + +using namespace facebook::cachelib_examples; + +int main(int argc, char** argv) { + folly::init(&argc, &argv); + + initializeCache(); + + std::string value(4*1024, 'X'); // 4 KB value + const size_t NUM_ITEMS = 13000; + + // Use cache + { + for (size_t i = 0; i < NUM_ITEMS; ++i) { + std::string key = "key" + std::to_string(i); + auto res = put(key, value); + + std::ignore = res; + assert(res); + } + + size_t nFound = 0; + size_t nNotFound = 0; + for (size_t i = 0; i < NUM_ITEMS; ++i) { + std::string key = "key" + std::to_string(i); + auto item = get(key); + if (item) { + ++nFound; + folly::StringPiece sp{reinterpret_cast(item->getMemory()), + item->getSize()}; + std::ignore = sp; + assert(sp == value); + } else { + ++nNotFound; + } + } + std::cout << "Found:\t\t" << nFound << " items\n" + << "Not found:\t" << nNotFound << " items" << std::endl; + } + + destroyCache(); +} \ No newline at end of file