-
Notifications
You must be signed in to change notification settings - Fork 39
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
Conversation
pmalek-sumo
commented
Apr 5, 2022
•
edited
Loading
edited
cc9da42
to
331d639
Compare
331d639
to
ec60339
Compare
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
|
ec60339
to
b3fc6e9
Compare
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, ",")))
using 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())
using 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())
|
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. |
c73f7b6
to
46df176
Compare
PTAL @swiatekm-sumo |
There was a problem hiding this 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?
We could in theory. OTOH it's tested indirectly in other tests. |
46df176
to
d8fe2f6
Compare
// allocations. | ||
sb := strings.Builder{} | ||
// We preallocate space for key, value, equal sign and quotes. | ||
sb.Grow(len(key) + len(value) + 1 + 2) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 const
s but then what type should they be? If we stick to rune
s 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).
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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 :) 👍 / 👎
d8fe2f6
to
35271f1
Compare
… decrease allocations
35271f1
to
7371688
Compare
… decrease allocations (#530) * refactor(sumologicextension): add prometheus formatter metrc2string benchmark * refactor(sumologicextension): use bytes slices and strings.Builder to decrease allocations