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

math/big: Binomial can be be twice as fast on average by using symetry #10084

Closed
zephyr opened this issue Mar 4, 2015 · 2 comments
Closed

math/big: Binomial can be be twice as fast on average by using symetry #10084

zephyr opened this issue Mar 4, 2015 · 2 comments
Milestone

Comments

@zephyr
Copy link

zephyr commented Mar 4, 2015

The current implementation of Binomial(n, k int64) *Int performs 2*k big.nat multiplications. This can be much improved for any k>n/2, since math gives us Binomial(n,k) == Binomial(n,n-k).

This is trivial to implement and gives on average an substantive improvement both in CPU and memory consumption.

On a shiny Intel Core i7 processor:

$ go test -bench=.
PASS
BenchmarkStd-2           500       2889379 ns/op
BenchmarkFancy-2        1000       1384765 ns/op
ok      _/home/intel    3.289s

[go version devel +122384e 2015-03-04 20:55:55 +0000 linux/amd64]

On a raspberry pi 1 Model B rev. 2:

$ go test -bench=.
PASS
BenchmarkStd          20      74138648 ns/op
BenchmarkFancy        50      37191099 ns/op
ok      _/home/pi   3.744s

[go version devel +5432b4d Sun Mar 1 18:38:21 2015 +0000 linux/arm]
/* save the following as binomial_test.go */
package main

import (
    "math/big"
    "testing"
)

type binomialFunc func(n, k int) *big.Int

// nearly the same as big.Binomial
func stdBinomial(n, k int) *big.Int {
    var res big.Int
    return res.Binomial(int64(n), int64(k))
}

// a improved function which takes advantage of symetry
func fancyBinomial(n, k int) *big.Int {
    if k > n/2 {
        return stdBinomial(n, n-k)
    }
    return stdBinomial(n, k)
}

func proofEquality(t *testing.T, f, g binomialFunc, n int) {
    for k := 0; k <= n; k++ {
        a := f(n, k)
        b := g(n, k)
        if a.Cmp(b) != 0 {
            t.Errorf("binom(%d, %d) = %s ≠ %s\n", n, k, a, b)
        }
    }
}

// Test if the improved function calculates the same values as the old one
func TestEquality(t *testing.T) {
    proofEquality(t, stdBinomial, fancyBinomial, 100) // 100 == ∞ ;)
}

// Calculates all values of binomial(n,k) for a given n and test if the symetry holds
func doSymmetry(b *testing.B, binom binomialFunc, n int) {
    upto := n / 2
    for k := 0; k < upto; k++ {
        l := binom(n, k)
        u := binom(n, n-k)

        if l.Cmp(u) != 0 {
            b.Fatalf("binom(%d, %d) = %s ≠ %s = binom(%d, %d)\n", n, k, l, u, n, n-k)
        }
    }
    if (n % 2) == 0 {
        binom(n, upto+1) // a lonely ›true‹ middle point is only symetric to itself
    }
}

func BenchmarkStd(t *testing.B) {
    for i := 0; i < t.N; i++ {
        doSymmetry(t, stdBinomial, 100)
    }
}

func BenchmarkFancy(t *testing.B) {
    for i := 0; i < t.N; i++ {
        doSymmetry(t, fancyBinomial, 100)
    }
}
@griesemer
Copy link
Contributor

@zephyr
Copy link
Author

zephyr commented Apr 1, 2015

Thank you :)

@mikioh mikioh added this to the Go1.5 milestone Apr 5, 2015
@golang golang locked and limited conversation to collaborators Jun 25, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants