/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ART_COMPILER_UTILS_DEDUPE_SET_INL_H_
#define ART_COMPILER_UTILS_DEDUPE_SET_INL_H_
#include "dedupe_set.h"
#include <algorithm>
#include <inttypes.h>
#include <unordered_map>
#include "base/mutex.h"
#include "base/hash_set.h"
#include "base/stl_util.h"
#include "base/stringprintf.h"
#include "base/time_utils.h"
namespace art {
template <typename InKey,
typename StoreKey,
typename Alloc,
typename HashType,
typename HashFunc,
HashType kShard>
struct DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::Stats {
size_t collision_sum = 0u;
size_t collision_max = 0u;
size_t total_probe_distance = 0u;
size_t total_size = 0u;
};
template <typename InKey,
typename StoreKey,
typename Alloc,
typename HashType,
typename HashFunc,
HashType kShard>
class DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::Shard {
public:
Shard(const Alloc& alloc, const std::string& lock_name)
: alloc_(alloc),
lock_name_(lock_name),
lock_(lock_name_.c_str()),
keys_() {
}
~Shard() {
for (const HashedKey<StoreKey>& key : keys_) {
DCHECK(key.Key() != nullptr);
alloc_.Destroy(key.Key());
}
}
const StoreKey* Add(Thread* self, size_t hash, const InKey& in_key) REQUIRES(!lock_) {
MutexLock lock(self, lock_);
HashedKey<InKey> hashed_in_key(hash, &in_key);
auto it = keys_.Find(hashed_in_key);
if (it != keys_.end()) {
DCHECK(it->Key() != nullptr);
return it->Key();
}
const StoreKey* store_key = alloc_.Copy(in_key);
keys_.Insert(HashedKey<StoreKey> { hash, store_key });
return store_key;
}
void UpdateStats(Thread* self, Stats* global_stats) REQUIRES(!lock_) {
// HashSet<> doesn't keep entries ordered by hash, so we actually allocate memory
// for bookkeeping while collecting the stats.
std::unordered_map<HashType, size_t> stats;
{
MutexLock lock(self, lock_);
// Note: The total_probe_distance will be updated with the current state.
// It may have been higher before a re-hash.
global_stats->total_probe_distance += keys_.TotalProbeDistance();
global_stats->total_size += keys_.Size();
for (const HashedKey<StoreKey>& key : keys_) {
auto it = stats.find(key.Hash());
if (it == stats.end()) {
stats.insert({key.Hash(), 1u});
} else {
++it->second;
}
}
}
for (const auto& entry : stats) {
size_t number_of_entries = entry.second;
if (number_of_entries > 1u) {
global_stats->collision_sum += number_of_entries - 1u;
global_stats->collision_max = std::max(global_stats->collision_max, number_of_entries);
}
}
}
private:
template <typename T>
class HashedKey {
public:
HashedKey() : hash_(0u), key_(nullptr) { }
HashedKey(size_t hash, const T* key) : hash_(hash), key_(key) { }
size_t Hash() const {
return hash_;
}
const T* Key() const {
return key_;
}
bool IsEmpty() const {
return Key() == nullptr;
}
void MakeEmpty() {
key_ = nullptr;
}
private:
size_t hash_;
const T* key_;
};
class ShardEmptyFn {
public:
bool IsEmpty(const HashedKey<StoreKey>& key) const {
return key.IsEmpty();
}
void MakeEmpty(HashedKey<StoreKey>& key) {
key.MakeEmpty();
}
};
struct ShardHashFn {
template <typename T>
size_t operator()(const HashedKey<T>& key) const {
return key.Hash();
}
};
struct ShardPred {
typename std::enable_if<!std::is_same<StoreKey, InKey>::value, bool>::type
operator()(const HashedKey<StoreKey>& lhs, const HashedKey<StoreKey>& rhs) const {
DCHECK(lhs.Key() != nullptr);
DCHECK(rhs.Key() != nullptr);
// Rehashing: stored keys are already deduplicated, so we can simply compare key pointers.
return lhs.Key() == rhs.Key();
}
template <typename LeftT, typename RightT>
bool operator()(const HashedKey<LeftT>& lhs, const HashedKey<RightT>& rhs) const {
DCHECK(lhs.Key() != nullptr);
DCHECK(rhs.Key() != nullptr);
return lhs.Hash() == rhs.Hash() &&
lhs.Key()->size() == rhs.Key()->size() &&
std::equal(lhs.Key()->begin(), lhs.Key()->end(), rhs.Key()->begin());
}
};
Alloc alloc_;
const std::string lock_name_;
Mutex lock_;
HashSet<HashedKey<StoreKey>, ShardEmptyFn, ShardHashFn, ShardPred> keys_ GUARDED_BY(lock_);
};
template <typename InKey,
typename StoreKey,
typename Alloc,
typename HashType,
typename HashFunc,
HashType kShard>
const StoreKey* DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::Add(
Thread* self, const InKey& key) {
uint64_t hash_start;
if (kIsDebugBuild) {
hash_start = NanoTime();
}
HashType raw_hash = HashFunc()(key);
if (kIsDebugBuild) {
uint64_t hash_end = NanoTime();
hash_time_ += hash_end - hash_start;
}
HashType shard_hash = raw_hash / kShard;
HashType shard_bin = raw_hash % kShard;
return shards_[shard_bin]->Add(self, shard_hash, key);
}
template <typename InKey,
typename StoreKey,
typename Alloc,
typename HashType,
typename HashFunc,
HashType kShard>
DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::DedupeSet(const char* set_name,
const Alloc& alloc)
: hash_time_(0) {
for (HashType i = 0; i < kShard; ++i) {
std::ostringstream oss;
oss << set_name << " lock " << i;
shards_[i].reset(new Shard(alloc, oss.str()));
}
}
template <typename InKey,
typename StoreKey,
typename Alloc,
typename HashType,
typename HashFunc,
HashType kShard>
DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::~DedupeSet() {
// Everything done by member destructors.
}
template <typename InKey,
typename StoreKey,
typename Alloc,
typename HashType,
typename HashFunc,
HashType kShard>
std::string DedupeSet<InKey, StoreKey, Alloc, HashType, HashFunc, kShard>::DumpStats(
Thread* self) const {
Stats stats;
for (HashType shard = 0; shard < kShard; ++shard) {
shards_[shard]->UpdateStats(self, &stats);
}
return StringPrintf("%zu collisions, %zu max hash collisions, "
"%zu/%zu probe distance, %" PRIu64 " ns hash time",
stats.collision_sum,
stats.collision_max,
stats.total_probe_distance,
stats.total_size,
hash_time_);
}
} // namespace art
#endif // ART_COMPILER_UTILS_DEDUPE_SET_INL_H_