Skip to content
This repository was archived by the owner on Aug 23, 2023. It is now read-only.

Improve performance of SetTags #1158

Merged
merged 5 commits into from
Dec 20, 2018
Merged

Conversation

shanson7
Copy link
Collaborator

@shanson7 shanson7 commented Dec 1, 2018

While debugging slowness in groupByTags, the performance of SetTags stood out. I don't believe this was the primary source of slowness in my case, but it certainly could be a contributing factor, since this is called for every series that is processed via render. Aside from replacing SplitN with IndexByte, I also only allocate a new map if the existing one is nil. I ran the benchmark in two modes, one where I leave the Tags and one where I nil them out.

Here's the benchcmp when s.Tags == nil

benchmark                             old ns/op     new ns/op     delta
BenchmarkSetTags_00tags_00chars-8     272           193           -29.04%
BenchmarkSetTags_20tags_32chars-8     3725          1798          -51.73%

benchmark                             old MB/s     new MB/s     speedup
BenchmarkSetTags_00tags_00chars-8     51.36        72.48        1.41x
BenchmarkSetTags_20tags_32chars-8     358.05       741.88       2.07x

benchmark                             old allocs     new allocs     delta
BenchmarkSetTags_00tags_00chars-8     3              2              -33.33%
BenchmarkSetTags_20tags_32chars-8     23             2              -91.30%

benchmark                             old bytes     new bytes     delta
BenchmarkSetTags_00tags_00chars-8     352           336           -4.55%
BenchmarkSetTags_20tags_32chars-8     2256          1264          -43.97%

and here is when we reuse the Tags:

benchmark                             old ns/op     new ns/op     delta
BenchmarkSetTags_00tags_00chars-8     272           99.3          -63.49%
BenchmarkSetTags_20tags_32chars-8     3725          2081          -44.13%

benchmark                             old MB/s     new MB/s     speedup
BenchmarkSetTags_00tags_00chars-8     51.36        140.92       2.74x
BenchmarkSetTags_20tags_32chars-8     358.05       640.78       1.79x

benchmark                             old allocs     new allocs     delta
BenchmarkSetTags_00tags_00chars-8     3              0              -100.00%
BenchmarkSetTags_20tags_32chars-8     23             0              -100.00%

benchmark                             old bytes     new bytes     delta
BenchmarkSetTags_00tags_00chars-8     352           0             -100.00%
BenchmarkSetTags_20tags_32chars-8     2256          0             -100.00%

With a lot of tags, the cost of clearing the map is higher than reallocating, but this was go 1.10.3 and I know go 1.11 has optimized map clearing.

{
in: Series{
Target: "a;biglongtagkeyhere=andithasabiglongtagvaluetoo;c=d",
},
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

in.Target = in.Target + randString(tagValueLength)
}

b.ReportAllocs()
Copy link
Contributor

Choose a reason for hiding this comment

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

interesting. everywhere else we assume if the user wants this, they use the -test.benchmem flag.
i'm hesitant of introducing this here and creating inconsistency with other tests.
is there a clear benefit?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Well, it allows benchmarks that know that allocations are something useful to benchmark to enable them independently of other benchmarks that may run that don't need them. I can remove it though.

Copy link
Contributor

Choose a reason for hiding this comment

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

i suppose it's a fair argument. but "allocations being useful to measure", doesn't that go for most benchmark code, other than perhaps very computationally expensive or io bound stuff maybe.
and i just use the alloc measures typically as a way to give color on changes in the time taken.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Right. I like allocation benchmarks on certain code, because it's less subject to noisy neighbor with local benchmarks. I removed it in my last commit though.

Copy link
Contributor

Choose a reason for hiding this comment

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

ok

Copy link
Contributor

Choose a reason for hiding this comment

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

fyi re noisy neighbours i use cpu isolation.
see https://gist.github.com/Dieterbe/a52c95a9603507670eb39274544ee1a8


if s.Tags == nil {
// +1 for the name tag
s.Tags = make(map[string]string, numTags)
Copy link
Contributor

Choose a reason for hiding this comment

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

don't need to use numTags+1 per the comment?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch. Must have gotten lost during some of my benchmark rounds

@Dieterbe
Copy link
Contributor

With a lot of tags, the cost of clearing the map is higher than reallocating, but this was go 1.10.3 and I know go 1.11 has optimized map clearing.

correct. https://go-review.googlesource.com/c/go/+/110055 should pay off here.

}

index := strings.IndexByte(s.Target, ';')
name := s.Target[0:index]
Copy link
Contributor

Choose a reason for hiding this comment

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

hmm. surprised our qa gofmt script doesn't complain about the needless 0:

s.Tags["name"] = tagSplits[0]

// Do this last to overwrite any invalid "name" tag that might be preset
s.Tags["name"] = name
Copy link
Contributor

Choose a reason for hiding this comment

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

what do you mean with invalid? one specified by the user? maybe we should refer to those as 'illegal' or even clarify as overwrite any "name" tag that may have been illegally specified in the series tags. or actually drop the 'illegal' because i don't think it's written anywhere that it's illegal.

for i := 0; i < numTags; i++ {
in.Target = in.Target + ";" + randString(tagKeyLength)
in.Target = in.Target + "="
in.Target = in.Target + randString(tagValueLength)
Copy link
Contributor

Choose a reason for hiding this comment

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

why not simplify this to

in.Target += ";" + randString(tagKeyLength) + "=" + randString(tagValueLength)

also, we could use https://golang.org/pkg/strings/#Builder here

Copy link
Contributor

Choose a reason for hiding this comment

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

@shanson7 is it deliberate that you are still doing in.Target = in.Target + ... rather then += ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Nope

@Dieterbe
Copy link
Contributor

I ran the benchmark in two modes, one where I leave the Tags and one where I nil them out.

did you do this by uncommenting the nil setting line? instead of that, perhaps we should just have 2 separate benchmarks? to simplify reproducing the stats

Copy link
Contributor

@Dieterbe Dieterbe left a comment

Choose a reason for hiding this comment

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

looks good, but needs some tweaks.

@shanson7
Copy link
Collaborator Author

Upgraded my local golang to 1.11.4 and now the map clearing approach shows it's benefit:

BenchmarkSetTags_20tags_32chars-8          	10000000	      1666 ns/op	 800.46 MB/s	    1264 B/op	       2 allocs/op
BenchmarkSetTags_20tags_32chars_reused-8   	20000000	      1096 ns/op	1216.87 MB/s	     288 B/op	       1 allocs/op

@Dieterbe Dieterbe merged commit 36065b4 into grafana:master Dec 20, 2018
@Dieterbe Dieterbe added this to the vnext milestone Feb 11, 2019
@shanson7 shanson7 deleted the improveSetTags branch March 6, 2019 14:27
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants