Add update_theta_sketch::get_result() to trim to k in one pass - #515
Add update_theta_sketch::get_result() to trim to k in one pass#515stojkomilos wants to merge 3 commits into
Conversation
Returns a compact_theta_sketch bounded to the nominal size k (2^lg_k), matching the at-most-k guarantee of theta_union::get_result(). It does the nth_element/erase cutback directly on the output vector compact() already allocates, avoiding the throwaway 2k rehash of trim()+compact(). Co-authored-by: Isaac
Matches the ordered = true parameter of theta_union::get_result() and theta_intersection::get_result(); sorts only when ordered is requested. Co-authored-by: Isaac
301e495 to
c174f89
Compare
proost
left a comment
There was a problem hiding this comment.
Two problems
-
As you can see, Changing API need discussion first: https://github.com/apache/datasketches-cpp/blob/master/CONTRIBUTING.md#getting-your-proposed-changes-accepted
-
It is technically wrong.
TEST_CASE("theta sketch: get_result silently destroys exact mode", "[theta_sketch]") {
const uint32_t k = 1 << theta_constants::DEFAULT_LG_K; // 4096
const int n = 5000;
update_theta_sketch sketch = update_theta_sketch::builder().build();
for (int i = 0; i < n; i++) sketch.update(i);
REQUIRE_FALSE(sketch.is_estimation_mode());
REQUIRE(sketch.get_num_retained() == n);
REQUIRE(sketch.get_num_retained() > k);
REQUIRE(sketch.get_estimate() == Approx(5000.0));
REQUIRE(sketch.get_lower_bound(2) == sketch.get_upper_bound(2));
// --- control: compact() preserves exactness ---
compact_theta_sketch exact = sketch.compact();
REQUIRE_FALSE(exact.is_estimation_mode());
REQUIRE(exact.get_num_retained() == n);
REQUIRE(exact.get_lower_bound(2) == exact.get_upper_bound(2));
// --- branch: get_result() trades the exact answer for an estimate ---
compact_theta_sketch result = sketch.get_result();
REQUIRE(result.is_estimation_mode()); // was exact, now estimating
REQUIRE(result.get_num_retained() == k);
REQUIRE(result.get_theta64() < theta_constants::MAX_THETA);
REQUIRE(result.get_estimate() == Approx(5000.0).epsilon(0.05));
REQUIRE(result.get_lower_bound(2) < result.get_upper_bound(2));
REQUIRE(result.get_lower_bound(2) < n);
REQUIRE(result.get_upper_bound(2) > n);
}
Thank you for the response. I will send a mail for the API discussion. As for your 2. point, I don't understand your point? It is is estimate mode yes, that is intentional, that is the purpose (the purpose is to have 2^lg_k=nominal_size entires used, just like .get_result() for theta union will do, so it has the same behaviour for .get_result()) |
|
@stojkomilos not calling to "shrink_to_fit" same reason too? |
|
Co-authored-by: Isaac <no-reply@databricks.com>
What changed
Add
get_result()toupdate_theta_sketch_alloc. It returns acompact_theta_sketchtrimmed to at most the nominal sizek(2^lg_k) in a single pass, without rebuilding the hash table.Background: an update sketch's hash table retains up to
~15/16 * 2kentries between rebuilds, andcompact()intentionally keeps all of them (extra entries below theta improve the estimate). To trim a result onktoday, a caller doestrim()thencompact(), this is too slow and inneificent (does malloc, dealloc).I named this function
get_result()in reference to theta union (and likely intersectino) which gurantee to return a already trimmed result.How tested
get_result trims to k in one pass: builds an 8000-item sketch (retains more thank); asserts the defaultget_result()returns exactlykordered entries matchingtrim()+compact(true)(same theta and retained set), and thatget_result(false)returns the same trimmed set unordered.get_result on empty and below-k sketches: empty stays empty and ordered; a 100-item exact-mode sketch returns untrimmed with all entries, ordered by default.Local run: