Skip to content
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

cmd/compile: expose what contributes to consuming the inlining budget #65780

Open
jespino opened this issue Feb 18, 2024 · 3 comments
Open

cmd/compile: expose what contributes to consuming the inlining budget #65780

jespino opened this issue Feb 18, 2024 · 3 comments
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Performance
Milestone

Comments

@jespino
Copy link

jespino commented Feb 18, 2024

Proposal Details

Expose what contributes to consuming the inlining budget as a debug flag

Sometimes, understanding why a function is inlined or not or what the main contributors to the function budget consumption are is not easy.

This proposal is to add a -d flag to the go tool compile command that enables traces of budget consumption.

Proof of concept

I already have a POC that does more or less the job and a tool that takes advantage of that to give a summary.

This is an example of this execution:

> go build -gcflags "-d=inlcostreason" hello.go
./hello.go:24:6: inline-function-cost: 123
./hello.go:24:6: inline-function-cost: 0,not inlining too expensive
./hello.go:25:13: inline-cost: 57,non-leaf inlining,0
./hello.go:25:13: inline-cost: 1,node,1
./hello.go:25:13: inline-cost: 1,node,2
./hello.go:25:13: inline-cost: 1,node,6
./hello.go:25:16: inline-cost: 1,node,3
./hello.go:25:16: inline-cost: 1,node,4
./hello.go:25:25: inline-cost: 1,node,5
./hello.go:26:20: inline-cost: 59,inlined function body,7
./hello.go:26:20: inline-cost: 1,node,7
...

This file, passing from a tool that I built, gives you this (showing only part of the output):

...
func usage() {  //  123, total inline cost, not inlining too expensive
	fmt.Fprintf(os.Stderr, "usage: helloserver [options]\n")  //  63, non-leaf inlining, node
	flag.PrintDefaults()  //  60, inlined function body, node
	os.Exit(2)
}
...

To understand what I did, I just added in the right places (where the budget changes) blocks like this:

if base.Debug.InlCostReason != 0 {
    base.WarnfAt(v.lastFunctionPos, "inline-cost: %d,%s,%d", inlineExtraThrowCost, "trow", v.counter)
    v.counter++
}

The counter avoids line deduplication, and the lastFunctionPos avoids showing already inlinable code as part of other functions. But those are implementation details that we need to figure out later the best approach here.

@gopherbot gopherbot added this to the Proposal milestone Feb 18, 2024
@seankhliao seankhliao changed the title proposal: compiler/inliner: expose what contributes to consuming the inlining budget proposal: cmd/compile: expose what contributes to consuming the inlining budget Feb 18, 2024
@seankhliao
Copy link
Member

cc @golang/compiler

@thanm
Copy link
Contributor

thanm commented Feb 20, 2024

Hi @jespino,

Thanks for filing this issue.

I'm going to remove the proposal label, since this type of change deals with the compiler implementation, as opposed to adding new APIs or making modifications to the language. For these sorts of changes there is no need to go through the heavyweight proposal review process (which involves committee meetings, long lead times, etc).

Regarding the specifics: I'm fine with a new debug option, but a couple of things to keep in mind:

First, we (Go compiler team) are making changes to the compiler's internal IR and transforms/phases all the time, and that can include how the inliner evaluates functions to decide whether they are inline candidates. A given IR construct within some function might evaluate to cost 6 in one Go release and then evaluate to 7 or 3 in a subsequent release, because of changes the inliner or changes to the parser or other phases. The implication here is that Go users should not be depending on specific numbers reported by any inline budget debugging tool.

The second thing is that we're actively moving the inliner away from a model in which inlinability is decided as a property of a given function (I like to call this the "inline F everywhere or inline F nowhere" scheme) and towards a model in which the properties of specific call sites get taken into account (meaning that function F may be inlined at some call sites and not at others). The intent here is to encourage "productive" inlines and discourage inlines that just contribute to code bloat. Here is some made-up Go code to illustrate:

package x

import (
	"fmt"
	"os"
)

func Y(x int, q []int) int {
	if len(q) == 0 {
		return x
	}
	v := 0
	for i := range q {
		v += x ^ i
	}
	return v
}

func hasSomeCalls(q, r int, z [][]int) int {
	if q < 0 {
		fmt.Fprintf(os.Stderr, "with Y reporting %d, unexpected bad q value",
			Y(q, z[0]))
		os.Exit(1)
	}
	v := 0
	for i := 0; i < 100000; i++ {
		v += Y(r, z[i&3])
	}
	return v
}

There are two calls to "Y" above. The first call takes place on an error path just before we terminate the program. Inlining "Y" at this call won't have any meaningful impact on performance, it will just make the binary a bit more bloated. The second call to "Y" on the other hand takes place within a loop that we know is going to execute a lot.

We are trying to change the compiler in ways that will discourage inlining at the first call site and encourage inlining at the second call site. To do this we need to move away from the existing scheme of just sizing up the function and declaring it "always inlined" or "never inlined" depending on what the size cutoff is.

The changes we're making to the inliner are currently enabled via GOEXPERIMENT=newinliner; this is an active development branch, so new things are being added to it on tip.

With this in mind, it seems fine to me to add some sort of "-d" flag, and I like the idea of producing an annotate Go source file with inline info in comments. It would be great if your tool could be extended to work with whatever we come up with at the end of the GOEXPERIMENT=newinliner effort as well.

@thanm thanm changed the title proposal: cmd/compile: expose what contributes to consuming the inlining budget cmd/compile: expose what contributes to consuming the inlining budget Feb 20, 2024
@thanm thanm added Performance NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. and removed Proposal labels Feb 20, 2024
@jespino
Copy link
Author

jespino commented Feb 20, 2024

Thanks @thanm that is amazing, I'll get into the code of the new inliner and see if still makes sense to trace what exactly is causing it to be or not inlined, and how to expose that. If there is not an easy to track way of doing it, maybe makes no sense to trace it. But let me investigate and I'll come back and extend or modify the idea.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Performance
Projects
None yet
Development

No branches or pull requests

4 participants