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>
|
@proost Since noone seems to have responded to the emailing list I sent about the API change, can we merge this? It's not a change to any existing API, it's just adding a new read-only method, so it won't mess it other peoples existing code paths. |
|
@apache/datasketches-committers |
|
Thank you for the suggestion @stojkomilos and for the review @proost ! Could you explain more about the intended use-case? I think Unsure we need union parity here because union clips its result to k hashes as its accumulating state has to stay bounded across arbitrarily many merges. An update sketch does not have this constraint. It holds up to ~15/16(2k) values between rebuilds, and |
leerho
left a comment
There was a problem hiding this comment.
I don't approve of this -- at least until I get a better understanding of why it is needed.
|
I'm not sure I understand the motivation for this. The role of the UpdateSketch is to allow updating while retaining as much accuracy as possible. Reducing the retained entries to K significantly reduces accuracy. The role of the CompactSketch is to maintain that accuracy in an immutable and smaller form. Compared to the frequency of updates, obtaining a sketch in its compact form is normally far less frequent. Yet you complain that it is slow. That implies to me you must be doing this operation frequently. I am curious, why? Are you keeping a large number of reduced accuracy sketches around? If you subsequently merge these, your merge accuracy will be reduced also. Why in the world would you want to do that? The motivation for reducing the result to K on the ThetaUnion::getResult() is for a whole different reason. After unioning a number of theta sketches and before obtaining any 'result' or answer from the union operation, the retained entries MUST be reduced to K (actually MinK) in order to preserve order insensitivity and accuracy of the merge operation itself. And that is the role of getResult(). Not doing that will result in a dramatic increase in the variance of the estimate, basically throwing the accuracy guarantees out the window. This is why the ThetaUnion does not have a getEstimate(). To attempt to do that without reducing the retained entries to K first, would be a disaster. And why the ThetaSketch (in Java) does not provide a public trim() operation. I do note that there is a public trim() operation in the C++ version of theta_sketch.hpp, but I would argue that it is in the wrong place or should be exclusively used by the Union operations. I'm not convinced that "getResult()" makes any sense for the UpdateSketch, because the UpdateSketch itself IS the result -- for subsequent set operations or for obtaining the current estimate. |
|
replying to @leerho
TLDR - I claim that this does have a use case (proven by a example - use case for my company), and I can change the name to anything you want that would be more fitting, like getCompactTrimmed() If you agree, can we reopen the PR? |
|
reply to @c-dickens
|
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: