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

refactor(sumologicextension): use bytes slices and strings.Builder to decrease allocations #530

Merged
merged 2 commits into from
Apr 7, 2022

Conversation

pmalek-sumo
Copy link
Contributor

@pmalek-sumo pmalek-sumo commented Apr 5, 2022

$ benchcmp main.txt pr.txt
benchmark                                          old ns/op     new ns/op     delta
Benchmark_PrometheusFormatter_Metric2String-16     66901         53862         -19.49%
Benchmark_PrometheusFormatter_Metric2String-16     65607         53773         -18.04%
Benchmark_PrometheusFormatter_Metric2String-16     65205         54047         -17.11%

benchmark                                          old allocs     new allocs     delta
Benchmark_PrometheusFormatter_Metric2String-16     696            512            -26.44%
Benchmark_PrometheusFormatter_Metric2String-16     696            512            -26.44%
Benchmark_PrometheusFormatter_Metric2String-16     696            512            -26.44%

benchmark                                          old bytes     new bytes     delta
Benchmark_PrometheusFormatter_Metric2String-16     20326         15793         -22.30%
Benchmark_PrometheusFormatter_Metric2String-16     20341         15785         -22.40%
Benchmark_PrometheusFormatter_Metric2String-16     20315         15811         -22.17%

@pmalek-sumo pmalek-sumo self-assigned this Apr 5, 2022
@github-actions github-actions bot added the go label Apr 5, 2022
@pmalek-sumo pmalek-sumo force-pushed the optimize-prometheus-formatter branch from cc9da42 to 331d639 Compare April 5, 2022 14:06
@pmalek-sumo pmalek-sumo marked this pull request as ready for review April 5, 2022 14:06
@pmalek-sumo pmalek-sumo requested a review from a team as a code owner April 5, 2022 14:06
@pmalek-sumo pmalek-sumo force-pushed the optimize-prometheus-formatter branch from 331d639 to ec60339 Compare April 5, 2022 14:34
@swiatekm
Copy link

swiatekm commented Apr 5, 2022

Have you benchmarked how much of the improvement comes from introducing StringBuilder vs the other changes? The StringBuilder code is much uglier, and intuitively it shouldn't be that much faster than fmt.Sprintf for short strings.

@pmalek-sumo
Copy link
Contributor Author

Have you benchmarked how much of the improvement comes from introducing StringBuilder vs the other changes? The StringBuilder code is much uglier, and intuitively it shouldn't be that much faster than fmt.Sprintf for short strings.

Just those 2 changes introducing strings.Builder in tags2String() contribute ~~20-25% of allocs and similarly in time:

benchmark                                          old ns/op     new ns/op     delta
Benchmark_PrometheusFormatter_Metric2String-16     49478         59523         +20.30%
Benchmark_PrometheusFormatter_Metric2String-16     50142         64352         +28.34%
Benchmark_PrometheusFormatter_Metric2String-16     49223         59849         +21.59%

benchmark                                          old allocs     new allocs     delta
Benchmark_PrometheusFormatter_Metric2String-16     538            674            +25.28%
Benchmark_PrometheusFormatter_Metric2String-16     538            674            +25.28%
Benchmark_PrometheusFormatter_Metric2String-16     538            674            +25.28%

benchmark                                          old bytes     new bytes     delta
Benchmark_PrometheusFormatter_Metric2String-16     16950         19635         +15.84%
Benchmark_PrometheusFormatter_Metric2String-16     16923         19645         +16.08%
Benchmark_PrometheusFormatter_Metric2String-16     16938         19654         +16.03%

@pmalek-sumo pmalek-sumo force-pushed the optimize-prometheus-formatter branch from ec60339 to b3fc6e9 Compare April 5, 2022 16:33
@pmalek-sumo
Copy link
Contributor Author

Another comparison:

	returnValue := make([]string, 0, length)
	mergedAttributes.Range(func(k string, v pdata.AttributeValue) bool {
		key := f.sanitizeKeyBytes([]byte(k))
		value := f.sanitizeValue(v.AsString())

		returnValue = append(
			returnValue,
			fmt.Sprintf(
				`%s="%s"`,
				key, value,
			),
		)
		return true
	})

	return prometheusTags(fmt.Sprintf("{%s}", strings.Join(returnValue, ",")))
goos: darwin
goarch: amd64
pkg: github.com/SumoLogic/sumologic-otel-collector/pkg/exporter/sumologicexporter
cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
Benchmark_PrometheusFormatter_Metric2String
Benchmark_PrometheusFormatter_Metric2String-16             20386             58591 ns/op           19630 B/op        674 allocs/op
Benchmark_PrometheusFormatter_Metric2String-16             20182             59102 ns/op           19668 B/op        674 allocs/op
Benchmark_PrometheusFormatter_Metric2String-16             19946             61476 ns/op           19647 B/op        674 allocs/op
PASS
ok      github.com/SumoLogic/sumologic-otel-collector/pkg/exporter/sumologicexporter    5.821s

using strings.Builder

	returnValue := make([]string, 0, length)
	mergedAttributes.Range(func(k string, v pdata.AttributeValue) bool {
		key := f.sanitizeKeyBytes([]byte(k))
		value := f.sanitizeValue(v.AsString())

		sb := strings.Builder{}
		sb.Grow(len(key) + len(value) + 1 + 2)
		sb.Write(key)
		sb.WriteRune('=')
		sb.WriteRune('"')
		sb.WriteString(value)
		sb.WriteRune('"')

		returnValue = append(
			returnValue,
			fmt.Sprintf(
				"%s", sb.String(),
			),
		)
		return true
	})

	ret := strings.Join(returnValue, ",")
	sb := strings.Builder{}
	sb.Grow(2 + len(ret))
	sb.WriteRune('{')
	sb.WriteString(ret)
	sb.WriteRune('}')

	return prometheusTags(sb.String())
goos: darwin
goarch: amd64
pkg: github.com/SumoLogic/sumologic-otel-collector/pkg/exporter/sumologicexporter
cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
Benchmark_PrometheusFormatter_Metric2String
Benchmark_PrometheusFormatter_Metric2String-16             21086             55722 ns/op           18930 B/op        658 allocs/op
Benchmark_PrometheusFormatter_Metric2String-16             21694             57132 ns/op           18978 B/op        658 allocs/op
Benchmark_PrometheusFormatter_Metric2String-16             21644             58168 ns/op           18953 B/op        658 allocs/op
PASS
ok      github.com/SumoLogic/sumologic-otel-collector/pkg/exporter/sumologicexporter    5.771s

using strings.Builder and no fmt

	returnValue := make([]string, 0, length)
	mergedAttributes.Range(func(k string, v pdata.AttributeValue) bool {
		key := f.sanitizeKeyBytes([]byte(k))
		value := f.sanitizeValue(v.AsString())

		sb := strings.Builder{}
		sb.Grow(len(key) + len(value) + 1 + 2)
		sb.Write(key)
		sb.WriteRune('=')
		sb.WriteRune('"')
		sb.WriteString(value)
		sb.WriteRune('"')

		returnValue = append(
			returnValue,
			sb.String(),
		)
		return true
	})

	ret := strings.Join(returnValue, ",")
	sb := strings.Builder{}
	sb.Grow(2 + len(ret))
	sb.WriteRune('{')
	sb.WriteString(ret)
	sb.WriteRune('}')

	return prometheusTags(sb.String())
goos: darwin
goarch: amd64
pkg: github.com/SumoLogic/sumologic-otel-collector/pkg/exporter/sumologicexporter
cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
Benchmark_PrometheusFormatter_Metric2String
Benchmark_PrometheusFormatter_Metric2String-16             24160             51110 ns/op           16954 B/op        538 allocs/op
Benchmark_PrometheusFormatter_Metric2String-16             23751             50131 ns/op           16942 B/op        538 allocs/op
Benchmark_PrometheusFormatter_Metric2String-16             24018             49481 ns/op           16944 B/op        538 allocs/op
PASS
ok      github.com/SumoLogic/sumologic-otel-collector/pkg/exporter/sumologicexporter    5.389s

@swiatekm
Copy link

swiatekm commented Apr 6, 2022

Pretty significant difference after all, then. Ok, I'm in favor of using the StringBuilders, let's just make it clear why we're doing it this way with comments and possibly moving the key=value formatting to a separate function.

@pmalek-sumo pmalek-sumo force-pushed the optimize-prometheus-formatter branch 2 times, most recently from c73f7b6 to 46df176 Compare April 6, 2022 09:16
@pmalek-sumo
Copy link
Contributor Author

PTAL @swiatekm-sumo

Copy link

@swiatekm swiatekm left a comment

Choose a reason for hiding this comment

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

LGTM. Do you reckon we should add separate unit tests for stringsJoinAndSurround or are we sufficiently covered by existing ones?

@pmalek-sumo
Copy link
Contributor Author

