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

BigInteger: Replace ==ZERO by isZero. #302

Merged
merged 1 commit into from
Jul 14, 2024

Conversation

glureau
Copy link
Contributor

@glureau glureau commented Jun 11, 2024

Replacing == ZERO by isZero()

Motivation

Similar of #301 but for BigInteger. Equals are more costly than testing isZero or isNegative.

Special case

BigInteger.pow(BigInteger) uses exponentiationBySquaring which is now using isZero, but I was not able to reach this code path in my tests.
JVM was triggering an OutOfMemory (Java heap space) after 90s of running (my exponent was BigInteger.parse(Long.MAX_VALUE + "1")).
I took a shortcut by commenting the call to pow(Long), so that it forces the use of exponentiationBySquaring, and allow me to benchmark with "little" values (1 googol being "little").

Benchmark (JVM)

Best of 3 runs, custom iterations (see benchmark code), JVM, M3 Pro with 36GB RAM, duration in seconds

Method BEFORE AFTER % time reduction
subtract 1.13 1.08 4%
ZERO.subtract() 0.581 0.502 14%
subtract(ZERO) 0.683 0.330 52%
pow* (exp by squaring) 1.10 1.08 2%
modInverse 0.586 0.577 2%
times(Char) 2.27 2.16 5%
pow 0.081 0.070 14%

Benchmark code

    @Test
    fun perfSubtractZero() {
        val bd = BigInteger.parseString("123")
        val duration = measureTime {
            repeat(100_000_000) { bd - BigInteger.ZERO }
        }
        println("subtract(ZERO): $duration")
    }
    
    @Test
    fun perfZeroSubtract() {
        val bd = BigInteger.parseString("123")
        val duration = measureTime {
            repeat(100_000_000) { BigInteger.ZERO - bd }
        }
        println("ZERO.subtract(): $duration")
    }
    
    @Test
    fun perfSubtract() {
        val a = BigInteger.parseString("123")
        val b = BigInteger.parseString("34")
        val duration = measureTime {
            repeat(100_000_000) { a - b }
        }
        println("subtract: $duration")
    }
    
    @Test
    fun perfPowWithExponentiationBySquaring() {
        // Warning, this test has biased value, it should use an exponent > Long.MAX_VALUE to go
        // through exponentiationBySquaring. For benchmarking this test I've commented out the usage of pow(Long)
        val a = BigInteger.parseString("10")
        val b = BigInteger.parseString("100")
        val duration = measureTime {
            repeat(1_000_000) { a.pow(b) }
        }
        println("pow*: $duration")
    }

    @Test
    fun perfPow() {
        // Warning, this test has biased value, it should use an exponent > Long.MAX_VALUE to go
        // through exponentiationBySquaring. For benchmarking this test I've commented out the usage of pow(Long)
        val a = BigInteger.parseString("10")
        val b = BigInteger.parseString("100")
        val duration = measureTime {
            repeat(1_000_000) { a.pow(b) }
        }
        println("pow: $duration")
    }


    @Test
    fun perfModInverse() {
        // 3803 and 3329 are both primes
        val a = BigInteger.parseString("3803")
        val b = BigInteger.parseString("3329")
        val duration = measureTime {
            repeat(1_000_000) { a.modInverse(b) }
        }
        println("modInverse: $duration")
    }

    @Test
    fun perfTimes() {
        val a = BigInteger.parseString("38")
        val duration = measureTime {
            repeat(1_000_000) { a.times('b') }
        }
        println("times(Char): $duration")
    }

Raw data

Before

Run 1
subtract: 1.526356041s
ZERO.subtract(): 581.206625ms
pow: 1.199554083s
subtract(ZERO): 682.647833ms
modInverse: 586.281166ms
times(Char): 2.274098500s
pow: 89.229875ms

Run 2
subtract: 1.380918708s
ZERO.subtract(): 621.464125ms
pow: 1.098721042s
subtract(ZERO): 693.304667ms
modInverse: 608.127292ms
times(Char): 2.287946833s
pow: 83.628250ms

Run 3
subtract: 1.133838833s
ZERO.subtract(): 645.497916ms
pow: 1.234771250s
subtract(ZERO): 687.548750ms
modInverse: 649.144083ms
times(Char): 2.287897334s
pow: 80.517ms

After

Run 1
subtract: 1.078803625s
ZERO.subtract(): 536.864958ms
pow: 1.128199250s
subtract(ZERO): 329.929625ms
modInverse: 611.962750ms
times(Char): 2.214370791s
pow: 79.141750ms

Run 2
subtract: 1.137907458s
ZERO.subtract(): 525.679166ms
pow: 1.083095208s
subtract(ZERO): 544.093ms
modInverse: 599.358250ms
times(Char): 2.156597666s
pow: 86.663125ms

Run 3
subtract: 1.449096584s
ZERO.subtract(): 502.667083ms
pow: 1.076523292s
subtract(ZERO): 393.253875ms
modInverse: 574.755417ms
times(Char): 2.203168959s
pow: 69.521125ms

@glureau glureau force-pushed the perf_biginteger_zero_comparison branch from 544db8c to 9637261 Compare June 11, 2024 10:37
@ionspin
Copy link
Owner

ionspin commented Jun 12, 2024

Hi @glureau thank you for all the pull requests, they are highly appreciated. I won't have time/access to the repo next couple of weeks, so I'll look into the requests once I get back. Thanks once again!

@glureau
Copy link
Contributor Author

glureau commented Jun 12, 2024

Thank you for all your work! Great library and nice to have such a good support and response time for an open source project. Enjoy your days off!

PS: I've published a 0.3.10-SNAPSHOT2 on my fork https://github.com/users/glureau/packages?repo_name=kotlin-multiplatform-bignum for my own needs with all the improvements if anyone else is interested to test it, but I recommend to wait a bit more if you can ;)

@ionspin ionspin merged commit 002d898 into ionspin:main Jul 14, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants