Skip to content

Improve performance of attribute.NewSet #7743

@dashpole

Description

@dashpole

I was going to write a blog about the metric SDK optimization work, and was working on updating https://github.com/promlabs/prometheus-otel-benchmarks to demonstrate the improvements. I tried adding a benchmark that follows our best practices for performance in promlabs/prometheus-otel-benchmarks@2c01c31, but I still see an allocation in attribute.NewSet.

I confirmed that is the case in the BenchmarkNewSet benchmark here:

BenchmarkNewSet                  	  233108	       503.3 ns/op	     448 B/op	       1 allocs/op

Unfortunately, this mostly makes the optimizations in the metrics SDK a bit irrelevant. A counter increment is now ~60 ns and has no allocations, but creating the attribute set to pass to it is 500ns with an allocation. The SDK can be used in a performant way, but only if the user precomputes all commonly used attribute sets.

Ideas

I was playing around with some ideas, but I haven't been able to get any to work yet.

One idea is to introduce a constructor for attribute.Set that accepts another attribute.Set as storage:

func NewReusedSet(existing *Set, kvs KeyValue...) *Set {}

The idea would be that the user can create (yet another) sync.Pool, and manage the re-use of sets with the same number of underlying attributes. This has the advantage of providing users a way to re-use sets without the risk of a different library causing issues.

Another idea is to add Set.Free():

//  Free releases the underlying storage of the Set to allow it to be re-used by subsequent NewSet calls.
func (s *Set) Free() {}

which puts the set in a pool of sets with the same number of attributes. I prototyped this here: main...dashpole:opentelemetry-go:noalloc_set, but wasn't able to get it to work properly, while still keeping Set comparable.

I'm looking for other ideas as well. I think this is a big enough performance penalty for us to even consider a v2 of the attributes package, if necessary. Currently, writing performant metrics code is quite onerous: https://github.com/open-telemetry/opentelemetry-go/blob/main/CONTRIBUTING.md#attribute-and-option-allocation-management, and this may be an opportunity to address that as well.

"Bound" instruments could also help here, but will miss a number of common use-cases for metrics libraries, so I don't consider that a complete solution.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions