-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Allow linker to perform deadcode elimination for program using Cobra #1956
base: main
Are you sure you want to change the base?
Conversation
This PR exceeds the recommended size of 200 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. |
The CLA thing isn't working for some reason. |
This PR exceeds the recommended size of 200 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. |
Thanks @aarzilli ! |
Unless I made a mistake there shouldn't be any behavior changes (as in, observable from the outside). Happy to explain anything about this if needed. |
Ping? |
1 similar comment
Ping? |
This PR exceeds the recommended size of 200 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. |
This PR exceeds the recommended size of 200 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. |
This PR exceeds the recommended size of 200 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. |
This PR exceeds the recommended size of 200 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. |
This PR exceeds the recommended size of 200 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. |
@aarzilli I apologize for such a long delay, but I had misunderstood the value of this PR. I now realize that it may help all programs using Cobra, so I'm very interested. I'm trying to convince myself that that this change actually has an impact. Here is what I tried.
I would have expected to see the Could you clarify what I should expect? This is the program:
|
One easy way to see the impact of this change is to compile your example program with and without this PR:
This is an extreme example, where the size of the executable is reduced by 38%, but reductions of 10% are realistic. As to your question, saying that deadcode elimination gets disabled is incorrect: in reality it continues to function but in a reduced capacity. Specifically if Your Unused function is not an exported method so, unless it is called by an exported method, it can always be deadcode eliminated. If you want to see the Unused function make it all the way to the executable you have to do something like this:
All of the noinline directives are needed because small functions like this would be removed by the inliner and the fmt.Println call is needed to make the Astruct type itself reachable (if all calls to the methods of Astruct are static the linker can still prove that Unused is unreachable). With these changes you get:
without the PR and:
with the PR. |
Thanks for the explanation @aarzilli, I can now see the benefits. So, with this PR, programs that don't set new templates (usage, help or version) will be able to do dead code elimination fully. What about programs that do modify those templates? If they want to get full usage of dead code elimination they should convert their template use to a go function like you have done, IIUC? My next step for this review is to try to override the help/usage/version in that fashion and see if it works as expected. I believe that using |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great.
I still want to do some testing, but I don't expect any big surprises.
Here are some comments for the PR.
The PR also needs to be rebased.
Thanks again for your patience, I think we can get this in soon.
cobra_test.go
Outdated
dirname = "test_deadcode" | ||
progname = "test_deadcode_elimination" | ||
) | ||
os.Mkdir(dirname, 0770) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm getting a warning here.
Can you change line to _ = os.Mkdir(dirname, 0770)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
cobra_test.go
Outdated
os.Exit(1) | ||
} | ||
} | ||
`), 0660) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm getting a warning here.
Can you change the permissions to 0600
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
cobra_test.go
Outdated
if err != nil { | ||
t.Fatalf("could not write test program: %v", err) | ||
} | ||
defer os.RemoveAll(dirname) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you move this defer to right after the os.Mkdir call to guarantee it gets called even if the t.Fatalf above gets triggered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -222,3 +226,57 @@ func TestRpad(t *testing.T) { | |||
}) | |||
} | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a comment above this test explaining that deadcode elimination is reduced when MethodByName is present which will affect all programs using cobra; so this test make sure we don't introduce code that depends on MethodByName().
Also please put a comment with the URL of this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See if you like what I wrote.
What do you think about updating the documentation in the three sections starting here https://github.com/spf13/cobra/blob/main/site/content/user_guide.md#defining-your-own-help to explain the side-effect of overriding the template, and therefore that it is recommended to set a function instead? |
This PR exceeds the recommended size of 200 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. |
Yes, this is correct.
It imagine should be trivial. |
This PR exceeds the recommended size of 200 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. |
The linter has failed I looked in detail into this and it makes sense. For example, programs that use some k8s libraries will still end up using templates. I’ve tested this with both tanzu and helm and the binary size didn’t change. So I was just hesitant in accepting this because it complicates the Cobra code base and prevents any future use of templates. But it also feels wrong to force any program using cobra to have a dependency on templates. @aarzilli maybe you can convince me. Did you say Delve would be able to reduce its size with this change? |
At my company, most of services see a reduction of their size by 40% to 45% when DCE is enabled.
As for k8s, most of it depends on I think that k8s should not influence the design of other libs. There is a huge go ecosystem outside k8s. |
Cobra, in its default configuration, will execute a template to generate help, usage and version outputs. Text/template execution calls MethodByName and MethodByName disables dead code elimination in the Go linker, therefore all programs that make use of cobra will be linked with dead code elimination disabled, even if they end up replacing the default usage, help and version formatters with a custom function and no actual text/template evaluations are ever made at runtime. Dead code elimination in the linker helps reduce disk space and memory utilization of programs. For example, for the simple example program used by TestDeadcodeElimination 40% of the final executable size is dead code. For a more realistic example, 12% of the size of Delve's executable is deadcode. This PR changes Cobra so that, in its default configuration, it does not automatically inhibit deadcode elimination by: 1. changing Cobra's default behavior to emit output for usage and help using simple Go functions instead of template execution 2. quarantining all calls to template execution into SetUsageTemplate, SetHelpTemplate and SetVersionTemplate so that the linker can statically determine if they are reachable
This PR exceeds the recommended size of 200 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size. |
Delve uses templates (and reflection) in various places but cobra is the only place where it couldn't be easily circumvented. I think you are right that any given large Go program is likely to end up having multiple different dependencies on reflection but cobra itself is not large, nor is it only recommended for large programs. You say "prevents any future use of templates" but I don't think it's true: first if they aren't statically reachable they are allowed, secondly this decision could be reversed if a compelling use case for templates was found. |
Fixes #2015
Cobra, in its default configuration, will execute a template to generate help, usage and version outputs. Text/template execution calls MethodByName and MethodByName disables dead code elimination in the Go linker, therefore all programs that make use of cobra will be linked with dead code elimination disabled, even if they end up replacing the default usage, help and version formatters with a custom function and no actual text/template evaluations are ever made at runtime.
Dead code elimination in the linker helps reduce disk space and memory utilization of programs. For example, for the simple example program used by TestDeadcodeElimination 40% of the final executable size is dead code. For a more realistic example, 12% of the size of Delve's executable is deadcode.
This PR changes Cobra so that, in its default configuration, it does not automatically inhibit deadcode elimination by: