1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #ifndef ART_COMPILER_UTILS_DEDUPE_SET_INL_H_ 18 #define ART_COMPILER_UTILS_DEDUPE_SET_INL_H_ 19 20 #include "dedupe_set.h" 21 22 #include <inttypes.h> 23 24 #include <algorithm> 25 #include <unordered_map> 26 27 #include "android-base/stringprintf.h" 28 29 #include "base/hash_set.h" 30 #include "base/mutex.h" 31 #include "base/stl_util.h" 32 #include "base/time_utils.h" 33 34 namespace art { 35 36 template <typename InKey, 37 typename StoreKey, 38 typename Alloc, 39 typename HashType, 40 typename HashFunc, 41 HashType kShard> 42 struct DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::Stats { 43 size_t collision_sum = 0u; 44 size_t collision_max = 0u; 45 size_t total_probe_distance = 0u; 46 size_t total_size = 0u; 47 }; 48 49 template <typename InKey, 50 typename StoreKey, 51 typename Alloc, 52 typename HashType, 53 typename HashFunc, 54 HashType kShard> 55 class DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::Shard { 56 public: 57 Shard(const Alloc& alloc, const std::string& lock_name) 58 : alloc_(alloc), 59 lock_name_(lock_name), 60 lock_(lock_name_.c_str()), 61 keys_() { 62 } 63 64 ~Shard() { 65 for (const HashedKey<StoreKey>& key : keys_) { 66 DCHECK(key.Key() != nullptr); 67 alloc_.Destroy(key.Key()); 68 } 69 } 70 71 const StoreKey* Add(Thread* self, size_t hash, const InKey& in_key) REQUIRES(!lock_) { 72 MutexLock lock(self, lock_); 73 HashedKey<InKey> hashed_in_key(hash, &in_key); 74 auto it = keys_.find(hashed_in_key); 75 if (it != keys_.end()) { 76 DCHECK(it->Key() != nullptr); 77 return it->Key(); 78 } 79 const StoreKey* store_key = alloc_.Copy(in_key); 80 keys_.insert(HashedKey<StoreKey> { hash, store_key }); 81 return store_key; 82 } 83 84 size_t Size(Thread* self) { 85 MutexLock lock(self, lock_); 86 return keys_.size(); 87 } 88 89 void UpdateStats(Thread* self, Stats* global_stats) REQUIRES(!lock_) { 90 // HashSet<> doesn't keep entries ordered by hash, so we actually allocate memory 91 // for bookkeeping while collecting the stats. 92 std::unordered_map<HashType, size_t> stats; 93 { 94 MutexLock lock(self, lock_); 95 // Note: The total_probe_distance will be updated with the current state. 96 // It may have been higher before a re-hash. 97 global_stats->total_probe_distance += keys_.TotalProbeDistance(); 98 global_stats->total_size += keys_.size(); 99 for (const HashedKey<StoreKey>& key : keys_) { 100 auto it = stats.find(key.Hash()); 101 if (it == stats.end()) { 102 stats.insert({key.Hash(), 1u}); 103 } else { 104 ++it->second; 105 } 106 } 107 } 108 for (const auto& entry : stats) { 109 size_t number_of_entries = entry.second; 110 if (number_of_entries > 1u) { 111 global_stats->collision_sum += number_of_entries - 1u; 112 global_stats->collision_max = std::max(global_stats->collision_max, number_of_entries); 113 } 114 } 115 } 116 117 private: 118 template <typename T> 119 class HashedKey { 120 public: 121 HashedKey() : hash_(0u), key_(nullptr) { } 122 HashedKey(size_t hash, const T* key) : hash_(hash), key_(key) { } 123 124 size_t Hash() const { 125 return hash_; 126 } 127 128 const T* Key() const { 129 return key_; 130 } 131 132 bool IsEmpty() const { 133 return Key() == nullptr; 134 } 135 136 void MakeEmpty() { 137 key_ = nullptr; 138 } 139 140 private: 141 size_t hash_; 142 const T* key_; 143 }; 144 145 class ShardEmptyFn { 146 public: 147 bool IsEmpty(const HashedKey<StoreKey>& key) const { 148 return key.IsEmpty(); 149 } 150 151 void MakeEmpty(HashedKey<StoreKey>& key) { 152 key.MakeEmpty(); 153 } 154 }; 155 156 struct ShardHashFn { 157 template <typename T> 158 size_t operator()(const HashedKey<T>& key) const { 159 return key.Hash(); 160 } 161 }; 162 163 struct ShardPred { 164 typename std::enable_if<!std::is_same<StoreKey, InKey>::value, bool>::type 165 operator()(const HashedKey<StoreKey>& lhs, const HashedKey<StoreKey>& rhs) const { 166 DCHECK(lhs.Key() != nullptr); 167 DCHECK(rhs.Key() != nullptr); 168 // Rehashing: stored keys are already deduplicated, so we can simply compare key pointers. 169 return lhs.Key() == rhs.Key(); 170 } 171 172 template <typename LeftT, typename RightT> 173 bool operator()(const HashedKey<LeftT>& lhs, const HashedKey<RightT>& rhs) const { 174 DCHECK(lhs.Key() != nullptr); 175 DCHECK(rhs.Key() != nullptr); 176 return lhs.Hash() == rhs.Hash() && 177 lhs.Key()->size() == rhs.Key()->size() && 178 std::equal(lhs.Key()->begin(), lhs.Key()->end(), rhs.Key()->begin()); 179 } 180 }; 181 182 Alloc alloc_; 183 const std::string lock_name_; 184 Mutex lock_; 185 HashSet<HashedKey<StoreKey>, ShardEmptyFn, ShardHashFn, ShardPred> keys_ GUARDED_BY(lock_); 186 }; 187 188 template <typename InKey, 189 typename StoreKey, 190 typename Alloc, 191 typename HashType, 192 typename HashFunc, 193 HashType kShard> 194 const StoreKey* DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::Add( 195 Thread* self, const InKey& key) { 196 uint64_t hash_start; 197 if (kIsDebugBuild) { 198 hash_start = NanoTime(); 199 } 200 HashType raw_hash = HashFunc()(key); 201 if (kIsDebugBuild) { 202 uint64_t hash_end = NanoTime(); 203 hash_time_ += hash_end - hash_start; 204 } 205 HashType shard_hash = raw_hash / kShard; 206 HashType shard_bin = raw_hash % kShard; 207 return shards_[shard_bin]->Add(self, shard_hash, key); 208 } 209 210 template <typename InKey, 211 typename StoreKey, 212 typename Alloc, 213 typename HashType, 214 typename HashFunc, 215 HashType kShard> 216 DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::DedupeSet(const char* set_name, 217 const Alloc& alloc) 218 : hash_time_(0) { 219 for (HashType i = 0; i < kShard; ++i) { 220 std::ostringstream oss; 221 oss << set_name << " lock " << i; 222 shards_[i].reset(new Shard(alloc, oss.str())); 223 } 224 } 225 226 template <typename InKey, 227 typename StoreKey, 228 typename Alloc, 229 typename HashType, 230 typename HashFunc, 231 HashType kShard> 232 DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::~DedupeSet() { 233 // Everything done by member destructors. 234 } 235 236 template <typename InKey, 237 typename StoreKey, 238 typename Alloc, 239 typename HashType, 240 typename HashFunc, 241 HashType kShard> 242 size_t DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::Size(Thread* self) const { 243 size_t result = 0u; 244 for (const auto& shard : shards_) { 245 result += shard->Size(self); 246 } 247 return result; 248 } 249 250 template <typename InKey, 251 typename StoreKey, 252 typename Alloc, 253 typename HashType, 254 typename HashFunc, 255 HashType kShard> 256 std::string DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::DumpStats( 257 Thread* self) const { 258 Stats stats; 259 for (HashType shard = 0; shard < kShard; ++shard) { 260 shards_[shard]->UpdateStats(self, &stats); 261 } 262 return android::base::StringPrintf("%zu collisions, %zu max hash collisions, " 263 "%zu/%zu probe distance, %" PRIu64 " ns hash time", 264 stats.collision_sum, 265 stats.collision_max, 266 stats.total_probe_distance, 267 stats.total_size, 268 hash_time_); 269 } 270 271 272 } // namespace art 273 274 #endif // ART_COMPILER_UTILS_DEDUPE_SET_INL_H_ 275