LGTM. Do you reckon we should add separate unit tests for stringsJoinAndSurround or are we sufficiently covered by existing ones?

We could in theory. OTOH it's tested indirectly in other tests.

@pmalek-sumo pmalek-sumo force-pushed the optimize-prometheus-formatter branch from 46df176 to d8fe2f6 Compare April 7, 2022 09:05
// allocations.
sb := strings.Builder{}
// We preallocate space for key, value, equal sign and quotes.
sb.Grow(len(key) + len(value) + 1 + 2)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that it would be more readable to not use hardcoded values in this case
const equalSign string = "="
const quoteSign string = "\""
sb.Grow(len(key) + len(value) + len(equalSign) + len(quoteSign) + len(quoteSign)
This still does not guarantee consistency of allocated buffer and real space used to contain all characters.
One example of fixing it, would be, hiding string builder under higher level builder api with at least two functions for one allocating space and second for building string which would check (at runtime) consistency between allocated space and really used one.

Copy link

Choose a reason for hiding this comment

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

What would be the outcome of that check in your view @pdelewski? Let's say you try to write more data to the buffer than what is allocated, StringBuilder will automatically allocate more space. Should we then panic, print a warning, or something else, instead?

Copy link
Contributor

@pdelewski pdelewski Apr 7, 2022

Choose a reason for hiding this comment

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

It depends if that preallocation is critical for us and if that's the case we can return error or panic, however I don't want to over engineer that, so maybe it would be good to check what is benefit of preallocating buffer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can benchmark this with and without the preallocation if we really want to find the performance difference between the 2 approaches but @swiatekm-sumo has a point that strings.Builder does re-allocate when needed and it's its feature so there's really no point to work around that I'd say.

To address your suggestion @pdelewski we could make those consts but then what type should they be? If we stick to runes then they are always of length 1 and you cannot take rune's length i.e.

r := '['
l := len(r) // <- this won't compile

In that case we'd need to resort to strings but then we'd have to measure if it makes sense from performance standpoint (probably it does).

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to use runes? Are they better, faster in this particular case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It looks like it's basically the same:

This PR:

goos: darwin
goarch: amd64
pkg: github.com/SumoLogic/sumologic-otel-collector/pkg/exporter/sumologicexporter
cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
Benchmark_PrometheusFormatter_Metric2String
Benchmark_PrometheusFormatter_Metric2String-16             22712             51177 ns/op           15821 B/op        512 allocs/op
Benchmark_PrometheusFormatter_Metric2String-16             23038             50360 ns/op           15791 B/op        512 allocs/op
Benchmark_PrometheusFormatter_Metric2String-16             23560             51604 ns/op           15810 B/op        512 allocs/op
PASS
ok      github.com/SumoLogic/sumologic-otel-collector/pkg/exporter/sumologicexporter    5.578s

This approach:

	sb.WriteString("=")
	sb.WriteString(`"`)
	sb.WriteString(value)
	sb.WriteString(`"`)
goos: darwin
goarch: amd64
pkg: github.com/SumoLogic/sumologic-otel-collector/pkg/exporter/sumologicexporter
cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
Benchmark_PrometheusFormatter_Metric2String
Benchmark_PrometheusFormatter_Metric2String-16             23334             50641 ns/op           15804 B/op        512 allocs/op
Benchmark_PrometheusFormatter_Metric2String-16             24022             51114 ns/op           15809 B/op        512 allocs/op
Benchmark_PrometheusFormatter_Metric2String-16             24036             49831 ns/op           15811 B/op        512 allocs/op

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@pdelewski Please take a look now.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would stick to string if you don't mind (it's simpler to read as we don't need to mix different types and we avoid questions why runes are there)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please post your review then :) 👍 / 👎

@pmalek-sumo pmalek-sumo force-pushed the optimize-prometheus-formatter branch from d8fe2f6 to 35271f1 Compare April 7, 2022 13:39
@pmalek-sumo pmalek-sumo force-pushed the optimize-prometheus-formatter branch from 35271f1 to 7371688 Compare April 7, 2022 14:31
@pmalek-sumo pmalek-sumo merged commit 7e4d3df into main Apr 7, 2022
@pmalek-sumo pmalek-sumo deleted the optimize-prometheus-formatter branch April 7, 2022 14:51
kasia-kujawa pushed a commit that referenced this pull request May 13, 2022
… decrease allocations (#530)

* refactor(sumologicextension): add prometheus formatter metrc2string benchmark

* refactor(sumologicextension): use bytes slices and strings.Builder to decrease allocations
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants