Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

Pure Go math.Abs outperforms assembly version

949 views
Skip to first unread message

Jeremy Jackins

unread,
Oct 28, 2015, 9:31:17 PM10/28/15
to golang-dev
In the math package, there is a pure-go version of abs:

func abs(x float64) float64 {
switch {
case x < 0:
return -x
case x == 0:
return 0 // return correctly abs(-0)
}
return x
}

On my system (darwin/amd64), the assembly version is somewhat faster:

(Pure Go version)
BenchmarkAbs-4 1000000000         2.78 ns/op

(abs_amd64.s version)
BenchmarkAbs-4 1000000000         2.08 ns/op

However, if I replace the switch statement in the pure Go version:

func abs(x float64) float64 {
if x < 0 {
return -x
} else if x == 0 {
return 0 // return correctly abs(-0)
}
return x
}

It is about twice as fast as the assembly version:
BenchmarkAbs-4 2000000000         1.04 ns/op

I assume this is related to inlining. This change speeds up a real program of mine, which calls math.Abs in a hot loop, by about 30%. I don't know what these results look like on other architectures. Would a CR to use the switchless pure Go version, at least on platforms where it's measured to be faster, be considered?

Thanks,
Jeremy

Brad Fitzpatrick

unread,
Oct 28, 2015, 10:24:23 PM10/28/15
to Jeremy Jackins, golang-dev
cmd/compile: functions with switches cannot be inlined 

I'm not aware of any bug about inlining assembly.

--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Robert Griesemer

unread,
Oct 28, 2015, 10:24:48 PM10/28/15
to Jeremy Jackins, golang-dev
I'm not sure that exported functions containing switches are inlined yet, so there may be something else going on.

For that matter, have you tried to implement abs(x) using math.Float64frombits(Float64bits(x) &^ 1<<63) (which should simply clear the sign bit) ?

These functions should be easily inlineable since they basically just play tricks around the type system.

(We may not be able to replace abs with this because of NaNs, but I haven't thought it through yet).

- gri

On Wed, Oct 28, 2015 at 6:31 PM, Jeremy Jackins <jeremy...@gmail.com> wrote:

--

Brendan Tracey

unread,
Oct 28, 2015, 10:30:02 PM10/28/15
to Robert Griesemer, Jeremy Jackins, golang-dev
On Oct 28, 2015, at 8:24 PM, Robert Griesemer <g...@golang.org> wrote:

I'm not sure that exported functions containing switches are inlined yet, so there may be something else going on.

For that matter, have you tried to implement abs(x) using math.Float64frombits(Float64bits(x) &^ 1<<63) (which should simply clear the sign bit) ?

These functions should be easily inlineable since they basically just play tricks around the type system.

(We may not be able to replace abs with this because of NaNs, but I haven't thought it through yet).

The code sample provided is fine with NaN. NaN is not less than 0, and it’s not equal to zero. Can remove the “else” though.

Russ Cox

unread,
Oct 29, 2015, 10:25:14 AM10/29/15
to Brendan Tracey, Robert Griesemer, Jeremy Jackins, golang-dev
Probably the right thing to do is to delete the Abs assembly on all architectures and replace with

func Abs(x float64) float64 {
    return Float64frombits(Float64bits(x)&^(1<<63))
}

This is what the assembly versions all do, and there's no need to write that code in assembly.

Russ

Keith Randall

unread,
Oct 29, 2015, 11:43:53 AM10/29/15
to Russ Cox, Brendan Tracey, Robert Griesemer, Jeremy Jackins, golang-dev
Current assembly:

MOVQ   $(1<<63), BX
MOVQ   BX, X0 // movsd $(-0.0), x0
MOVSD  x+0(FP), X1
ANDNPD X1, X0
MOVSD  X0, ret+8(FP)
RET

Generated by the compiler from Russ' example:

0x0000 00000 (abs.go:5) SUBQ $16, SP
0x0004 00004 (abs.go:5) XORPS X0, X0
0x0007 00007 (abs.go:6) MOVSD "".x+24(FP), X0
0x000d 00013 (abs.go:6) MOVSD X0, math.f·2(SP)
0x0012 00018 (abs.go:6) LEAQ math.f·2(SP), BX
0x0016 00022 (abs.go:6) MOVQ (BX), BX
0x0019 00025 (abs.go:6) MOVQ $9223372036854775807, BP
0x0023 00035 (abs.go:6) ANDQ BP, BX
0x0026 00038 (abs.go:6) MOVQ BX, math.b·2+8(SP)
0x002b 00043 (abs.go:6) XORPS X0, X0
0x002e 00046 (abs.go:6) LEAQ math.b·2+8(SP), BX
0x0033 00051 (abs.go:6) MOVSD (BX), X0
0x0037 00055 (abs.go:6) MOVSD X0, "".~r1+32(FP)
0x003d 00061 (abs.go:6) ADDQ $16, SP
0x0041 00065 (abs.go:6) RET

Russ Cox

unread,
Oct 29, 2015, 11:54:36 AM10/29/15
to Keith Randall, Brendan Tracey, Robert Griesemer, Jeremy Jackins, golang-dev
Someone should improve our code generator. :-)

Robert Griesemer

unread,
Oct 29, 2015, 11:56:14 AM10/29/15
to Keith Randall, Russ Cox, Brendan Tracey, Jeremy Jackins, golang-dev
The & operator in Float64bits forces variables into memory I suspect.

Keith Randall

unread,
Oct 29, 2015, 11:57:59 AM10/29/15
to Robert Griesemer, Russ Cox, Brendan Tracey, Jeremy Jackins, golang-dev
SSA:
0x0000 00000 (abs.go:5) SUBQ $16, SP
0x0004 00004 (abs.go:5) MOVQ $0, "".~r1+32(FP)
0x000d 00013 (abs.go:5) MOVSD "".x+16(SP), X0
0x0013 00019 (abs.go:6) MOVSD X0, math.f·2(SP)
0x0018 00024 (abs.go:6) MOVQ math.f·2(SP), AX
0x001c 00028 (abs.go:6) MOVQ $9223372036854775807, CX
0x0026 00038 (abs.go:6) ANDQ CX, AX
0x0029 00041 (abs.go:6) MOVQ AX, math.b·2+8(SP)
0x002e 00046 (abs.go:6) MOVSD math.b·2+8(SP), X0
0x0034 00052 (abs.go:6) MOVSD X0, "".~r1+32(FP)
0x003a 00058 (abs.go:6) ADDQ $16, SP
0x003e 00062 (abs.go:6) RET

better hand-written code:
MOVQ   x+0(FP), AX
SHLQ   $1, AX
SHRQ   $1, AX
MOVQ   AX, ret+8(FP)
RET

Robert Griesemer

unread,
Oct 29, 2015, 11:58:39 AM10/29/15
to Keith Randall, Russ Cox, Brendan Tracey, Jeremy Jackins, golang-dev
I suspect this is a front-end issue.

Jeremy Jackins

unread,
Oct 29, 2015, 9:49:39 PM10/29/15
to Robert Griesemer, Keith Randall, Russ Cox, Brendan Tracey, golang-dev
Thanks all for giving this your attention.
Reply all
Reply to author
Forward
0 new messages