Commit 0efd32c4 authored by Alex Snast's avatar Alex Snast Committed by Facebook GitHub Bot

add heterogeneous access support to EvictingCacheMap

Summary:
Heterogeneous access is described [here](https://github.com/facebook/folly/blob/master/folly/container/F14.md#heterogeneous-key-type-with-transparent-hash-and-equality) and this diff adds this capability to EvictingCacheMap.

See `HeterogeneousAccess` test from EvictingCacheMapTest.cpp for usage example.

Reviewed By: yfeldblum

Differential Revision: D26879601

fbshipit-source-id: 027352f2daa6773ab981b924732e1056550f6ea2
parent 0fbe9c5b
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include <boost/iterator/iterator_adaptor.hpp> #include <boost/iterator/iterator_adaptor.hpp>
#include <boost/utility.hpp> #include <boost/utility.hpp>
#include <folly/container/HeterogeneousAccess.h>
#include <folly/lang/Exception.h> #include <folly/lang/Exception.h>
namespace folly { namespace folly {
...@@ -94,25 +95,24 @@ namespace folly { ...@@ -94,25 +95,24 @@ namespace folly {
template < template <
class TKey, class TKey,
class TValue, class TValue,
class THash = std::hash<TKey>, class THash = HeterogeneousAccessHash<TKey>,
class TKeyEqual = std::equal_to<TKey>> class TKeyEqual = HeterogeneousAccessEqualTo<TKey>>
class EvictingCacheMap { class EvictingCacheMap {
private: private:
// typedefs for brevity // typedefs for brevity
struct Node; struct Node;
struct KeyHasher; struct KeyHasher;
struct KeyValueEqual; struct KeyValueEqual;
typedef boost::intrusive::link_mode<boost::intrusive::safe_link> link_mode; using LinkMode = boost::intrusive::link_mode<boost::intrusive::safe_link>;
typedef boost::intrusive::unordered_set< using NodeMap = boost::intrusive::unordered_set<
Node, Node,
boost::intrusive::hash<KeyHasher>, boost::intrusive::hash<KeyHasher>,
boost::intrusive::equal<KeyValueEqual>> boost::intrusive::equal<KeyValueEqual>>;
NodeMap; using NodeList = boost::intrusive::list<Node>;
typedef boost::intrusive::list<Node> NodeList; using TPair = std::pair<const TKey, TValue>;
typedef std::pair<const TKey, TValue> TPair;
public: public:
typedef std::function<void(TKey, TValue&&)> PruneHookCall; using PruneHookCall = std::function<void(TKey, TValue&&)>;
// iterator base : returns TPair on dereference // iterator base : returns TPair on dereference
template <typename Value, typename TIterator> template <typename Value, typename TIterator>
...@@ -141,19 +141,46 @@ class EvictingCacheMap { ...@@ -141,19 +141,46 @@ class EvictingCacheMap {
}; };
// iterators // iterators
typedef iterator_base<TPair, typename NodeList::iterator> iterator; using iterator = iterator_base<TPair, typename NodeList::iterator>;
typedef iterator_base<const TPair, typename NodeList::const_iterator> using const_iterator =
const_iterator; iterator_base<const TPair, typename NodeList::const_iterator>;
typedef iterator_base<TPair, typename NodeList::reverse_iterator> using reverse_iterator =
reverse_iterator; iterator_base<TPair, typename NodeList::reverse_iterator>;
typedef iterator_base<const TPair, typename NodeList::const_reverse_iterator> using const_reverse_iterator =
const_reverse_iterator; iterator_base<const TPair, typename NodeList::const_reverse_iterator>;
// the default map typedefs // the default map typedefs
using key_type = TKey; using key_type = TKey;
using mapped_type = TValue; using mapped_type = TValue;
using hasher = THash; using hasher = THash;
private:
template <typename K, typename T>
using EnableHeterogeneousFind = std::enable_if_t<
detail::EligibleForHeterogeneousFind<TKey, THash, TKeyEqual, K>::value,
T>;
template <typename K, typename T>
using EnableHeterogeneousInsert = std::enable_if_t<
detail::EligibleForHeterogeneousInsert<TKey, THash, TKeyEqual, K>::value,
T>;
template <typename K>
using IsIter = Disjunction<
std::is_same<iterator, remove_cvref_t<K>>,
std::is_same<const_iterator, remove_cvref_t<K>>>;
template <typename K, typename T>
using EnableHeterogeneousErase = std::enable_if_t<
detail::EligibleForHeterogeneousFind<
TKey,
THash,
TKeyEqual,
std::conditional_t<IsIter<K>::value, TKey, K>>::value &&
!IsIter<K>::value,
T>;
public:
/** /**
* Construct a EvictingCacheMap * Construct a EvictingCacheMap
* @param maxSize maximum size of the cache map. Once the map size exceeds * @param maxSize maximum size of the cache map. Once the map size exceeds
...@@ -219,8 +246,11 @@ class EvictingCacheMap { ...@@ -219,8 +246,11 @@ class EvictingCacheMap {
* @param key key to search for * @param key key to search for
* @return true if exists, false otherwise * @return true if exists, false otherwise
*/ */
bool exists(const TKey& key) const { bool exists(const TKey& key) const { return existsImpl(key); }
return findInIndex(key) != index_.end();
template <typename K, EnableHeterogeneousFind<K, int> = 0>
bool exists(const K& key) const {
return existsImpl(key);
} }
/** /**
...@@ -230,12 +260,11 @@ class EvictingCacheMap { ...@@ -230,12 +260,11 @@ class EvictingCacheMap {
* @return the value if it exists * @return the value if it exists
* @throw std::out_of_range exception of the key does not exist * @throw std::out_of_range exception of the key does not exist
*/ */
TValue& get(const TKey& key) { TValue& get(const TKey& key) { return getImpl(key); }
auto it = find(key);
if (it == end()) { template <typename K, EnableHeterogeneousFind<K, int> = 0>
throw_exception<std::out_of_range>("Key does not exist"); TValue& get(const K& key) {
} return getImpl(key);
return it->second;
} }
/** /**
...@@ -245,13 +274,18 @@ class EvictingCacheMap { ...@@ -245,13 +274,18 @@ class EvictingCacheMap {
* @return the iterator of the object (a std::pair of const TKey, TValue) or * @return the iterator of the object (a std::pair of const TKey, TValue) or
* end() if it does not exist * end() if it does not exist
*/ */
iterator find(const TKey& key) { iterator find(const TKey& key) { return findImpl(*this, key); }
auto it = findInIndex(key);
if (it == index_.end()) { template <typename K, EnableHeterogeneousFind<K, int> = 0>
return end(); iterator find(const K& key) {
return findImpl(*this, key);
} }
lru_.splice(lru_.begin(), lru_, lru_.iterator_to(*it));
return iterator(lru_.iterator_to(*it)); const_iterator find(const TKey& key) const { return findImpl(*this, key); }
template <typename K, EnableHeterogeneousFind<K, int> = 0>
const_iterator find(const K& key) const {
return findImpl(*this, key);
} }
/** /**
...@@ -262,16 +296,21 @@ class EvictingCacheMap { ...@@ -262,16 +296,21 @@ class EvictingCacheMap {
* @throw std::out_of_range exception of the key does not exist * @throw std::out_of_range exception of the key does not exist
*/ */
const TValue& getWithoutPromotion(const TKey& key) const { const TValue& getWithoutPromotion(const TKey& key) const {
auto it = findWithoutPromotion(key); return getWithoutPromotionImpl(*this, key);
if (it == end()) {
throw_exception<std::out_of_range>("Key does not exist");
} }
return it->second;
template <typename K, EnableHeterogeneousFind<K, int> = 0>
const TValue& getWithoutPromotion(const K& key) const {
return getWithoutPromotionImpl(*this, key);
} }
TValue& getWithoutPromotion(const TKey& key) { TValue& getWithoutPromotion(const TKey& key) {
auto const& cThis = *this; return getWithoutPromotionImpl(*this, key);
return const_cast<TValue&>(cThis.getWithoutPromotion(key)); }
template <typename K, EnableHeterogeneousFind<K, int> = 0>
TValue& getWithoutPromotion(const K& key) {
return getWithoutPromotionImpl(*this, key);
} }
/** /**
...@@ -282,13 +321,21 @@ class EvictingCacheMap { ...@@ -282,13 +321,21 @@ class EvictingCacheMap {
* end() if it does not exist * end() if it does not exist
*/ */
const_iterator findWithoutPromotion(const TKey& key) const { const_iterator findWithoutPromotion(const TKey& key) const {
auto it = findInIndex(key); return findWithoutPromotionImpl(*this, key);
return (it == index_.end()) ? end() : const_iterator(lru_.iterator_to(*it)); }
template <typename K, EnableHeterogeneousFind<K, int> = 0>
const_iterator findWithoutPromotion(const K& key) const {
return findWithoutPromotionImpl(*this, key);
} }
iterator findWithoutPromotion(const TKey& key) { iterator findWithoutPromotion(const TKey& key) {
auto it = findInIndex(key); return findWithoutPromotionImpl(*this, key);
return (it == index_.end()) ? end() : iterator(lru_.iterator_to(*it)); }
template <typename K, EnableHeterogeneousFind<K, int> = 0>
iterator findWithoutPromotion(const K& key) {
return findWithoutPromotionImpl(*this, key);
} }
/** /**
...@@ -296,13 +343,11 @@ class EvictingCacheMap { ...@@ -296,13 +343,11 @@ class EvictingCacheMap {
* @param key key associated with the value * @param key key associated with the value
* @return true if the key existed and was erased, else false * @return true if the key existed and was erased, else false
*/ */
bool erase(const TKey& key) { bool erase(const TKey& key) { return eraseImpl(key); }
auto it = findInIndex(key);
if (it != index_.end()) { template <typename K, EnableHeterogeneousErase<K, int> = 0>
erase(const_iterator(lru_.iterator_to(*it))); bool erase(const K& key) {
return true; return eraseImpl(key);
}
return false;
} }
/** /**
...@@ -332,22 +377,16 @@ class EvictingCacheMap { ...@@ -332,22 +377,16 @@ class EvictingCacheMap {
TValue value, TValue value,
bool promote = true, bool promote = true,
PruneHookCall pruneHook = nullptr) { PruneHookCall pruneHook = nullptr) {
auto it = findInIndex(key); setImpl(key, std::forward<TValue>(value), promote, pruneHook);
if (it != index_.end()) {
it->pr.second = std::move(value);
if (promote) {
lru_.splice(lru_.begin(), lru_, lru_.iterator_to(*it));
} }
} else {
auto node = new Node(key, std::move(value));
index_.insert(*node);
lru_.push_front(*node);
// no evictions if maxSize_ is 0 i.e. unlimited capacity template <typename K, EnableHeterogeneousInsert<K, int> = 0>
if (maxSize_ > 0 && size() > maxSize_) { void set(
prune(clearSize_, pruneHook); const K& key,
} TValue value,
} bool promote = true,
PruneHookCall pruneHook = nullptr) {
setImpl(key, std::forward<TValue>(value), promote, pruneHook);
} }
/** /**
...@@ -361,18 +400,13 @@ class EvictingCacheMap { ...@@ -361,18 +400,13 @@ class EvictingCacheMap {
*/ */
std::pair<iterator, bool> insert( std::pair<iterator, bool> insert(
const TKey& key, TValue value, PruneHookCall pruneHook = nullptr) { const TKey& key, TValue value, PruneHookCall pruneHook = nullptr) {
auto node = std::make_unique<Node>(key, std::move(value)); return insertImpl(key, std::forward<TValue>(value), pruneHook);
auto pair = index_.insert(*node);
if (pair.second) {
lru_.push_front(*node);
node.release();
// no evictions if maxSize_ is 0 i.e. unlimited capacity
if (maxSize_ > 0 && size() > maxSize_) {
prune(clearSize_, pruneHook);
}
} }
return std::make_pair(iterator(lru_.iterator_to(*pair.first)), pair.second);
template <typename K, EnableHeterogeneousInsert<K, int> = 0>
std::pair<iterator, bool> insert(
const K& key, TValue value, PruneHookCall pruneHook = nullptr) {
return insertImpl(key, std::forward<TValue>(value), pruneHook);
} }
/** /**
...@@ -440,10 +474,10 @@ class EvictingCacheMap { ...@@ -440,10 +474,10 @@ class EvictingCacheMap {
} }
private: private:
struct Node : public boost::intrusive::unordered_set_base_hook<link_mode>, struct Node : public boost::intrusive::unordered_set_base_hook<LinkMode>,
public boost::intrusive::list_base_hook<link_mode> { public boost::intrusive::list_base_hook<LinkMode> {
Node(const TKey& key, TValue&& value) template <typename K>
: pr(std::make_pair(key, std::move(value))) {} Node(const K& key, TValue&& value) : pr(key, std::move(value)) {}
TPair pr; TPair pr;
}; };
...@@ -452,16 +486,21 @@ class EvictingCacheMap { ...@@ -452,16 +486,21 @@ class EvictingCacheMap {
std::size_t operator()(const Node& node) const { std::size_t operator()(const Node& node) const {
return hash(node.pr.first); return hash(node.pr.first);
} }
std::size_t operator()(const TKey& key) const { return hash(key); } template <typename K>
std::size_t operator()(const K& key) const {
return hash(key);
}
THash hash; THash hash;
}; };
struct KeyValueEqual { struct KeyValueEqual {
KeyValueEqual(const TKeyEqual& keyEqual) : equal(keyEqual) {} KeyValueEqual(const TKeyEqual& keyEqual) : equal(keyEqual) {}
bool operator()(const TKey& lhs, const Node& rhs) const { template <typename K>
bool operator()(const K& lhs, const Node& rhs) const {
return equal(lhs, rhs.pr.first); return equal(lhs, rhs.pr.first);
} }
bool operator()(const Node& lhs, const TKey& rhs) const { template <typename K>
bool operator()(const Node& lhs, const K& rhs) const {
return equal(lhs.pr.first, rhs); return equal(lhs.pr.first, rhs);
} }
bool operator()(const Node& lhs, const Node& rhs) const { bool operator()(const Node& lhs, const Node& rhs) const {
...@@ -470,6 +509,99 @@ class EvictingCacheMap { ...@@ -470,6 +509,99 @@ class EvictingCacheMap {
TKeyEqual equal; TKeyEqual equal;
}; };
template <typename K>
bool existsImpl(const K& key) const {
return findInIndex(key) != index_.end();
}
template <typename K>
TValue& getImpl(const K& key) {
auto it = findImpl(*this, key);
if (it == end()) {
throw_exception<std::out_of_range>("Key does not exist");
}
return it->second;
}
template <typename Self>
using self_iterator_t =
std::conditional_t<std::is_const<Self>::value, const_iterator, iterator>;
template <typename Self, typename K>
static auto findImpl(Self& self, const K& key) {
auto it = self.findInIndex(key);
if (it == self.index_.end()) {
return self.end();
}
self.lru_.splice(self.lru_.begin(), self.lru_, self.lru_.iterator_to(*it));
return self_iterator_t<Self>(self.lru_.iterator_to(*it));
}
template <typename Self, typename K>
static auto& getWithoutPromotionImpl(Self& self, const K& key) {
auto it = self.findWithoutPromotion(key);
if (it == self.end()) {
throw_exception<std::out_of_range>("Key does not exist");
}
return it->second;
}
template <typename Self, typename K>
static auto findWithoutPromotionImpl(Self& self, const K& key) {
auto it = self.findInIndex(key);
return (it == self.index_.end())
? self.end()
: self_iterator_t<Self>(self.lru_.iterator_to(*it));
}
template <typename K>
bool eraseImpl(const K& key) {
auto it = findInIndex(key);
if (it != index_.end()) {
erase(const_iterator(lru_.iterator_to(*it)));
return true;
}
return false;
}
template <typename K>
void setImpl(
const K& key, TValue value, bool promote, PruneHookCall pruneHook) {
auto it = findInIndex(key);
if (it != index_.end()) {
it->pr.second = std::move(value);
if (promote) {
lru_.splice(lru_.begin(), lru_, lru_.iterator_to(*it));
}
} else {
auto node = new Node(key, std::move(value));
index_.insert(*node);
lru_.push_front(*node);
// no evictions if maxSize_ is 0 i.e. unlimited capacity
if (maxSize_ > 0 && size() > maxSize_) {
prune(clearSize_, pruneHook);
}
}
}
template <typename K>
auto insertImpl(const K& key, TValue value, PruneHookCall pruneHook) {
auto node = std::make_unique<Node>(key, std::move(value));
auto pair = index_.insert(*node);
if (pair.second) {
lru_.push_front(*node);
node.release();
// no evictions if maxSize_ is 0 i.e. unlimited capacity
if (maxSize_ > 0 && size() > maxSize_) {
prune(clearSize_, pruneHook);
}
}
return std::pair<iterator, bool>(
lru_.iterator_to(*pair.first), pair.second);
}
/** /**
* Get the iterator in in the index associated with a specific key. This is * Get the iterator in in the index associated with a specific key. This is
* merely a search in the index and does not promote the object. * merely a search in the index and does not promote the object.
...@@ -477,11 +609,13 @@ class EvictingCacheMap { ...@@ -477,11 +609,13 @@ class EvictingCacheMap {
* @return the NodeMap::iterator to the Node containing the object * @return the NodeMap::iterator to the Node containing the object
* (a std::pair of const TKey, TValue) or index_.end() if it does not exist * (a std::pair of const TKey, TValue) or index_.end() if it does not exist
*/ */
typename NodeMap::iterator findInIndex(const TKey& key) { template <typename K>
typename NodeMap::iterator findInIndex(const K& key) {
return index_.find(key, KeyHasher(keyHash_), KeyValueEqual(keyEqual_)); return index_.find(key, KeyHasher(keyHash_), KeyValueEqual(keyEqual_));
} }
typename NodeMap::const_iterator findInIndex(const TKey& key) const { template <typename K>
typename NodeMap::const_iterator findInIndex(const K& key) const {
return index_.find(key, KeyHasher(keyHash_), KeyValueEqual(keyEqual_)); return index_.find(key, KeyHasher(keyHash_), KeyValueEqual(keyEqual_));
} }
......
...@@ -713,3 +713,54 @@ TEST(EvictingCacheMap, IteratorConversion) { ...@@ -713,3 +713,54 @@ TEST(EvictingCacheMap, IteratorConversion) {
EXPECT_FALSE((std::is_convertible<cri, ri>::value)); EXPECT_FALSE((std::is_convertible<cri, ri>::value));
EXPECT_TRUE((std::is_convertible<cri, cri>::value)); EXPECT_TRUE((std::is_convertible<cri, cri>::value));
} }
TEST(EvictingCacheMap, HeterogeneousAccess) {
constexpr std::array pieces{
std::pair{"one"_sp, 1},
std::pair{"two"_sp, 2},
std::pair{"three"_sp, 3},
};
constexpr std::array charstars{
std::pair{"four", 4},
std::pair{"five", 5},
std::pair{"six", 6},
std::pair{"seven", 7},
};
EvictingCacheMap<std::string, int> map(0);
for (auto&& [key, value] : pieces) {
auto [_, inserted] = map.insert(key, value);
EXPECT_TRUE(inserted);
}
for (auto&& [key, value] : charstars) {
map.set(key, value);
}
for (auto&& [key, value] : pieces) {
auto exists = map.exists(key);
EXPECT_TRUE(exists);
auto iter = map.find(key);
EXPECT_TRUE(iter != map.end());
EXPECT_EQ(iter->second, value);
iter = map.findWithoutPromotion(key);
EXPECT_TRUE(iter != map.end());
EXPECT_EQ(iter->second, value);
}
for (auto&& [key, value] : charstars) {
auto result = map.get(key);
EXPECT_EQ(result, value);
result = map.getWithoutPromotion(key);
EXPECT_EQ(result, value);
}
for (auto&& [key, _] : pieces) {
auto erased = map.erase(key);
EXPECT_TRUE(erased);
erased = map.erase(key);
EXPECT_FALSE(erased);
}
for (auto&& [key, _] : charstars) {
map.erase(map.findWithoutPromotion(key));
}
EXPECT_TRUE(map.empty());
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment