#error "This should only be included by AtomicHashArray.h"
#endif
+#include <type_traits>
+
#include <folly/Bits.h>
#include <folly/detail/AtomicHashUtils.h>
namespace folly {
// AtomicHashArray private constructor --
-template <class KeyT, class ValueT,
- class HashFcn, class EqualFcn, class Allocator, class ProbeFcn>
-AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+template <class KeyT, class ValueT, class HashFcn, class EqualFcn,
+ class Allocator, class ProbeFcn, class KeyConvertFcn>
+AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
AtomicHashArray(size_t capacity, KeyT emptyKey, KeyT lockedKey,
KeyT erasedKey, double _maxLoadFactor, size_t cacheSize)
: capacity_(capacity),
* of key and returns true, or if key does not exist returns false and
* ret.index is set to capacity_.
*/
-template <class KeyT, class ValueT,
- class HashFcn, class EqualFcn, class Allocator, class ProbeFcn>
-typename AtomicHashArray<KeyT, ValueT,
- HashFcn, EqualFcn, Allocator, ProbeFcn>::SimpleRetT
-AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
-findInternal(const KeyT key_in) {
- DCHECK_NE(key_in, kEmptyKey_);
- DCHECK_NE(key_in, kLockedKey_);
- DCHECK_NE(key_in, kErasedKey_);
- for (size_t idx = keyToAnchorIdx(key_in), numProbes = 0;
+template <class KeyT, class ValueT, class HashFcn, class EqualFcn,
+ class Allocator, class ProbeFcn, class KeyConvertFcn>
+template <class LookupKeyT, class LookupHashFcn, class LookupEqualFcn>
+typename AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::SimpleRetT
+AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
+findInternal(const LookupKeyT key_in) {
+ checkLegalKeyIfKey<LookupKeyT>(key_in);
+
+ for (size_t idx = keyToAnchorIdx<LookupKeyT, LookupHashFcn>(key_in),
+ numProbes = 0;
;
idx = ProbeFcn()(idx, numProbes, capacity_)) {
const KeyT key = acquireLoadKey(cells_[idx]);
- if (LIKELY(EqualFcn()(key, key_in))) {
+ if (LIKELY(LookupEqualFcn()(key, key_in))) {
return SimpleRetT(idx, true);
}
if (UNLIKELY(key == kEmptyKey_)) {
* this will be the previously inserted value, and if the map is full it is
* default.
*/
-template <class KeyT, class ValueT,
- class HashFcn, class EqualFcn, class Allocator, class ProbeFcn>
-template <typename... ArgTs>
-typename AtomicHashArray<KeyT, ValueT,
- HashFcn, EqualFcn, Allocator, ProbeFcn>::SimpleRetT
-AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
-insertInternal(KeyT key_in, ArgTs&&... vCtorArgs) {
+template <class KeyT, class ValueT, class HashFcn, class EqualFcn,
+ class Allocator, class ProbeFcn, class KeyConvertFcn>
+template <typename LookupKeyT,
+ typename LookupHashFcn,
+ typename LookupEqualFcn,
+ typename LookupKeyToKeyFcn,
+ typename... ArgTs>
+typename AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::SimpleRetT
+AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
+insertInternal(LookupKeyT key_in, ArgTs&&... vCtorArgs) {
const short NO_NEW_INSERTS = 1;
const short NO_PENDING_INSERTS = 2;
- CHECK_NE(key_in, kEmptyKey_);
- CHECK_NE(key_in, kLockedKey_);
- CHECK_NE(key_in, kErasedKey_);
+ checkLegalKeyIfKey<LookupKeyT>(key_in);
- size_t idx = keyToAnchorIdx(key_in);
+ size_t idx = keyToAnchorIdx<LookupKeyT, LookupHashFcn>(key_in);
size_t numProbes = 0;
for (;;) {
DCHECK_LT(idx, capacity_);
// If we fail, fall through to comparison below; maybe the insert that
// just beat us was for this very key....
if (tryLockCell(cell)) {
+ KeyT key_new;
// Write the value - done before unlocking
try {
+ key_new = LookupKeyToKeyFcn()(key_in);
+ typedef typename std::remove_const<LookupKeyT>::type
+ LookupKeyTNoConst;
+ constexpr bool kAlreadyChecked =
+ std::is_same<KeyT, LookupKeyTNoConst>::value;
+ if (!kAlreadyChecked) {
+ checkLegalKeyIfKey(key_new);
+ }
DCHECK(relaxedLoadKey(*cell) == kLockedKey_);
new (&cell->second) ValueT(std::forward<ArgTs>(vCtorArgs)...);
- unlockCell(cell, key_in); // Sets the new key
+ unlockCell(cell, key_new); // Sets the new key
} catch (...) {
// Transition back to empty key---requires handling
// locked->empty below.
// An erase() can race here and delete right after our insertion
// Direct comparison rather than EqualFcn ok here
// (we just inserted it)
- DCHECK(relaxedLoadKey(*cell) == key_in ||
+ DCHECK(relaxedLoadKey(*cell) == key_new ||
relaxedLoadKey(*cell) == kErasedKey_);
--numPendingEntries_;
++numEntries_; // This is a thread cached atomic increment :)
}
const KeyT thisKey = acquireLoadKey(*cell);
- if (EqualFcn()(thisKey, key_in)) {
+ if (LookupEqualFcn()(thisKey, key_in)) {
// Found an existing entry for our key, but we don't overwrite the
// previous value.
return SimpleRetT(idx, false);
}
}
-
/*
* erase --
*
* erased key will never be reused. If there's an associated value, we won't
* touch it either.
*/
-template <class KeyT, class ValueT,
- class HashFcn, class EqualFcn, class Allocator, class ProbeFcn>
-size_t AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+template <class KeyT, class ValueT, class HashFcn, class EqualFcn,
+ class Allocator, class ProbeFcn, class KeyConvertFcn>
+size_t AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
erase(KeyT key_in) {
CHECK_NE(key_in, kEmptyKey_);
CHECK_NE(key_in, kLockedKey_);
}
}
-template <class KeyT, class ValueT,
- class HashFcn, class EqualFcn, class Allocator, class ProbeFcn>
-typename AtomicHashArray<KeyT, ValueT,
- HashFcn, EqualFcn, Allocator, ProbeFcn>::SmartPtr
-AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+template <class KeyT, class ValueT, class HashFcn, class EqualFcn,
+ class Allocator, class ProbeFcn, class KeyConvertFcn>
+typename AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::SmartPtr
+AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
create(size_t maxSize, const Config& c) {
CHECK_LE(c.maxLoadFactor, 1.0);
CHECK_GT(c.maxLoadFactor, 0.0);
return map;
}
-template <class KeyT, class ValueT,
- class HashFcn, class EqualFcn, class Allocator, class ProbeFcn>
-void AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+template <class KeyT, class ValueT, class HashFcn, class EqualFcn,
+ class Allocator, class ProbeFcn, class KeyConvertFcn>
+void AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
destroy(AtomicHashArray* p) {
assert(p);
}
// clear -- clears all keys and values in the map and resets all counters
-template <class KeyT, class ValueT,
- class HashFcn, class EqualFcn, class Allocator, class ProbeFcn>
-void AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+template <class KeyT, class ValueT, class HashFcn, class EqualFcn,
+ class Allocator, class ProbeFcn, class KeyConvertFcn>
+void AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
clear() {
FOR_EACH_RANGE(i, 0, capacity_) {
if (cells_[i].first != kEmptyKey_) {
// Iterator implementation
-template <class KeyT, class ValueT,
- class HashFcn, class EqualFcn, class Allocator, class ProbeFcn>
+template <class KeyT, class ValueT, class HashFcn, class EqualFcn,
+ class Allocator, class ProbeFcn, class KeyConvertFcn>
template <class ContT, class IterVal>
-struct AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+struct AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
aha_iterator
: boost::iterator_facade<aha_iterator<ContT,IterVal>,
IterVal,
}
};
+// Enables specializing checkLegalKey without specializing its class.
+namespace detail {
+// Local copy of folly::gen::Identity, to avoid heavy dependencies.
+class AHAIdentity {
+ public:
+ template<class Value>
+ auto operator()(Value&& value) const ->
+ decltype(std::forward<Value>(value)) {
+ return std::forward<Value>(value);
+ }
+};
+
+template <typename NotKeyT, typename KeyT>
+inline void checkLegalKeyIfKeyTImpl(NotKeyT ignored, KeyT emptyKey,
+ KeyT lockedKey, KeyT erasedKey) {
+}
+
+template <typename KeyT>
+inline void checkLegalKeyIfKeyTImpl(KeyT key_in, KeyT emptyKey,
+ KeyT lockedKey, KeyT erasedKey) {
+ DCHECK_NE(key_in, emptyKey);
+ DCHECK_NE(key_in, lockedKey);
+ DCHECK_NE(key_in, erasedKey);
+}
+} // namespace detail
+
template <class KeyT, class ValueT,
class HashFcn = std::hash<KeyT>,
class EqualFcn = std::equal_to<KeyT>,
class Allocator = std::allocator<char>,
- class ProbeFcn = AtomicHashArrayLinearProbeFcn>
+ class ProbeFcn = AtomicHashArrayLinearProbeFcn,
+ class KeyConvertFcn = detail::AHAIdentity>
class AtomicHashMap;
template <class KeyT, class ValueT,
class HashFcn = std::hash<KeyT>,
class EqualFcn = std::equal_to<KeyT>,
class Allocator = std::allocator<char>,
- class ProbeFcn = AtomicHashArrayLinearProbeFcn>
+ class ProbeFcn = AtomicHashArrayLinearProbeFcn,
+ class KeyConvertFcn = detail::AHAIdentity>
class AtomicHashArray : boost::noncopyable {
static_assert((std::is_convertible<KeyT,int32_t>::value ||
std::is_convertible<KeyT,int64_t>::value ||
public:
typedef KeyT key_type;
typedef ValueT mapped_type;
+ typedef HashFcn hasher;
+ typedef EqualFcn key_equal;
+ typedef KeyConvertFcn key_convert;
typedef std::pair<const KeyT, ValueT> value_type;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
// Cannot have pre-instantiated const Config instance because of SIOF.
static SmartPtr create(size_t maxSize, const Config& c = Config());
- iterator find(KeyT k) {
- return iterator(this, findInternal(k).idx);
+ /*
+ * find --
+ *
+ *
+ * Returns the iterator to the element if found, otherwise end().
+ *
+ * As an optional feature, the type of the key to look up (LookupKeyT) is
+ * allowed to be different from the type of keys actually stored (KeyT).
+ *
+ * This enables use cases where materializing the key is costly and usually
+ * redudant, e.g., canonicalizing/interning a set of strings and being able
+ * to look up by StringPiece. To use this feature, LookupHashFcn must take
+ * a LookupKeyT, and LookupEqualFcn must take KeyT and LookupKeyT as first
+ * and second parameter, respectively.
+ *
+ * See folly/test/ArrayHashArrayTest.cpp for sample usage.
+ */
+ template <typename LookupKeyT = key_type,
+ typename LookupHashFcn = hasher,
+ typename LookupEqualFcn = key_equal>
+ iterator find(LookupKeyT k) {
+ return iterator(this,
+ findInternal<LookupKeyT, LookupHashFcn, LookupEqualFcn>(k).idx);
}
- const_iterator find(KeyT k) const {
- return const_cast<AtomicHashArray*>(this)->find(k);
+
+ template <typename LookupKeyT = key_type,
+ typename LookupHashFcn = hasher,
+ typename LookupEqualFcn = key_equal>
+ const_iterator find(LookupKeyT k) const {
+ return const_cast<AtomicHashArray*>(this)->
+ find<LookupKeyT, LookupHashFcn, LookupEqualFcn>(k);
}
/*
*
* Same contract as insert(), but performs in-place construction
* of the value type using the specified arguments.
+ *
+ * Also, like find(), this method optionally allows 'key_in' to have a type
+ * different from that stored in the table; see find(). If and only if no
+ * equal key is already present, this method converts 'key_in' to a key of
+ * type KeyT using the provided LookupKeyToKeyFcn.
*/
- template <typename... ArgTs>
- std::pair<iterator,bool> emplace(KeyT key_in, ArgTs&&... vCtorArgs) {
- SimpleRetT ret = insertInternal(key_in, std::forward<ArgTs>(vCtorArgs)...);
+ template <typename LookupKeyT = key_type,
+ typename LookupHashFcn = hasher,
+ typename LookupEqualFcn = key_equal,
+ typename LookupKeyToKeyFcn = key_convert,
+ typename... ArgTs>
+ std::pair<iterator,bool> emplace(LookupKeyT key_in, ArgTs&&... vCtorArgs) {
+ SimpleRetT ret = insertInternal<LookupKeyT,
+ LookupHashFcn,
+ LookupEqualFcn,
+ LookupKeyToKeyFcn>(
+ key_in,
+ std::forward<ArgTs>(vCtorArgs)...);
return std::make_pair(iterator(this, ret.idx), ret.success);
}
- template <typename... ArgTs>
- SimpleRetT insertInternal(KeyT key, ArgTs&&... vCtorArgs);
+ template <typename LookupKeyT = key_type,
+ typename LookupHashFcn = hasher,
+ typename LookupEqualFcn = key_equal,
+ typename LookupKeyToKeyFcn = detail::AHAIdentity,
+ typename... ArgTs>
+ SimpleRetT insertInternal(LookupKeyT key, ArgTs&&... vCtorArgs);
- SimpleRetT findInternal(const KeyT key);
+ template <typename LookupKeyT = key_type,
+ typename LookupHashFcn = hasher,
+ typename LookupEqualFcn = key_equal>
+ SimpleRetT findInternal(const LookupKeyT key);
+
+ template <typename MaybeKeyT>
+ void checkLegalKeyIfKey(MaybeKeyT key) {
+ detail::checkLegalKeyIfKeyTImpl(key, kEmptyKey_, kLockedKey_, kErasedKey_);
+ }
static std::atomic<KeyT>* cellKeyPtr(const value_type& r) {
// We need some illegal casting here in order to actually store
std::memory_order_acq_rel);
}
- inline size_t keyToAnchorIdx(const KeyT k) const {
- const size_t hashVal = HashFcn()(k);
+ template <class LookupKeyT = key_type, class LookupHashFcn = hasher>
+ inline size_t keyToAnchorIdx(const LookupKeyT k) const {
+ const size_t hashVal = LookupHashFcn()(k);
const size_t probe = hashVal & kAnchorMask_;
return LIKELY(probe < capacity_) ? probe : hashVal % capacity_;
}
typename HashFcn,
typename EqualFcn,
typename Allocator,
- typename ProbeFcn>
-AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+ typename ProbeFcn,
+ typename KeyConvertFcn>
+AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
AtomicHashMap(size_t finalSizeEst, const Config& config)
: kGrowthFrac_(config.growthFactor < 0 ?
1.0 - config.maxLoadFactor : config.growthFactor) {
typename HashFcn,
typename EqualFcn,
typename Allocator,
- typename ProbeFcn>
-template <typename... ArgTs>
-std::pair<typename AtomicHashMap<KeyT, ValueT, HashFcn,
- EqualFcn, Allocator, ProbeFcn>::iterator, bool>
-AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
-emplace(key_type k, ArgTs&&... vCtorArgs) {
- SimpleRetT ret = insertInternal(k, std::forward<ArgTs>(vCtorArgs)...);
+ typename ProbeFcn,
+ typename KeyConvertFcn>
+template <typename LookupKeyT,
+ typename LookupHashFcn,
+ typename LookupEqualFcn,
+ typename LookupKeyToKeyFcn,
+ typename... ArgTs>
+std::pair<typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator,
+ ProbeFcn, KeyConvertFcn>::iterator, bool>
+AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
+emplace(LookupKeyT k, ArgTs&&... vCtorArgs) {
+ SimpleRetT ret = insertInternal<LookupKeyT,
+ LookupHashFcn,
+ LookupEqualFcn,
+ LookupKeyToKeyFcn>(
+ k, std::forward<ArgTs>(vCtorArgs)...);
SubMap* subMap = subMaps_[ret.i].load(std::memory_order_relaxed);
return std::make_pair(iterator(this, ret.i, subMap->makeIter(ret.j)),
ret.success);
typename HashFcn,
typename EqualFcn,
typename Allocator,
- typename ProbeFcn>
-template <typename... ArgTs>
-typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+ typename ProbeFcn,
+ typename KeyConvertFcn>
+template <typename LookupKeyT,
+ typename LookupHashFcn,
+ typename LookupEqualFcn,
+ typename LookupKeyToKeyFcn,
+ typename... ArgTs>
+typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
SimpleRetT
-AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
-insertInternal(key_type key, ArgTs&&... vCtorArgs) {
+AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
+insertInternal(LookupKeyT key, ArgTs&&... vCtorArgs) {
beginInsertInternal:
auto nextMapIdx = // this maintains our state
numMapsAllocated_.load(std::memory_order_acquire);
FOR_EACH_RANGE(i, 0, nextMapIdx) {
// insert in each map successively. If one succeeds, we're done!
SubMap* subMap = subMaps_[i].load(std::memory_order_relaxed);
- ret = subMap->insertInternal(key, std::forward<ArgTs>(vCtorArgs)...);
+ ret = subMap->template insertInternal<LookupKeyT,
+ LookupHashFcn,
+ LookupEqualFcn,
+ LookupKeyToKeyFcn>(
+ key, std::forward<ArgTs>(vCtorArgs)...);
if (ret.idx == subMap->capacity_) {
continue; //map is full, so try the next one
}
typename HashFcn,
typename EqualFcn,
typename Allocator,
- typename ProbeFcn>
-typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+ typename ProbeFcn,
+ typename KeyConvertFcn>
+template <class LookupKeyT, class LookupHashFcn, class LookupEqualFcn>
+typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
iterator
-AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::find(
- KeyT k) {
- SimpleRetT ret = findInternal(k);
+AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::find(
+ LookupKeyT k) {
+ SimpleRetT ret = findInternal<LookupKeyT, LookupHashFcn, LookupEqualFcn>(k);
if (!ret.success) {
return end();
}
typename HashFcn,
typename EqualFcn,
typename Allocator,
- typename ProbeFcn>
+ typename ProbeFcn,
+ typename KeyConvertFcn>
+template <class LookupKeyT, class LookupHashFcn, class LookupEqualFcn>
typename AtomicHashMap<KeyT, ValueT,
- HashFcn, EqualFcn, Allocator, ProbeFcn>::const_iterator
-AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
-find(KeyT k) const {
- return const_cast<AtomicHashMap*>(this)->find(k);
+ HashFcn, EqualFcn, Allocator, ProbeFcn, KeyConvertFcn>::const_iterator
+AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
+find(LookupKeyT k) const {
+ return const_cast<AtomicHashMap*>(this)->find<LookupKeyT,
+ LookupHashFcn,
+ LookupEqualFcn>(k);
}
// findInternal --
typename HashFcn,
typename EqualFcn,
typename Allocator,
- typename ProbeFcn>
-typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+ typename ProbeFcn,
+ typename KeyConvertFcn>
+template <class LookupKeyT, class LookupHashFcn, class LookupEqualFcn>
+typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
SimpleRetT
-AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
- findInternal(const KeyT k) const {
+AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
+ findInternal(const LookupKeyT k) const {
SubMap* const primaryMap = subMaps_[0].load(std::memory_order_relaxed);
- typename SubMap::SimpleRetT ret = primaryMap->findInternal(k);
+ typename SubMap::SimpleRetT ret =
+ primaryMap->template findInternal<LookupKeyT,
+ LookupHashFcn,
+ LookupEqualFcn>(k);
if (LIKELY(ret.idx != primaryMap->capacity_)) {
return SimpleRetT(0, ret.idx, ret.success);
}
FOR_EACH_RANGE(i, 1, numMaps) {
// Check each map successively. If one succeeds, we're done!
SubMap* thisMap = subMaps_[i].load(std::memory_order_relaxed);
- ret = thisMap->findInternal(k);
+ ret = thisMap->template findInternal<LookupKeyT,
+ LookupHashFcn,
+ LookupEqualFcn>(k);
if (LIKELY(ret.idx != thisMap->capacity_)) {
return SimpleRetT(i, ret.idx, ret.success);
}
typename HashFcn,
typename EqualFcn,
typename Allocator,
- typename ProbeFcn>
-typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+ typename ProbeFcn,
+ typename KeyConvertFcn>
+typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
SimpleRetT
-AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
findAtInternal(uint32_t idx) const {
uint32_t subMapIdx, subMapOffset;
if (idx & kSecondaryMapBit_) {
typename HashFcn,
typename EqualFcn,
typename Allocator,
- typename ProbeFcn>
-typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+ typename ProbeFcn,
+ typename KeyConvertFcn>
+typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
size_type
-AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
erase(const KeyT k) {
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
FOR_EACH_RANGE(i, 0, numMaps) {
typename HashFcn,
typename EqualFcn,
typename Allocator,
- typename ProbeFcn>
-size_t AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+ typename ProbeFcn,
+ typename KeyConvertFcn>
+size_t AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
capacity() const {
size_t totalCap(0);
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
typename HashFcn,
typename EqualFcn,
typename Allocator,
- typename ProbeFcn>
-size_t AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+ typename ProbeFcn,
+ typename KeyConvertFcn>
+size_t AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
spaceRemaining() const {
size_t spaceRem(0);
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
typename HashFcn,
typename EqualFcn,
typename Allocator,
- typename ProbeFcn>
-void AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+ typename ProbeFcn,
+ typename KeyConvertFcn>
+void AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
clear() {
subMaps_[0].load(std::memory_order_relaxed)->clear();
int const numMaps = numMapsAllocated_
typename HashFcn,
typename EqualFcn,
typename Allocator,
- typename ProbeFcn>
-size_t AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+ typename ProbeFcn,
+ typename KeyConvertFcn>
+size_t AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
size() const {
size_t totalSize(0);
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
typename HashFcn,
typename EqualFcn,
typename Allocator,
- typename ProbeFcn>
+ typename ProbeFcn,
+ typename KeyConvertFcn>
inline uint32_t
-AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
encodeIndex(uint32_t subMap, uint32_t offset) {
DCHECK_EQ(offset & kSecondaryMapBit_, 0); // offset can't be too big
if (subMap == 0) return offset;
typename HashFcn,
typename EqualFcn,
typename Allocator,
- typename ProbeFcn>
+ typename ProbeFcn,
+ typename KeyConvertFcn>
template <class ContT, class IterVal, class SubIt>
-struct AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
+struct AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>::
ahm_iterator : boost::iterator_facade<ahm_iterator<ContT, IterVal, SubIt>,
IterVal,
boost::forward_traversal_tag> {
{}
};
-template<class KeyT, class ValueT,
- class HashFcn, class EqualFcn, class Allocator, class ProbeFcn>
+template<class KeyT, class ValueT, class HashFcn, class EqualFcn,
+ class Allocator, class ProbeFcn, class KeyConvertFcn>
class AtomicHashMap : boost::noncopyable {
-typedef AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>
+typedef AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn,
+ Allocator, ProbeFcn, KeyConvertFcn>
SubMap;
public:
typedef std::pair<const KeyT, ValueT> value_type;
typedef HashFcn hasher;
typedef EqualFcn key_equal;
+ typedef KeyConvertFcn key_convert;
typedef value_type* pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
*
* Same contract as insert(), but performs in-place construction
* of the value type using the specified arguments.
+ *
+ * Also, like find(), this method optionally allows 'key_in' to have a type
+ * different from that stored in the table; see find(). If and only if no
+ * equal key is already present, this method converts 'key_in' to a key of
+ * type KeyT using the provided LookupKeyToKeyFcn.
*/
- template <typename... ArgTs>
- std::pair<iterator,bool> emplace(key_type k, ArgTs&&... vCtorArg);
+ template <typename LookupKeyT = key_type,
+ typename LookupHashFcn = hasher,
+ typename LookupEqualFcn = key_equal,
+ typename LookupKeyToKeyFcn = key_convert,
+ typename... ArgTs>
+ std::pair<iterator,bool> emplace(LookupKeyT k, ArgTs&&... vCtorArg);
/*
* find --
*
- * Returns an iterator into the map.
+ * Returns the iterator to the element if found, otherwise end().
+ *
+ * As an optional feature, the type of the key to look up (LookupKeyT) is
+ * allowed to be different from the type of keys actually stored (KeyT).
*
- * If the key is not found, returns end().
+ * This enables use cases where materializing the key is costly and usually
+ * redudant, e.g., canonicalizing/interning a set of strings and being able
+ * to look up by StringPiece. To use this feature, LookupHashFcn must take
+ * a LookupKeyT, and LookupEqualFcn must take KeyT and LookupKeyT as first
+ * and second parameter, respectively.
+ *
+ * See folly/test/ArrayHashMapTest.cpp for sample usage.
*/
- iterator find(key_type k);
- const_iterator find(key_type k) const;
+ template <typename LookupKeyT = key_type,
+ typename LookupHashFcn = hasher,
+ typename LookupEqualFcn = key_equal>
+ iterator find(LookupKeyT k);
+
+ template <typename LookupKeyT = key_type,
+ typename LookupHashFcn = hasher,
+ typename LookupEqualFcn = key_equal>
+ const_iterator find(LookupKeyT k) const;
/*
* erase --
SimpleRetT() = default;
};
- template <typename... ArgTs>
- SimpleRetT insertInternal(KeyT key, ArgTs&&... value);
-
- SimpleRetT findInternal(const KeyT k) const;
+ template <typename LookupKeyT = key_type,
+ typename LookupHashFcn = hasher,
+ typename LookupEqualFcn = key_equal,
+ typename LookupKeyToKeyFcn = key_convert,
+ typename... ArgTs>
+ SimpleRetT insertInternal(LookupKeyT key, ArgTs&&... value);
+
+ template <typename LookupKeyT = key_type,
+ typename LookupHashFcn = hasher,
+ typename LookupEqualFcn = key_equal>
+ SimpleRetT findInternal(const LookupKeyT k) const;
SimpleRetT findAtInternal(uint32_t idx) const;
TEST(Aha, Create_cstr_i64) {
auto obj = AtomicHashArray<const char*, int64_t>::create(12);
}
+
+static bool legalKey(char* a);
+
+// Support two additional key lookup types (char and StringPiece) using
+// one set of traits.
+struct EqTraits {
+ bool operator()(char* a, char* b) {
+ return legalKey(a) && (strcmp(a, b) == 0);
+ }
+ bool operator()(char* a, const char& b) {
+ return legalKey(a) && (a[0] != '\0') && (a[0] == b);
+ }
+ bool operator()(char* a, const StringPiece b) {
+ return legalKey(a) &&
+ (strlen(a) == b.size()) && (strncmp(a, b.begin(), b.size()) == 0);
+ }
+};
+
+struct HashTraits {
+ size_t operator()(char* a) {
+ size_t result = 0;
+ while (a[0] != 0) result += static_cast<size_t>(*(a++));
+ return result;
+ }
+ size_t operator()(const char& a) {
+ return static_cast<size_t>(a);
+ }
+ size_t operator()(const StringPiece a) {
+ size_t result = 0;
+ for (const auto& ch : a) result += static_cast<size_t>(ch);
+ return result;
+ }
+};
+
+// Creates malloc'ed null-terminated strings.
+struct KeyConvertTraits {
+ char* operator()(const char& a) {
+ return strndup(&a, 1);
+ }
+ char* operator()(const StringPiece a) {
+ return strndup(a.begin(), a.size());
+ }
+};
+
+typedef AtomicHashArray<char*, int64_t, HashTraits, EqTraits,
+ MmapAllocator<char>, AtomicHashArrayQuadraticProbeFcn,
+ KeyConvertTraits>
+ AHACstrInt;
+AHACstrInt::Config cstrIntCfg;
+
+static bool legalKey(char* a) {
+ return a != cstrIntCfg.emptyKey &&
+ a != cstrIntCfg.lockedKey &&
+ a != cstrIntCfg.erasedKey;
+}
+
+TEST(Aha, LookupAny) {
+ auto arr = AHACstrInt::create(12);
+
+ arr->insert(std::make_pair(strdup("f"), 42));
+ EXPECT_EQ(42, arr->find("f")->second);
+ {
+ // Look up a single char, successfully.
+ auto it = arr->find('f');
+ EXPECT_EQ(42, it->second);
+ }
+ {
+ // Look up a single char, unsuccessfully.
+ auto it = arr->find('g');
+ EXPECT_TRUE(it == arr->end());
+ }
+ {
+ // Insert a new char key.
+ auto res = arr->emplace('h', static_cast<int64_t>(123));
+ EXPECT_TRUE(res.second);
+ EXPECT_TRUE(res.first != arr->end());
+ // Look up the string version.
+ EXPECT_EQ(123, arr->find("h")->second);
+ }
+ {
+ // Fail to emplace an existing key.
+ auto res = arr->emplace('f', static_cast<int64_t>(123));
+ EXPECT_FALSE(res.second);
+ EXPECT_TRUE(res.first != arr->end());
+ }
+
+ for (auto it : *arr) free(it.first);
+}
using std::string;
using folly::AtomicHashMap;
using folly::AtomicHashArray;
+using folly::StringPiece;
// Tunables:
DEFINE_double(targetLoadFactor, 0.75, "Target memory utilization fraction.");
return key / 3;
}
+static bool legalKey(const char* a);
+
+struct EqTraits {
+ bool operator()(const char* a, const char* b) {
+ return legalKey(a) && (strcmp(a, b) == 0);
+ }
+ bool operator()(const char* a, const char& b) {
+ return legalKey(a) && (a[0] != '\0') && (a[0] == b);
+ }
+ bool operator()(const char* a, const StringPiece b) {
+ return legalKey(a) &&
+ (strlen(a) == b.size()) && (strcmp(a, b.begin()) == 0);
+ }
+};
+
+struct HashTraits {
+ size_t operator()(const char* a) {
+ size_t result = 0;
+ while (a[0] != 0) result += static_cast<size_t>(*(a++));
+ return result;
+ }
+ size_t operator()(const char& a) {
+ return static_cast<size_t>(a);
+ }
+ size_t operator()(const StringPiece a) {
+ size_t result = 0;
+ for (const auto& ch : a) result += static_cast<size_t>(ch);
+ return result;
+ }
+};
+
+typedef AtomicHashMap<const char*, int64_t, HashTraits, EqTraits> AHMCstrInt;
+AHMCstrInt::Config cstrIntCfg;
+
+static bool legalKey(const char* a) {
+ return a != cstrIntCfg.emptyKey &&
+ a != cstrIntCfg.lockedKey &&
+ a != cstrIntCfg.erasedKey;
+}
+
+TEST(Ahm, BasicLookup) {
+ AHMCstrInt myMap(1024, cstrIntCfg);
+ EXPECT_TRUE(myMap.begin() == myMap.end());
+ myMap.insert(std::make_pair("f", 42));
+ EXPECT_EQ(42, myMap.find("f")->second);
+ {
+ // Look up a single char, successfully.
+ auto it = myMap.find<char>('f');
+ EXPECT_EQ(42, it->second);
+ }
+ {
+ // Look up a single char, unsuccessfully.
+ auto it = myMap.find<char>('g');
+ EXPECT_TRUE(it == myMap.end());
+ }
+ {
+ // Look up a string piece, successfully.
+ const StringPiece piece("f");
+ auto it = myMap.find(piece);
+ EXPECT_EQ(42, it->second);
+ }
+}
+
TEST(Ahm, grow) {
VLOG(1) << "Overhead: " << sizeof(AHArrayT) << " (array) " <<
sizeof(AHMapT) + sizeof(AHArrayT) << " (map/set) Bytes.";