| // 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 |