Skip to content

Commit

Permalink
int: optimize performance by avoiding allocation
Browse files Browse the repository at this point in the history
This change defines a new representation for Int on 64-bit machines
running a POSIX operating system, by reserving a 4GB portion of the
address space.

Pointers to addresses in this region represent int32 values,
and are disjoint from all *big.Int pointers,
allowing us to represent the union in a single pointer.
This means the conversion from Int to Value does not allocate.

The gauss benchmark (added in this CL) shows -40% ns, -48% bytes, -63% allocs:
Benchmark/bench_gauss-12  	      84	  13648744 ns/op	 3175816 B/op	  105862 allocs/op (before)
Benchmark/bench_gauss-12  	      55	  24283703 ns/op	 6119844 B/op	  289862 allocs/op (after)

On 32-but machines, or those running a non-POSIX system,
we continue to use the old representation.

Change-Id: Ib3b116760c5c9c0c8096314c90e68ddcb4b89bc0

.

Change-Id: I512065d0f09c10cf55b87d28673282c078ae2e3c
  • Loading branch information
adonovan committed Jun 16, 2020
1 parent 529d4af commit 6f21ecf
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 26 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ require (
github.com/chzyer/logex v1.1.10 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c // indirect
golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c
)
27 changes: 3 additions & 24 deletions starlark/int.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,9 @@ import (
)

// Int is the type of a Starlark int.
type Int struct {
// We use only the signed 32 bit range of small to ensure
// that small+small and small*small do not overflow.
small_ int64 // minint32 <= small <= maxint32
big_ *big.Int // big != nil <=> value is not representable as int32
}

// --- low-level accessors ---

// get returns the small and big components of the Int.
// small is defined only if big is nil.
func (i Int) get() (small int64, big *big.Int) {
return i.small_, i.big_
}

// Precondition: math.MinInt32 <= x && x <= math.MaxInt32
func makeSmallInt(x int64) Int {
return Int{small_: x}
}

// Precondition: x cannot be represented as int32.
func makeBigInt(x *big.Int) Int {
return Int{big_: x}
}
//
// The zero value is not a legal value; use MakeInt(0).
type Int struct{ impl intImpl }

// --- high-level accessors ---

Expand Down
33 changes: 33 additions & 0 deletions starlark/int_generic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//+build !linux,!darwin
//+build !amd64,!arm64,!mips64x,!ppc64x

package starlark

// generic Int implementation

import "math/big"

type intImpl struct {
// We use only the signed 32-bit range of small to ensure
// that small+small and small*small do not overflow.
small_ int64 // minint32 <= small <= maxint32
big_ *big.Int // big != nil <=> value is not representable as int32
}

// --- low-level accessors ---

// get returns the small and big components of the Int.
// small is defined only if big is nil.
func (i Int) get() (small int64, big *big.Int) {
return i.small_, i.big_
}

// Precondition: math.MinInt32 <= x && x <= math.MaxInt32
func makeSmallInt(x int64) Int {
return Int{intImpl{small_: x}}
}

// Precondition: x cannot be represented as int32.
func makeBigInt(x *big.Int) Int {
return Int{intImpl{big_: x}}
}
55 changes: 55 additions & 0 deletions starlark/int_posix64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//+build linux darwin
//+build amd64 arm64 mips64x ppc64x

package starlark

// Optimized Int implementation for 64-bit machines running POSIX (for mmap).
// It reserves a portion of the address space to represent int32 values.

import (
"log"
"math"
"math/big"
"unsafe"

"golang.org/x/sys/unix"
)

// intImpl represents a union of (int32, *big.Int) in a single pointer,
// so that Int-to-Value conversions need not allocate.
//
// The pointer is either a *big.Int, if the value is big, or a pointer into a
// reserved portion of the address space (smallints), if the value is small.
//
// See int_generic.go for the basic representation concepts.
type intImpl unsafe.Pointer

// get returns the (small, big) arms of the union.
func (i Int) get() (int64, *big.Int) {
ptr := uintptr(i.impl)
if ptr >= smallints && ptr < smallints+1<<32 {
return math.MinInt32 + int64(ptr-smallints), nil
}
return 0, (*big.Int)(i.impl)
}

// Precondition: math.MinInt32 <= x && x <= math.MaxInt32
func makeSmallInt(x int64) Int {
return Int{intImpl(uintptr(x-math.MinInt32) + smallints)}
}

// Precondition: x cannot be represented as int32.
func makeBigInt(x *big.Int) Int { return Int{intImpl(x)} }

// smallints is the base address of a 2^32 byte memory region.
// Pointers to addresses in this region represent int32 values.
// We assume smallints is not at the very top of the address space.
var smallints = reserveAddresses(1 << 32)

func reserveAddresses(len int) uintptr {
b, err := unix.Mmap(-1, 0, len, unix.PROT_READ, unix.MAP_PRIVATE|unix.MAP_ANONYMOUS)
if err != nil {
log.Fatalf("mmap: %v", err)
}
return uintptr(unsafe.Pointer(&b[0]))
}
8 changes: 7 additions & 1 deletion starlark/testdata/benchmark.star
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ def bench_int():
a += 1

def bench_bigint():
a = 1 << 31 # maxint32 + 1
a = 1 << 31 # maxint32 + 1
for _ in range1000:
a += 1

def bench_gauss():
# Sum of arithmetic series. All results fit in int32.
acc = 0
for x in range(92000):
acc += x

0 comments on commit 6f21ecf

Please sign in to comment.