Skip to content

cmd/compile: function signature based optimizations #71628

@mateusz834

Description

@mateusz834

It is widely known that passing anything to an interface method call with arguments that contain pointers causes heap allocations of that value. This happens because the compiler’s escape analysis cannot prove that the call would not cause the values to be escaped. Even though we don't know exactly which function is going to be called, there is one thing that we know at the caller site: the signature of the function being called. Using that fact, the compiler could collect all functions with the same function signature, run escape analysis over them and then use the per-signature result to decide whether dynamic dispatch calls need to have the arguments escaped. You can think of this as a fallback. Currently the fallback is to heap allocate, with this approach, if none of the functions with the same signature cause their arguments to escape, then dynamic dispatch calls would not require heap allocation either.

If this was implemented in the compiler I would expect a reduced heap allocations count in most programs, some examples:

  • The byte slice passed to io.Reader/io.Writer would likely not need to be allocated on the heap.
  • Arguments passed to http.Handler to be stack allocated (they are often wrapped, especially the http.ResponseWriter).
  • Log functions in log/slog would not cause allocations, and the optimization in slog.Record would not be really needed anymore:

    go/src/log/slog/logger.go

    Lines 271 to 272 in 215de81

    r := NewRecord(time.Now(), level, msg, pc)
    r.AddAttrs(attrs...)

    // Allocation optimization: an inline array sized to hold
    // the majority of log calls (based on examination of open-source
    // code). It holds the start of the list of Attrs.
    front [nAttrsInline]Attr
    // The number of Attrs in front.
    nFront int
    // The list of Attrs except for those in front.
    // Invariants:
    // - len(back) > 0 iff nFront == len(front)
    // - Unused array elements are zero. Used to detect mistakes.
    back []Attr

    It is also worth noting that log/slog API would likely not need the Enabled() method if this kind of optimization was present:
    type Handler interface {
    // Enabled reports whether the handler handles records at the given level.
    // The handler ignores records whose level is lower.
    // It is called early, before any arguments are processed,
    // to save effort if the log event should be discarded.
    // If called from a Logger method, the first argument is the context
    // passed to that method, or context.Background() if nil was passed
    // or the method does not take a context.
    // The context is passed so Enabled can use its values
    // to make a decision.
    Enabled(context.Context, Level) bool
  • This would reduce the amount of people reaching for sync.Pool (proposal: net/http: add Request.CopyTo #68501)

Obviously, this would not work in all cases and has some drawbacks, it only takes one function to force heap allocation of other dynamic dispatch calls (of functions with same signature). This might easily prevent signatures based solely on basic types from benefiting from this optimization, say: func(b []byte) { go func ( /* do sth with b */ }, so folks might pollute functions with some unused and unneeded parameters just to make this optimization work: func(b []byte, _ ...struct{}). This optimization would be beneficial for signatures that take/return custom (package-local) types, like log/slogs Handle(context.Context, slog.Record) error.

This is just an idea that came to my mind while reading #62653. I'm not even sure whether this change is rational, because changes in the escape analysis results would most likely require the dependencies to be recompiled, changes to the cache and probably some other stuff that I am not even aware of.

The no-copy string -> []byte optimization could also take benefit from this kind of optimization.

CC @golang/compiler

Metadata

Metadata

Assignees

No one assigned

    Labels

    ImplementationIssues describing a semantics-preserving change to the Go implementation.NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.Performancecompiler/runtimeIssues related to the Go compiler and/or runtime.

    Type

    No type

    Projects

    Status

    Todo

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions