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

NormalizeMetricFamilies: Optimize memory usage #1394

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

ethanvc
Copy link

@ethanvc ethanvc commented Nov 27, 2023

my first contribution to prometheus and this mr is going to make code more efficient.

@ArthurSens
Copy link
Member

Hey, thanks for the PR! Could you sign the commits to make CI happy?

I'm assuming the efficiency improvement here is that we don't need to allocate memory for metric families with empty metric arrays, but could you elaborate if that's really what you're improving here?

@ethanvc ethanvc force-pushed the opt_NormalizeMetricFamilies branch from 955a5b3 to 6f666c7 Compare December 12, 2023 23:41
@ethanvc
Copy link
Author

ethanvc commented Dec 12, 2023

Hey, thanks for the PR! Could you sign the commits to make CI happy?

I'm assuming the efficiency improvement here is that we don't need to allocate memory for metric families with empty metric arrays, but could you elaborate if that's really what you're improving here?

Sorry, already signed off.
There are serval minus impovements here:

  1. do not need to allocate memory for names, which is a little large when we have lots of metrics.
  2. do not need to allocate memory for empty metrics, but i think we may have very small amount of them.
  3. do not need to searh the map for every mertrics, which save cpu times.

But i am not too sure if the key for metricFamiliesByName is always equal to MetricFamily.Name, which may crash if Name is nil after this change.

Copy link
Member

@ArthurSens ArthurSens left a comment

Choose a reason for hiding this comment

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

By creating result before pruning metricFamilies with empty metrics, we might be over allocating memory 🤔

Not allocating space for names looks more relevant though, so that's good :)

But i am not too sure if the key for metricFamiliesByName is always equal to MetricFamily.Name, which may crash if Name is nil after this change.

Reading the code now, I can see that we get the metricFamily name from desc.fqName, which can't be nil or empty(See validation[1][2])

@bwplotka bwplotka changed the title optimize memory usage NormalizeMetricFamilies: Optimize memory usage Dec 21, 2023
Copy link
Member

@bwplotka bwplotka left a comment

Choose a reason for hiding this comment

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

Thanks @ethanvc ! BTW what triggered this change? Some production performance issue or just looking at the code?

Generally, it's a premature optimization if we don't have a memory profile suggesting this code path inefficiency matters. It's also might be very ineffective change if we don't do any (at least) microbenchmarks to verify if we worsened or improved the efficiency (learning from my "Efficient Go" book btw) (:

With that preambule, let's check if, at least in theory, this makes sense:

  1. do not need to allocate memory for names, which is a little large when we have lots of metrics.

Makes sense.

  1. do not need to allocate memory for empty metrics, but i think we may have very small amount of them.

Your code still allocates memory for empty metrics, but indeed that should be fine. I don't know of any case where metric family might have empty metric (there might be, just I might not educated here) 🤔

  1. do not need to searh the map for every mertrics, which save cpu times.

Not sure what do you mean by search, maybe you meant sort? I assume mf.Metric == 0 case is near 0% often, so on scale I don't think it matters. Generally you reduced from having 3 for loops to 1 for loop, but those are so small I doubt we will see considerable or any CPU improvement in practice. This is where only good benchmark would tell us.

But i am not too sure if the key for metricFamiliesByName is always equal to MetricFamily.Name, which may crash if Name is nil after this change.
Reading the code now, I can see that we get the metricFamily name from desc.fqName, which can't be nil or empty(See validation[1][2])

Nice, I am not worried about empty or nil case (see my suggestion). I more worried about map key is different than mf.Name. As @ArthurSens verified, this should be the case (for now 🙈 ).

Anyway, I think it's little harm to merge this (with my suggestion) WITHOUT micro-benchmarks, but I wouldn't be too excited here. I would even be not surprised if this function is now slower due to things we might miss (cacheability of the code, branching etc). Making any assumptions on such low level is guessing (:

It should allocate less (without benchmark that's again, just guess), but is it a lot? Even with 1000 metric families (extreme already) for []string that's only 24 + 16*1k bytes, so ~16KB of memory on every scrape (10-60s), easily garbage collected in between (I don' think this function is used more often)

LGTM, with below suggestion to be fixed first. Thanks @ArthurSens for detailed review 💪🏽

prometheus/internal/metric.go Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants