|
Katana Plug-in APIs 0.1
|
00001 #ifndef INCLUDED_FNGEOLIBUTIL_ATTRIBUTEKEYEDCACHE_H 00002 #define INCLUDED_FNGEOLIBUTIL_ATTRIBUTEKEYEDCACHE_H 00003 00004 #include "ns.h" 00005 00006 #include <atomic> 00007 #include <cstdlib> 00008 #include <memory> 00009 #include <mutex> 00010 #include <string> 00011 #ifdef ATTRIBUTEKEYEDCACHE_USE_BOOST 00012 #include <boost/unordered_map.hpp> 00013 #include <boost/unordered_set.hpp> 00014 #else 00015 #include <map> 00016 #include <set> 00017 #endif 00018 #include <vector> 00019 00020 #ifndef ATTRIBUTEKEYEDCACHE_NO_TBB 00021 #include <tbb/concurrent_hash_map.h> 00022 #include <tbb/concurrent_queue.h> 00023 #endif 00024 00025 #include <FnAttribute/FnAttribute.h> 00026 00027 FNGEOLIBUTIL_NAMESPACE_ENTER 00028 { 00029 template <class T, class PointerT = typename std::shared_ptr<T>> 00030 class AttributeKeyedCacheMutex 00031 { 00032 public: 00033 typedef PointerT IMPLPtr; 00034 00035 #ifdef ATTRIBUTEKEYEDCACHE_USE_BOOST 00036 using KeyMapType = boost::unordered_map<uint64_t, IMPLPtr>; 00037 using KeySetType = boost::unorderd_set<uint64_t>; 00038 #else 00039 using KeyMapType = std::map<uint64_t, IMPLPtr>; 00040 using KeySetType = std::set<uint64_t>; 00041 #endif 00042 00043 AttributeKeyedCacheMutex( 00044 const std::size_t maxNumEntries = 0xffffffff, 00045 const std::size_t maxNumInvalidKeys = 0xffffffff) 00046 : m_maxNumEntries(maxNumEntries), 00047 m_maxNumInvalidKeys(maxNumInvalidKeys) 00048 { 00049 } 00050 00051 virtual ~AttributeKeyedCacheMutex() {} 00052 00053 IMPLPtr getValue(const FnAttribute::Attribute& iAttr) 00054 { 00055 // For these purposes, good enough to use a simple 64-bit hash. 00056 // The rationale is that AttributeKeyedCache should not grow to 00057 // the point where 2**64th hash entries are insufficient. 00058 // 00059 // For example, with 65K worth of entries the odds are roughly 00060 // 1 in 10-billion. 00061 // For 6 million entries, the odds would still be 1 in a million 00062 // (If the num entries is greater than ~20 million, then all 128 00063 // bits of hash should be used) 00064 00065 const uint64_t hash = iAttr.getHash().uint64(); 00066 00067 if (m_maxNumInvalidKeys > 0) 00068 { 00069 // lock for our search in the invalid set 00070 std::lock_guard<std::mutex> lock(m_invalidKeysMutex); 00071 00072 if (m_invalidKeys.find(hash) != m_invalidKeys.end()) 00073 { 00074 return IMPLPtr(); 00075 } 00076 } 00077 00078 if (m_maxNumEntries > 0) 00079 { 00080 // lock for our search in the cache 00081 std::lock_guard<std::mutex> lock(m_entriesMutex); 00082 00083 const auto it = m_entries.find(hash); 00084 if (it != m_entries.end()) 00085 { 00086 return it->second; 00087 } 00088 } 00089 00090 IMPLPtr val = createValue(iAttr); 00091 if (val && m_maxNumEntries > 0) 00092 { 00093 std::lock_guard<std::mutex> lock(m_entriesMutex); 00094 if (!m_entries.count(hash)) 00095 { 00096 if (m_entries.size() == m_maxNumEntries) 00097 { 00098 const std::size_t idx = 00099 std::rand() % m_entriesVector.size(); 00100 const uint64_t keyToDrop = m_entriesVector[idx]; 00101 m_entriesVector[idx] = hash; 00102 m_entries.erase(keyToDrop); 00103 } 00104 else 00105 { 00106 m_entriesVector.push_back(hash); 00107 } 00108 m_entries.emplace(hash, val); 00109 } 00110 } 00111 else if (!val && m_maxNumInvalidKeys > 0) 00112 { 00113 std::lock_guard<std::mutex> lock(m_invalidKeysMutex); 00114 if (!m_invalidKeys.count(hash)) 00115 { 00116 if (m_invalidKeys.size() == m_maxNumInvalidKeys) 00117 { 00118 const std::size_t idx = 00119 std::rand() % m_invalidKeysVector.size(); 00120 const uint64_t keyToDrop = m_invalidKeysVector[idx]; 00121 m_invalidKeysVector[idx] = hash; 00122 m_invalidKeys.erase(keyToDrop); 00123 } 00124 else 00125 { 00126 m_invalidKeysVector.push_back(hash); 00127 } 00128 m_invalidKeys.emplace(hash); 00129 } 00130 } 00131 return val; 00132 } 00133 00134 void clear() 00135 { 00136 { 00137 std::lock_guard<std::mutex> lock(m_entriesMutex); 00138 m_entries.clear(); 00139 m_entriesVector.clear(); 00140 } 00141 00142 { 00143 std::lock_guard<std::mutex> lock(m_invalidKeysMutex); 00144 m_invalidKeys.clear(); 00145 m_invalidKeysVector.clear(); 00146 } 00147 } 00148 00149 protected: 00150 virtual IMPLPtr createValue(const FnAttribute::Attribute& iAttr) = 0; 00151 00152 private: 00153 const std::size_t m_maxNumEntries; 00154 const std::size_t m_maxNumInvalidKeys; 00155 00156 KeyMapType m_entries; 00157 KeySetType m_invalidKeys; 00158 00159 std::vector<uint64_t> m_entriesVector; 00160 std::vector<uint64_t> m_invalidKeysVector; 00161 00162 std::mutex m_invalidKeysMutex; 00163 std::mutex m_entriesMutex; 00164 }; 00165 00166 #ifndef ATTRIBUTEKEYEDCACHE_NO_TBB 00167 template <class T, class PointerT = 00168 typename std::shared_ptr<T>> 00169 class AttributeKeyedCacheLockFree 00170 { 00171 public: 00172 typedef PointerT IMPLPtr; 00173 00174 AttributeKeyedCacheLockFree( 00175 const std::size_t maxNumEntries = 0xffffffff, 00176 const std::size_t maxNumInvalidKeys = 0xffffffff) 00177 : m_maxNumEntries(maxNumEntries), 00178 m_maxNumInvalidKeys(maxNumInvalidKeys) 00179 { 00180 } 00181 00182 virtual ~AttributeKeyedCacheLockFree() {} 00183 00184 IMPLPtr getValue(const FnAttribute::Attribute& iAttr) 00185 { 00186 // For these purposes, good enough to use a simple 64-bit hash. 00187 // The rationale is that AttributeKeyedCache should not grow to 00188 // the point where 2**64th hash entries are insufficient. 00189 // 00190 // For example, with 65K worth of entries the odds are roughly 00191 // 1 in 10-billion. 00192 // For 6 million entries, the odds would still be 1 in a million 00193 // (If the num entries is greater than ~20 million, then all 128 00194 // bits of hash should be used) 00195 00196 const uint64_t hash = iAttr.getHash().uint64(); 00197 00198 if (m_maxNumInvalidKeys > 0) 00199 { 00200 typename decltype(m_invalidKeys)::const_accessor acc; 00201 if (m_invalidKeys.find(acc, hash)) 00202 { 00203 return {}; 00204 } 00205 } 00206 00207 if (m_maxNumEntries > 0) 00208 { 00209 typename decltype(m_entries)::const_accessor acc; 00210 if (m_entries.find(acc, hash)) 00211 { 00212 return acc->second; 00213 } 00214 } 00215 00216 IMPLPtr val = createValue(iAttr); 00217 if (val && m_maxNumEntries > 0) 00218 { 00219 insert<IMPLPtr, typename decltype(m_entries)::accessor>( 00220 hash, val, m_maxNumEntries, m_numEntries, m_entries, 00221 m_entriesQueue); 00222 } 00223 else if (!val && m_maxNumInvalidKeys > 0) 00224 { 00225 insert<bool, typename decltype(m_invalidKeys)::accessor>( 00226 hash, false, m_maxNumInvalidKeys, m_numInvalidKeys, 00227 m_invalidKeys, m_invalidKeysQueue); 00228 } 00229 return val; 00230 } 00231 00232 void clear() 00233 { 00234 if (m_numEntries.exchange(0)) 00235 { 00236 m_entries.clear(); 00237 m_entriesQueue.clear(); 00238 } 00239 00240 if (m_numInvalidKeys.exchange(0)) 00241 { 00242 m_invalidKeys.clear(); 00243 m_invalidKeysQueue.clear(); 00244 } 00245 } 00246 00247 protected: 00248 virtual IMPLPtr createValue(const FnAttribute::Attribute& iAttr) = 0; 00249 00250 private: 00251 template <typename ValueT, typename AccessorT> 00252 static void insert(const uint64_t key, 00253 const ValueT& value, 00254 const std::size_t& maxNumEntries, 00255 std::atomic_size_t& numEntries, 00256 tbb::concurrent_hash_map<uint64_t, ValueT>& entries, 00257 tbb::concurrent_queue<uint64_t>& entriesQueue) 00258 { 00259 AccessorT acc; 00260 if (entries.insert(acc, key)) 00261 { 00262 acc->second = value; 00263 acc.release(); 00264 00265 const bool dropKey = ++numEntries > maxNumEntries; 00266 if (dropKey) 00267 { 00268 uint64_t keyToDrop = 0; 00269 const bool retrieved = entriesQueue.try_pop(keyToDrop); 00270 (void)retrieved; 00271 assert(retrieved); 00272 const bool erased = entries.erase(keyToDrop); 00273 (void)erased; 00274 assert(erased); 00275 --numEntries; 00276 } 00277 00278 entriesQueue.push(key); 00279 } 00280 } 00281 00282 private: 00283 const std::size_t m_maxNumEntries; 00284 const std::size_t m_maxNumInvalidKeys; 00285 00286 std::atomic_size_t m_numEntries{0}; 00287 std::atomic_size_t m_numInvalidKeys{0}; 00288 00289 tbb::concurrent_hash_map<uint64_t, IMPLPtr> m_entries; 00290 tbb::concurrent_hash_map<uint64_t, bool /*unused*/> m_invalidKeys; 00291 00292 tbb::concurrent_queue<uint64_t> m_entriesQueue; 00293 tbb::concurrent_queue<uint64_t> m_invalidKeysQueue; 00294 }; 00295 #endif 00296 00350 template <class T, 00351 class PointerT = 00352 typename std::shared_ptr<T>, 00353 #ifdef ATTRIBUTEKEYEDCACHE_NO_TBB 00354 class Base = AttributeKeyedCacheMutex<T, PointerT> > 00355 #else 00356 class Base = AttributeKeyedCacheLockFree<T, PointerT> > 00357 #endif 00358 class AttributeKeyedCache : public Base 00359 { 00360 public: 00361 typedef PointerT IMPLPtr; 00362 00375 AttributeKeyedCache(const std::size_t maxNumEntries = 0xffffffff, 00376 const std::size_t maxNumInvalidKeys = 0xffffffff) 00377 : Base(maxNumEntries, maxNumInvalidKeys) 00378 { 00379 } 00380 00381 virtual ~AttributeKeyedCache() {} 00382 00392 IMPLPtr getValue(const FnAttribute::Attribute& iAttr) 00393 { 00394 return Base::getValue(iAttr); 00395 } 00396 00400 void clear() { Base::clear(); } 00401 00402 protected: 00419 virtual IMPLPtr createValue(const FnAttribute::Attribute& iAttr) = 0; 00420 }; 00421 00425 } 00426 FNGEOLIBUTIL_NAMESPACE_EXIT 00427 00428 #endif // INCLUDED_FNGEOLIBUTIL_ATTRIBUTEKEYEDCACHE_H
1.7.3