blob: d37a4f6fdfad3b617171b0c2ba37b7aa3f94f22f [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/sampling_heap_profiler/lock_free_address_hash_set.h"
#include <atomic>
#include <bit>
#include <limits>
#include <numeric>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/synchronization/lock.h"
namespace base {
namespace {
// Returns the result of a chi-squared test showing how evenly keys are
// distributed. `bucket_key_counts` is the count of keys stored in each bucket.
double ChiSquared(const std::vector<size_t>& bucket_key_counts) {
// Algorithm taken from
// https://en.wikipedia.org/wiki/Hash_function#Testing_and_measurement:
// "n is the number of keys, m is the number of buckets, and b[j] is the
// number of items in bucket j."
const size_t n =
std::accumulate(bucket_key_counts.begin(), bucket_key_counts.end(), 0u);
const size_t m = bucket_key_counts.size();
DCHECK(m);
const double numerator = std::accumulate(
bucket_key_counts.begin(), bucket_key_counts.end(), 0.0,
[](double sum, size_t b) { return sum + b * (b + 1) / 2.0; });
const double denominator = (n / (2.0 * m)) * (n + 2 * m - 1);
// `denominator` could be 0 if n == 0. An empty set has uniformity 1.0 by
// definition (all buckets have 0 keys).
return denominator ? (numerator / denominator) : 1.0;
}
} // namespace
void* const LockFreeAddressHashSet::kDeletedKey =
reinterpret_cast<void*>(intptr_t{-1});
LockFreeAddressHashSet::LockFreeAddressHashSet(size_t buckets_count,
Lock& lock,
bool multi_key)
: lock_(lock),
buckets_(buckets_count),
bucket_mask_(buckets_count - 1),
multi_key_(multi_key) {
DCHECK(std::has_single_bit(buckets_count));
DCHECK_LE(bucket_mask_, std::numeric_limits<uint32_t>::max());
}
LockFreeAddressHashSet::~LockFreeAddressHashSet() {
for (std::atomic<Node*>& bucket : buckets_) {
Node* node = bucket.load(std::memory_order_relaxed);
while (node) {
Node* next = node->next;
if (multi_key_) {
delete reinterpret_cast<MultiKeyNode*>(node);
} else {
delete reinterpret_cast<SingleKeyNode*>(node);
}
node = next;
}
}
}
void LockFreeAddressHashSet::Insert(void* key) {
lock_->AssertAcquired();
DCHECK_NE(key, nullptr);
DCHECK_NE(key, kDeletedKey);
CHECK(!Contains(key));
++size_;
// Note: There's no need to use std::atomic_compare_exchange here,
// as we do not support concurrent inserts, so values cannot change midair.
std::atomic<Node*>& bucket = buckets_[Hash(key) & bucket_mask_];
Node* node = bucket.load(std::memory_order_relaxed);
// First iterate over the bucket nodes and try to use an empty key slot.
for (; node != nullptr; node = node->next) {
for (KeySlot& key_slot : GetKeySlots(node)) {
void* existing_key = key_slot.load(std::memory_order_relaxed);
if (existing_key == nullptr || existing_key == kDeletedKey) {
key_slot.store(key, std::memory_order_relaxed);
return;
}
}
}
// There are no empty key slots to reuse left in the bucket.
// Create a new node first...
Node* new_node;
if (multi_key_) {
new_node = new MultiKeyNode(key, bucket.load(std::memory_order_relaxed));
} else {
new_node = new SingleKeyNode(key, bucket.load(std::memory_order_relaxed));
}
// ... and then publish the new chain.
bucket.store(new_node, std::memory_order_release);
}
void LockFreeAddressHashSet::Copy(const LockFreeAddressHashSet& other) {
lock_->AssertAcquired();
DCHECK_EQ(0u, size());
for (const std::atomic<Node*>& bucket : other.buckets_) {
for (const Node* node = bucket.load(std::memory_order_relaxed); node;
node = node->next) {
for (const KeySlot& key_slot : other.GetKeySlots(node)) {
void* key = key_slot.load(std::memory_order_relaxed);
if (key != nullptr && key != kDeletedKey) {
Insert(key);
}
}
}
}
}
LockFreeAddressHashSet::BucketStats LockFreeAddressHashSet::GetBucketStats()
const {
lock_->AssertAcquired();
std::vector<size_t> lengths;
lengths.reserve(buckets_.size());
std::vector<size_t> key_counts;
key_counts.reserve(buckets_.size());
for (const std::atomic<Node*>& bucket : buckets_) {
// Bucket length includes all non-null values, including kDeletedKey, since
// they will need to be searched when iterating. Key count only includes
// real keys.
size_t length = 0;
size_t key_count = 0;
for (const Node* node = bucket.load(std::memory_order_relaxed);
node != nullptr; node = node->next) {
for (const KeySlot& key_slot : GetKeySlots(node)) {
void* key = key_slot.load(std::memory_order_relaxed);
if (key == nullptr) {
break;
}
++length;
if (key != kDeletedKey) {
++key_count;
}
}
}
lengths.push_back(length);
key_counts.push_back(key_count);
}
return BucketStats(std::move(lengths), ChiSquared(key_counts));
}
LockFreeAddressHashSet::BucketStats::BucketStats(std::vector<size_t> lengths,
double chi_squared)
: lengths(std::move(lengths)), chi_squared(chi_squared) {}
LockFreeAddressHashSet::BucketStats::~BucketStats() = default;
LockFreeAddressHashSet::BucketStats::BucketStats(const BucketStats&) = default;
LockFreeAddressHashSet::BucketStats&
LockFreeAddressHashSet::BucketStats::operator=(const BucketStats&) = default;
} // namespace base