« BackGo allocation probescattered-thoughts.netSubmitted by blenderob 4 days ago
  • fsmv 4 days ago

    It is very difficult to avoid putting strings on the heap in go. I used the built in escape analysis tools and made sure I only use a constant amount of memory in the loop in my short https://github.com/fsmv/in.go progress bar program.

    The biggest problem is any string you pass as an argument to the fmt functions is moved onto the heap because interface{} is always counted as escaped from the stack (https://github.com/golang/go/issues/8618).

    • typical182 4 days ago

      > The biggest problem is any string you pass as an argument to the fmt functions is moved onto the heap

      FWIW, that's not quite correct. For example, a string literal passed as a fmt argument won't be moved to the heap.

      The upcoming Go 1.25 release has some related improvements that help strings in more cases. See for example https://go.dev/cl/649079.

      • coxley 4 days ago

        > because interface{} is always counted as escaped from the stack

        Not quite - if the function accepting interface{} can be inlined (and other heuristics are groovy), then it won't escape.

        Trivial example but it applies to real-world programs:

            > cat main.go
            package main
            
            import "github.com/google/uuid"
            
            func main() {
                    _ = foo(uuid.NewString())
            }
            
            func foo(s any) string {
                    switch s := s.(type) {
                    case string:
                            _ = "foo:" + s
                    }
                    return ""
            }
            
            # Build with escape analysis
            > go build -gcflags="-m=2" main.go
            # command-line-arguments
            ./main.go:9:6: can inline foo with cost 13 as: func(any) string { switch statement; return "" }
            ./main.go:5:6: can inline main with cost 77 as: func() { _ = foo(uuid.NewString()) }
            ./main.go:6:9: inlining call to foo
            ./main.go:6:24: uuid.NewString() does not escape
            ./main.go:6:9: "foo:" + s does not escape
            ./main.go:9:10: s does not escape
            ./main.go:12:14: "foo:" + s does not escape
      • felixge 4 days ago

        Hacking into the Go runtime with eBPF is definitely fun.

        But for a more long term solution in terms of reliability and overhead, it might be worth raising this as a feature request for the Go runtime itself. Type information could be provided via pprof labels on the allocation profiles.

        • aktau 4 days ago

          Not sure if there is already quorum on what a solution for adding labels to non-point-in-time[^1] profiles like the heap profile without leaking looks like: https://go.dev/issue/23458.

          [^1]: As opposed to profile that collect data only when activated, like the CPU profile. The heap profile is active from the beginning if `MemProfileRate` is set.

        • jasonthorsness 4 days ago

          Interesting... usually you can guess at what is being allocated from the function doing the allocation, but in this case the author was interested in types that are allocated from a ton of locations (spoiler alert: it was strings). Nice use of bpftrace to hack out the information required.

          • undefined 4 days ago
            [deleted]
            • osigurdson 4 days ago

              >> func (thing Thing) String() string { if thing == nil { return nil } str = ... return &str }

              It seems like the "..." of str = ... is the interesting part.

              • _bahd 4 days ago

                [flagged]

                • 90s_dev 4 days ago

                  I forgot to ask, that day that the Go team did an AMA here: did AI have any influence or sway or advice etc in choosing Go over other solutions?