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: math operation can be simplified for performance #45760

Closed
Jacalz opened this issue Apr 25, 2021 · 5 comments
Closed

cmd/compile: math operation can be simplified for performance #45760

Jacalz opened this issue Apr 25, 2021 · 5 comments

Comments

@Jacalz
Copy link
Contributor

@Jacalz Jacalz commented Apr 25, 2021

What version of Go are you using (go version)?

I tested with Go 1.15 and "tip" from https://go.godbolt.org.

Does this issue reproduce with the latest release?

I have not tested Go 1.16, but it most likely will due to "tip" (Go 1.17 in the making) shows the same thing.

What operating system and processor architecture are you using (go env)?

The amd64 architecture over on https://go.godbolt.org.

What did you do?

Math rules (order of operation) says that a * b + a * c == a * (b + c) which also applies for a * b + a * c + a * d == a * (b + c + d) and so on . This means that the same calculation can be done with less multiplications and more additions. It might not be a huge difference, but I would assume that the compiler would be able to optimize away the left part to the right part to save some multiplications, but that seems to not be the case.

This math simplification seems like it is being made correctly for int https://go.godbolt.org/z/aEK63sPvj (even when doing the same thing but with four variables https://go.godbolt.org/z/es1561rqa), but not for float32 and float64 https://go.godbolt.org/z/q31ErddaP.

What did you expect to see?

I had expected that the output would have been simplified to the following (ignore function name).

func MultiplySimple(a, b, c float32) float32 {
    return a * (b + c)
}
text    "".MultiplySimple(SB), NOSPLIT|ABIInternal, $0-16
        funcdata        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        funcdata        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        funcdata        $5, "".MultiplySimple.arginfo1(SB)
        addss   X2, X1
        mulss   X1, X0
        ret

What did you see instead?

I saw the two multiplications and one add, instead of two adds and one multiplication.

func MultiplyExpensive(a, b, c float32) float32 {
    return a * b + a * c
}

I got the following assembly output:

text    "".MultiplyExpensive(SB), NOSPLIT|ABIInternal, $0-16
        funcdata        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        funcdata        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        funcdata        $5, "".MultiplyExpensive.arginfo1(SB)
        mulss   X0, X1
        mulss   X2, X0
        addss   X1, X0
        ret
@Jacalz
Copy link
Contributor Author

@Jacalz Jacalz commented Apr 25, 2021

Feel free to change the title of the issue. I could not come up with anything better.

Loading

@ALTree
Copy link
Member

@ALTree ALTree commented Apr 25, 2021

Floating point arithmetic is not distributive:

https://play.golang.org/p/F7yEs9H4DxC

Loading

@ALTree
Copy link
Member

@ALTree ALTree commented Apr 25, 2021

In fact, write the same program in C and you'll see that for doubles GCC generates 2 multiplications for the second snippet (like Go does), unless you pass --ffast-math, which disables strict IEEE-754 arithmetic.

Loading

@randall77
Copy link
Contributor

@randall77 randall77 commented Apr 25, 2021

@ALTree is right, this optimization is not allowed for floats. Rounding occurs at different places which can affect the final result.

Go does have some leeway in the spec to do optimizations kind of like this one:

An implementation may combine multiple floating-point operations into a single fused operation, possibly across statements, and produce a result that differs from the value obtained by executing and rounding the instructions individually.

But that only allows dropping a rounding, not moving it around.

Loading

@randall77 randall77 closed this Apr 25, 2021
@Jacalz
Copy link
Contributor Author

@Jacalz Jacalz commented Apr 25, 2021

Alright, my bad. I've Leary something new today. I have been doing way too much math and did not have enough understanding of rounding to realise that it would produce different results. Thanks for correcting me 🙂

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
4 participants