Enable find/emplace for key types other than KeyT.
authorDaniel Andersson <koda@fb.com>
Tue, 1 Dec 2015 15:46:00 +0000 (07:46 -0800)
committerfacebook-github-bot-0 <folly-bot@fb.com>
Tue, 1 Dec 2015 16:20:22 +0000 (08:20 -0800)
Summary: Add template parameters to support arbitrary types when looking up a key.

This is useful to avoid the cost of 'materializing' a key which is likely to be present in the array (or, alternatively, switching on a tagged union KeyT).

Use the new capability to greatly simplify the lookup logic in HHVMs static string table.

Reviewed By: nbronson

Differential Revision: D2662451

fb-gh-sync-id: 707fa033f350b80ca8080af17f1a8a74c59f2e88

folly/AtomicHashArray-inl.h
folly/AtomicHashArray.h
folly/AtomicHashMap-inl.h
folly/AtomicHashMap.h
folly/test/AtomicHashArrayTest.cpp
folly/test/AtomicHashMapTest.cpp

index 1125bb980f8964b4fdb924b24561cdc2067e23bf..35a1927c53017de04822a04e9665252e94b1c9a2 100644 (file)
 #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),
@@ -43,20 +46,22 @@ AtomicHashArray(size_t capacity, KeyT emptyKey, KeyT lockedKey,
  *   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_)) {
@@ -83,20 +88,23 @@ findInternal(const KeyT key_in) {
  *   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_);
@@ -132,11 +140,20 @@ insertInternal(KeyT key_in, ArgTs&&... vCtorArgs) {
         // 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.
@@ -147,7 +164,7 @@ insertInternal(KeyT key_in, ArgTs&&... vCtorArgs) {
           // 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 :)
@@ -167,7 +184,7 @@ insertInternal(KeyT key_in, ArgTs&&... vCtorArgs) {
     }
 
     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);
@@ -191,7 +208,6 @@ insertInternal(KeyT key_in, ArgTs&&... vCtorArgs) {
   }
 }
 
-
 /*
  * erase --
  *
@@ -202,9 +218,10 @@ insertInternal(KeyT key_in, ArgTs&&... vCtorArgs) {
  *   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_);
@@ -250,11 +267,12 @@ erase(KeyT key_in) {
   }
 }
 
-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);
@@ -292,9 +310,10 @@ create(size_t maxSize, const Config& c) {
   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);
 
@@ -311,9 +330,10 @@ destroy(AtomicHashArray* 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_) {
@@ -331,10 +351,11 @@ clear() {
 
 // 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,
index 71ef2c76ab5fe87ffba5b5679c7d64c5dc064963..33c59247e38e5bc7e3a80b6b3499a7cbc08a0641 100644 (file)
@@ -62,18 +62,46 @@ struct AtomicHashArrayQuadraticProbeFcn
   }
 };
 
+// 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 ||
@@ -84,6 +112,9 @@ class AtomicHashArray : boost::noncopyable {
  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;
@@ -164,11 +195,37 @@ class AtomicHashArray : boost::noncopyable {
   //  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);
   }
 
   /*
@@ -194,10 +251,24 @@ class AtomicHashArray : boost::noncopyable {
    *
    *   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);
   }
 
@@ -276,10 +347,22 @@ friend class AtomicHashMap<KeyT,
 
 
 
-  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
@@ -330,8 +413,9 @@ friend class AtomicHashMap<KeyT,
       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_;
   }
index 61c23b14cfdd1f24698620e63b13cd75ec8da016..6526351f4dbea3eae68240663e35a6fda1331972 100644 (file)
@@ -29,8 +29,10 @@ template <typename KeyT,
           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) {
@@ -50,13 +52,23 @@ template <typename KeyT,
           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);
@@ -68,12 +80,19 @@ template <typename KeyT,
           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);
@@ -81,7 +100,11 @@ insertInternal(key_type key, ArgTs&&... vCtorArgs) {
   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
     }
@@ -151,12 +174,16 @@ template <typename KeyT,
           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();
   }
@@ -169,12 +196,17 @@ template <typename KeyT,
           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 --
@@ -183,13 +215,20 @@ template <typename KeyT,
           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);
   }
@@ -197,7 +236,9 @@ AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>::
   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);
     }
@@ -212,10 +253,13 @@ template <typename KeyT,
           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_) {
@@ -238,10 +282,13 @@ template <typename KeyT,
           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) {
@@ -260,8 +307,10 @@ template <typename KeyT,
           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);
@@ -278,8 +327,10 @@ template <typename KeyT,
           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);
@@ -300,8 +351,10 @@ template <typename KeyT,
           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_
@@ -321,8 +374,10 @@ template <typename KeyT,
           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);
@@ -355,9 +410,11 @@ template <typename KeyT,
           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;
@@ -378,9 +435,11 @@ template <typename KeyT,
           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> {
index c6f06bd8f27016d0dcf5e371e94ece91af2508cd..68e17e69b08cbf49177dfec7792d2b61a7492fa6 100644 (file)
@@ -155,10 +155,11 @@ struct AtomicHashMapFullError : std::runtime_error {
   {}
 };
 
-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:
@@ -167,6 +168,7 @@ typedef AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>
   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;
@@ -239,19 +241,44 @@ typedef AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>
    *
    *   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 --
@@ -403,10 +430,17 @@ typedef AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn, Allocator, ProbeFcn>
     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;
 
index 1f83bc754b8e9d17a536502b975ed219716d6498..cbf688108976aab5c13a705c597fe6b2117ebdc7 100644 (file)
@@ -247,3 +247,91 @@ TEST(Aha, InsertErase_i64_str) {
 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);
+}
index 63523edad9f1140b243affb3edd52f46a8b8d982..51f0b102fda2eb40d71e6e0a9bfa229adec4e175 100644 (file)
@@ -29,6 +29,7 @@ using std::vector;
 using std::string;
 using folly::AtomicHashMap;
 using folly::AtomicHashArray;
+using folly::StringPiece;
 
 // Tunables:
 DEFINE_double(targetLoadFactor, 0.75, "Target memory utilization fraction.");
@@ -113,6 +114,69 @@ static int genVal(int key) {
   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.";