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: optimize program running speed #46030

Open
aimuz opened this issue May 7, 2021 · 2 comments
Open

cmd/compile: optimize program running speed #46030

aimuz opened this issue May 7, 2021 · 2 comments

Comments

@aimuz
Copy link

@aimuz aimuz commented May 7, 2021

This is currently an idea, an idea to optimize the operation of the program. It may not be rigorous. I hope to rewrite the code to achieve optimal performance during the compilation phase. We all know that the most commonly used optimization method is splicing, not fmt.Printf or fmt.Sprintf.

But using fmt.Printf or fmt.Sprintf can achieve extremely high readability. So I was wondering whether it is possible to rewrite fmt.Sprintf into splicing during the compilation phase. E.g

msg := fmt.Sprintf("now time: %s", time.Now())
// After rewriting 
msg := "now time:" + time.Now().String()

ok := false
msg := fmt.Sprintf("hello world: %t", ok)
// After rewriting 
msg := "hello world:" + strconv.FormatBool(ok)


type Int int
func (i Int) String() string {
	return fmt.Sprintf("%d",i)
}
func main() {
	ok := false
	number := 100
	number2 := Int(100)
	msg :=  fmt.Sprintf("hello world: %t \nnumer: %d \nnumer2: %d number2: %s", ok, number, number2, number2)
}

// After rewriting 
type Int int
func (i Int) String() string {
	return strconv.FormatInt(int64(i), 10)
}
func main() {
	ok := false
	number := 100
	number2 := Int(100)
	msg := "hello world: " + strconv.FormatBool(ok) + " \nnumer: " + strconv.FormatInt(int64(number), 10) + " \nnumer2: " + strconv.FormatInt(int64(number2), 10) + " number2: " + number2.String()

}

The above is a preliminary idea of mine. I think this optimization can greatly improve performance without changing the code. Welcome to discuss

A simple benchmark I did

func BenchmarkPrintf(b *testing.B) {
	ok := false
	number := 100
	number2 := Int(100)
	var msg string
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		msg = fmt.Sprintf("hello world: %t \nnumer: %d \nnumer2: %d number2: %s", ok, number, number2, number2)
	}
	_ = msg
}

func BenchmarkStrconv(b *testing.B) {
	ok := false
	number := 100
	number2 := Int(100)
	var msg string
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		msg = "hello world: " + strconv.FormatBool(ok) + " \nnumer: " + strconv.FormatInt(int64(number), 10) + " \nnumer2: " + strconv.FormatInt(int64(number2), 10) + " number2: " + number2.String()
	}
	_ = msg
}
BenchmarkPrintf
BenchmarkPrintf-12       3365628               342.4 ns/op            67 B/op          2 allocs/op
BenchmarkStrconv
BenchmarkStrconv-12      7946673               145.9 ns/op            73 B/op          4 allocs/op
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented May 8, 2021

Note that BenchmarkStrconv reports 4 allocations per operation while BenchmarkPrintf reports 2 allocations. Even though BenchmarkStrconv runs faster, for the average program the extra two allocations will take more CPU time over all as the garbage collector has to work harder.

I think that this idea may be beneficial for format strings that contain only %s. I think it is unlikely to be beneficial if any other formatting directives are used.

Loading

@ianlancetaylor ianlancetaylor changed the title compile: optimize program running speed cmd/compile: optimize program running speed May 8, 2021
@ianlancetaylor ianlancetaylor added this to the Unplanned milestone May 8, 2021
@martisch
Copy link
Contributor

@martisch martisch commented May 10, 2021

Apart from not always being faster there are other reasons to avoid this kind of rewrite:

  • changing the code of fmt package without changing the compiler will result in behaviour that does not align with programmer expectation that changed the fmt code
  • if someone copies fmt into a new package (e.g. for small changes) there will be a performance loss even while it is potentially the same code being executed (that is why it is prefered for the compiler to match a pattern instead of a specific function)
  • if one of the called functions panics the stack trace will be different then from then expected from the fmt package.
  • the behaviour of String methods will too since fmt with recover the panic and print a string for it and the above code does not
  • In general the rewrite will need to be much more complex and needs to honor Stringer GoStringer and other method invocations on the types/interfaces.
  • it adds complexity to the compiler while it mostly will only support very narrow use cases (or otherwise add even more complexity)
  • adds additional test complexity to make sure both compiler generated code and fmt output are the same

Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
3 participants