Skip to content

trace: use weak in TracerProvider for storing tracers #6655

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

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

yumosx
Copy link
Contributor

@yumosx yumosx commented Apr 13, 2025

#6351

unique name

goos: darwin
goarch: arm64
pkg: go.opentelemetry.io/otel/sdk/trace
cpu: Apple M3
                       │ BenchmarkTracerWithUniqueName_output.txt │     afterUniqueName_output.txt      │
                       │                  sec/op                  │   sec/op     vs base                │
TracerWithUniqueName-8                                389.9n ± 9%   918.1n ± 8%  +135.44% (p=0.002 n=6)

                       │ BenchmarkTracerWithUniqueName_output.txt │ afterUniqueName_output.txt  │
                       │                   B/op                   │    B/op     vs base         │
TracerWithUniqueName-8                                543.5 ± 15%   550.0 ± 0%  ~ (p=1.000 n=6)

                       │ BenchmarkTracerWithUniqueName_output.txt │     afterUniqueName_output.txt      │
                       │                allocs/op                 │  allocs/op   vs base                │
TracerWithUniqueName-8                                 4.000 ± 0%   11.000 ± 0%  +175.00% (p=0.002 n=6)

same name:

goos: darwin
goarch: arm64
pkg: go.opentelemetry.io/otel/sdk/trace
cpu: Apple M3
                     │ BenchmarkTracerWithSameName_output.txt │      afterSameName_output.txt       │
                     │                 sec/op                 │   sec/op     vs base                │
TracerWithSameName-8                              20.84n ± 1%   51.05n ± 1%  +144.88% (p=0.002 n=6)

                     │ BenchmarkTracerWithSameName_output.txt │   afterSameName_output.txt    │
                     │                  B/op                  │    B/op     vs base           │
TracerWithSameName-8                               0.000 ± 0%   0.000 ± 0%  ~ (p=1.000 n=6) ¹
¹ all samples are equal

                     │ BenchmarkTracerWithSameName_output.txt │   afterSameName_output.txt    │
                     │               allocs/op                │ allocs/op   vs base           │
TracerWithSameName-8                               0.000 ± 0%   0.000 ± 0%  ~ (p=1.000 n=6) ¹
¹ all samples are equal

@yumosx
Copy link
Contributor Author

yumosx commented Apr 13, 2025

HI @pellared, I used weak to store the tracer (following the official Go blog's approach), but after performance testing, the results weren't good - performance actually decreased.

Am I using the wrong approach here?

@dmathieu
Copy link
Member

dmathieu commented Apr 15, 2025

The linked issue in the description seems unrelated to this PR.

Note that we won't be able to merge this until 1.25 is released (which is when we will drop support for 1.23).

@yumosx
Copy link
Contributor Author

yumosx commented Apr 15, 2025

The linked issue in the description seems unrelated to this PR.

Sorry, my bad, updated.

Note that we won't be able to merge this until 1.25 is released (which is when we will drop support for 1.23).

I know it, this pr is submitted as a PoC for early review.

as @pellared say

I hope we both learned something useful.

@dmathieu dmathieu marked this pull request as draft April 15, 2025 09:41
@@ -67,7 +69,7 @@ type TracerProvider struct {
embedded.TracerProvider

mu sync.Mutex
namedTracer map[instrumentation.Scope]*tracer
namedTracer sync.Map
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
namedTracer sync.Map
namedTracer sync.Map // map[instrumentation.Scope]weak.Pointer[*tracer]

Annotate the expected type.

provider: p,
instrumentationScope: is,
var tracerPtr *tracer
for {
Copy link
Member

Choose a reason for hiding this comment

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

I know this is coming from https://go.dev/blog/cleanups-and-weak. But using an infinite loop with an unclear break is a code smell.

stp.Tracer(names[i])
}
b.StopTimer()
b.Run("NoWeak, NoCleanup", func(b *testing.B) {
Copy link
Contributor Author

@yumosx yumosx Apr 16, 2025

Choose a reason for hiding this comment

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

I'm not sure if this test method is correct or reasonable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If this tests are reasonable, weak+cleanup works better based on the results.

BenchmarkTracerCleanup/WithWeakAndCleanup-8         	  691965	      1630 ns/op	     592 B/op	      13 allocs/op
BenchmarkTracerCleanup/NoWeak,_NoCleanup
BenchmarkTracerCleanup/NoWeak,_NoCleanup-8          	  304090	     91877 ns/op	     253 B/op	       5 allocs/op

@pellared
Copy link
Member

pellared commented Apr 17, 2025

Hi, currently I have no time to review this PR, but I just want to call out that use of weak would not "improve" the performance (on the hot-path). It will only give the garbage collector the possibility to remove tracers that are not referenced by the user. So the total used memory should be lower (after garbage collection) when tracers are "used and forgotten" by the user (caller).

@yumosx
Copy link
Contributor Author

yumosx commented Apr 17, 2025

use of weak would not "improve" the performance (on the hot-path). It will only give the garbage collector the possibility to remove tracers that are not referenced by the user.

Yes, this is right, The initial Benchmark test show map[instrument.Scope]*tracer performs better than weak.Pointer[tracer].

So the total used memory should be lower (after garbage collection) when tracers are "used and forgotten" by the user (caller).

the second test, forcing runtime.GC() shows the weak+cleanup version runs faster than without it.

I think this improvement comes from weak+cleanup lowering GC pressure.

finally, thank you for the insightful reply.

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