Skip to content

Commit

Permalink
dec: optimize expNN performance for large x with y = 1 and 1 < m < x
Browse files Browse the repository at this point in the history
  • Loading branch information
db47h committed May 9, 2020
1 parent 1a8c674 commit 8308a49
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 9 deletions.
25 changes: 16 additions & 9 deletions dec.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type dec []Word

var (
decOne = dec{1}
decTwo = dec{2}
decTen = dec{10}
)

Expand Down Expand Up @@ -699,7 +700,6 @@ func (z dec) expNN(x, y, m dec) dec {
// We likely end up being as long as the modulus.
z = z.make(len(m))
}
z = z.set(x)

// If the base is non-trivial and the exponent is large, we use
// 4-bit, windowed exponentiation. This involves precomputing 14 values
Expand All @@ -715,13 +715,22 @@ func (z dec) expNN(x, y, m dec) dec {
// }

// convert y from dec to base2 nat
// TODO(db47h): better way to do this?
yy := y.toNat(nil)
yy := y.toNat(make([]Word, 1))

v := yy[len(yy)-1] // v > 0 because yy is normalized and y > 0
shift := nlz(v) + 1
v <<= shift
var q dec

// zz and r are used to avoid allocating in mul and div as
// otherwise the arguments would alias.
var zz, r dec

// set x = x % m, this speeds up cases with large x even if len(y) == 1
if len(m) != 0 {
zz, r = zz.div(r, x, m)
x = dec(nil).set(r)
}
z = z.set(x)

const mask = 1 << (_W - 1)

Expand All @@ -730,9 +739,7 @@ func (z dec) expNN(x, y, m dec) dec {
// we also multiply by x, thus adding one to the power.

w := _W - int(shift)
// zz and r are used to avoid allocating in mul and div as
// otherwise the arguments would alias.
var zz, r dec

for j := 0; j < w; j++ {
zz = zz.sqr(z)
zz, z = z, zz
Expand All @@ -744,7 +751,7 @@ func (z dec) expNN(x, y, m dec) dec {

if len(m) != 0 {
zz, r = zz.div(r, z, m)
zz, r, q, z = q, z, zz, r
z, r = r, z
}

v <<= 1
Expand All @@ -764,7 +771,7 @@ func (z dec) expNN(x, y, m dec) dec {

if len(m) != 0 {
zz, r = zz.div(r, z, m)
zz, r, q, z = q, z, zz, r
z, r = r, z
}

v <<= 1
Expand Down
53 changes: 53 additions & 0 deletions dec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,29 @@ func TestDecExpNN(t *testing.T) {
}
}

func BenchmarkDecExpNN(b *testing.B) {
for i, test := range decExpNNTests {
x := decFromString(test.x)
y := decFromString(test.y)
out := decFromString(test.out)

var m dec
if len(test.m) > 0 {
m = decFromString(test.m)
}

b.Run(fmt.Sprintf("#%d", i), func(b *testing.B) {
var z dec
for it := 0; it < b.N; it++ {
z = z.expNN(x, y, m)
if z.cmp(out) != 0 {
b.Fatalf("#%d got %s want %s", i, z.utoa(10), out.utoa(10))
}
}
})
}
}

func BenchmarkDecExp3Power(b *testing.B) {
const x = 3
for _, y := range []Word{
Expand Down Expand Up @@ -786,3 +809,33 @@ func TestDecDiv(t *testing.T) {
}
}
}

// TODO(bd47h): move this to decimal_test
func benchmarkDiv(b *testing.B, aSize, bSize int) {
aa := rndDec1(aSize)
// bb := rndDec1(bSize)
bb := dec(nil).setWord(Word(rnd.Intn(_W-1)) + 1)
if aa.cmp(bb) < 0 {
aa, bb = bb, aa
}
x := dec(nil)
y := dec(nil)

b.ResetTimer()
for i := 0; i < b.N; i++ {
x.div(y, aa, bb)
}
}

func BenchmarkDiv(b *testing.B) {
sizes := []int{
10, 20, 50, 100, 200, 500, 1000,
// 1e4, 1e5, 1e6, 1e7,
}
for _, i := range sizes {
j := 2 * i
b.Run(fmt.Sprintf("%d/%d", j, i), func(b *testing.B) {
benchmarkDiv(b, j, i)
})
}
}

0 comments on commit 8308a49

Please sign in to comment.