diff --git a/fi/include/reverse_purge_hash_map_impl.hpp b/fi/include/reverse_purge_hash_map_impl.hpp index fa84c9e6..73fb803e 100644 --- a/fi/include/reverse_purge_hash_map_impl.hpp +++ b/fi/include/reverse_purge_hash_map_impl.hpp @@ -353,8 +353,9 @@ V reverse_purge_hash_map::purge() { const uint32_t limit = std::min(MAX_SAMPLE_SIZE, num_active_); uint32_t num_samples = 0; uint32_t i = 0; - AllocV av(allocator_); - V* samples = av.allocate(limit); + // A temporary of at most MAX_SAMPLE_SIZE values: on the stack rather than from the allocator, which need not be + // able to give memory back (e.g. an arena) + V samples[MAX_SAMPLE_SIZE]; while (num_samples < limit) { if (is_active(i)) { samples[num_samples++] = values_[i]; @@ -363,7 +364,6 @@ V reverse_purge_hash_map::purge() { } std::nth_element(samples, samples+ (num_samples / 2), samples + num_samples); const V median = samples[num_samples / 2]; - av.deallocate(samples, limit); subtract_and_keep_positive_only(median); return median; } diff --git a/fi/test/frequent_items_sketch_custom_type_test.cpp b/fi/test/frequent_items_sketch_custom_type_test.cpp index 6d077da4..85e0d6ba 100644 --- a/fi/test/frequent_items_sketch_custom_type_test.cpp +++ b/fi/test/frequent_items_sketch_custom_type_test.cpp @@ -98,4 +98,35 @@ TEST_CASE("frequent items: negative weight", "[frequent_items_sketch]") { REQUIRE(test_allocator_total_bytes == 0); } +// An allocator that counts its allocate() calls. +static long long counting_allocations = 0; + +template +struct counting_allocator { + using value_type = T; + counting_allocator() = default; + template + counting_allocator(const counting_allocator&) {} + T* allocate(std::size_t n) { + ++counting_allocations; + return static_cast(::operator new(n * sizeof(T))); + } + void deallocate(T* p, std::size_t) { ::operator delete(p); } +}; +template +bool operator==(const counting_allocator&, const counting_allocator&) { return true; } +template +bool operator!=(const counting_allocator&, const counting_allocator&) { return false; } + +TEST_CASE("frequent items: a purge does not allocate", "[frequent_items_sketch]") { + using sketch = frequent_items_sketch, std::equal_to, counting_allocator>; + // start the map at its maximum size, so it never grows and only purges + sketch s(5, 5); + for (int i = 0; i < 1000; ++i) s.update(i); + const long long before = counting_allocations; + for (int i = 1000; i < 100000; ++i) s.update(i); // thousands of purges + REQUIRE(counting_allocations == before); + REQUIRE(s.get_total_weight() == 100000); +} + } /* namespace datasketches */