Go version

go version go1.24.0 darwin/arm64

Output of go env in your module/workspace:

AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/Users/foo/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/foo/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/85/pvl_f0rs14n1c5n76py38wg40000gn/T/go-build2635512519=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/Users/foo/Repo/memory/go.mod'
GOMODCACHE='/Users/foo/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='darwin'
GOPATH='/Users/foo/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/foo/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.24.0'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

Run simple benchmark like:

func BenchmarkWeakPointer(b *testing.B) {
    l := []int{1, 10, 100, 1000, 10000, 100000, 1000000, 10000000}
    for _, n := range l {
        pointers := make([]weak.Pointer[int], n)

        b.Run(strconv.Itoa(n), func(b *testing.B) {
            for b.Loop() {
                runtime.GC()
            }
        })
        runtime.KeepAlive(pointers)
        runtime.GC()
    }
}

What did you see happen?

The GC cycles takes longer as there are more live weak pointers on the heap.

What did you expect to see?

The whole "Objects that are only pointed to by weak pointers are not considered reachable" narrative leads me to believe that the number of live weak pointers has no influence on the GC. After all, the GC mark phase should be able to ignore all weak pointers if they have no impact on reachability.

If the mark phase of the GC does, in fact, need to consider weak pointers, I believe this should at least be mentioned in the GoDoc for weak pointers. A warning indicating that the GC may take weak pointers into account would be helpful.

Comment From: gabyhelp

Related Issues

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: Jorropo

We can't have zero cost weak pointers because we can't not have any kind of tracking otherwise .Value would not be able to know when to return nil.

Do you want to a CL tweaking the documentation ?

Comment From: mknyszek

From my perspective, "Objects that are only pointed to by weak pointers are not considered reachable" is very clearly about only about reachability and not the implementation. As @Jorropo points out, any weak pointer implementation will require tracking by the GC, even one that doesn't affect your benchmark. (I could write a different benchmark that shows little cost with the current implementation, and a high cost in an alternative implementation -- in the end, I don't think it matters that much to the API docs which are about semantics, and the semantics are already subtle.)

I think the right place for discussing implementation details and performance characteristics is the GC guide (https://go.dev/doc/gc-guide), which now has a section about weak pointers. If you would like to send a patch, feel free (https://go.dev/doc/contribute). It's in the https://go.googlesource.com/website repository under _content/doc.