Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace hdr_hist with log_hist #12460

Merged
merged 5 commits into from
Aug 3, 2023
Merged

Conversation

ballard26
Copy link
Contributor

@ballard26 ballard26 commented Jul 26, 2023

This PR replaces all but one use-case of hdr_hist with log_hist. src/v/cluster/self_test can't be converted to log_hist yet as log_hist doesn't support generating quantiles.

This replacement reduces per-histogram memory usage from potentially hundreds kilobytes to 208 bytes. It also reduces the time to record a value to the histogram by about half.

Public metric histograms generated with;

ssx::metrics::report_default_histogram(hdr_hist);

are now replaced with;

log_hist.public_histogram_logform();

which both produce the same Prometheus histogram given that the same values were recorded to each. Hence it is a drop in replacement with no noticeable effects in the metrics.

The replacement for internal histograms, however, does not produce identical Prometheus histograms as what it replaces. In this case this;

hdr_hist.seastar_histogram_logform();

is now replaced with;

log_hist.internal_histogram_logform();

There are a couple differences between the two Prometheus histograms. The first is that the upper bound for the first bucket will be 8 instead of 10 now. The second is that buckets will now have upper bounds of 2^n -1 , hence comparing [replacement, current] the buckets are now [8, 10], [16, 20], [32, 41], [64, 83], [128, 167], [256, 335]....

This shouldn't be a huge problem as Prometheus supports in-frequent bucket bound changes. And since a upgrade necessitates a restart histogram values will be unstable anyway.

Beyond this the PR also refactors log_hist to add a couple of things;

  • Outputting a seastar metrics public histogram from log_hist_internal this allows us to share a single internal histogram for both public and internal metrics
  • Pausing auto latency measures. This allows us to avoid including purposefully added latencies (like the debounce in the fetch handler) in the overall latency.
  • A benchmark comparing hdr_hist::record to log_hist:record

Backports Required

  • none - not a bug fix
  • none - this is a backport
  • none - issue does not exist in previous branches
  • none - papercut/not impactful enough to backport
  • v23.2.x
  • v23.1.x
  • v22.3.x

Release Notes

  • none

@ballard26 ballard26 changed the title log_hist improvements log_hist improvements Jul 26, 2023
src/v/utils/log_hist.cc Show resolved Hide resolved
src/v/utils/log_hist.h Outdated Show resolved Hide resolved
@ballard26 ballard26 changed the title log_hist improvements Replace hdr_hist with log_hist Jul 27, 2023
PERF_TEST(log_hist, record) {
log_hist_internal h;
perf_tests::start_measuring_time();
#pragma nounroll
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw I think nounroll doesn't actually work on GCC

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a bummer... is there anything that works on clang and gcc?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't use gcc, so this isn't a concern? does it work on clang? can you wrap the loop in do_not_optimize?

@@ -185,7 +185,7 @@ class log_hist : public log_hist_base {
*/
void record(uint64_t val) {
_sample_sum += val;
const int i = std::clamp(
const unsigned i = std::clamp(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, how did you arrive at this being the cause?

Copy link
Contributor Author

@ballard26 ballard26 Jul 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The instruction was sign extending the register holding i into the register used to index the vector. After I switched it to unsigned clang used the register storing i to index into the vector without moving it to a different register. I guess the unsigned was enough to let clang know that i isn't going to be negative and won't need any sign extension when converting to a 64 bit index into the vector.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l33t

seastar::metrics::histogram internal_histogram_logform() const;

protected:
int _number_of_buckets;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks unused.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right you are, will remove.


protected:
int _number_of_buckets;
uint64_t _first_bucket_upper_bound;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: This could probably be a template parameter, with explicit template instantiation for the couple of used values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, will switch this over to explicit template instantiation.

protected:
int _number_of_buckets;
uint64_t _first_bucket_upper_bound;
std::vector<uint64_t> _counts;
Copy link
Member

@BenPope BenPope Jul 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: With some more templating, this could be a std::array, which takes the runtime of the benchmark from ~128ms to ~110ms.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Will make a couple changes to the commit above and add it to the PR with you marked as a co-author.

Copy link
Member

@BenPope BenPope left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be tempted to switch it around a bit:

pick caf95eb834 utils: remove unneeded movslq from record function
pick eab354af54 utils: refactor seastar_histogram_logform to allow for conversions from log_hist_internal to a public seastar hist
f 0c848330c9 utils/log_hist: Use std::array
f ee75bdb32f utils: remove log_hist_base
pick af9b60e904 utils: allow for pausing auto latency measurements in log_hist
pick 68b4bbf212 utils: add histogram benchmark
pick 0d5e0ec328 treewide: replace hdr_hist with log_hist

@emaxerrno
Copy link
Contributor

@ballard26 - i assume this comes with a vtools patch to remove hdrhist - i don't see the cmake changes.

@ballard26
Copy link
Contributor Author

@ballard26 - i assume this comes with a vtools patch to remove hdrhist - i don't see the cmake changes.

There is still one outstanding use of hdr_hist in src/v/cluster/self_test. Where it's being used to generate quantiles for a statistical summary. I'll be adding support to log_hist for generating quantiles this coming week. At which point I'll get a few PRs up to remove hdr_hist from RP and both of our build systems(public and vtools) entirely .

This reduces the memory required to store a histogram from kilobytes to
208 bytes. It also speeds up recording to the histogram by 2x.
Copy link
Member

@dotnwat dotnwat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

src/v/kafka/server/handlers/handler_probe.cc Show resolved Hide resolved
src/v/utils/log_hist.cc Show resolved Hide resolved
PERF_TEST(log_hist, record) {
log_hist_internal h;
perf_tests::start_measuring_time();
#pragma nounroll
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't use gcc, so this isn't a concern? does it work on clang? can you wrap the loop in do_not_optimize?

@dotnwat
Copy link
Member

dotnwat commented Aug 3, 2023

the linter error seems like a false positive i ran format locally on this pr and it seems fine.

@dotnwat dotnwat merged commit b050ac4 into redpanda-data:dev Aug 3, 2023
16 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants