Need to crazy-optimize some GMP stuff?

While crystal stdlib has a GMP binding (The Big Module) which is super convenient to use, what happens when you need something NOT convenient to use but faster?

The main bottleneck in Big’s performance is that they are immutable, so super tight loops with operations on Bigs will pay the object allocation tax.

So: GitHub - ralsina/mut_gmp: Mutable GMP bindings for Crystal with in-place operations for better performance in algorithms like spigot algorithms, a MUTABLE GMP binding.

How inconvenient is it?

require "mut_gmp"

a = MutGMP::MpZ.new(42)
b = MutGMP::MpZ.new(10)

a.add!(b)  # a is now 52, mutated in-place
a.mul!(2)  # a is now 104

# Operations can be chained
c = MutGMP::MpZ.new(5)
c.add!(10).mul!(2).sub!(5)  # c is now 25

Have fun!

See Add mutating methods to BigInt by JacobUb · Pull Request #9825 · crystal-lang/crystal · GitHub, especially the discussion on self-mutations

I honestly implemented exactly as much as was needed to make one specific benchmark faster :-)

As the PR mentions, the performance gain wasn’t as much as expected. In my own benchmarks I found out a similar thing trying to optimize an algorithm to match in performance with Ruby’s YJIT. I wonder: what’s your benchmark, and did you manage to make it competitive w.r.t. other langs?

The Pidigits benchmark in LangArena

The difference was fairly significant, around 4x faster with mutable ops.

PR here: Pidigits and JSONParseDom by ralsina · Pull Request #4 · kostya/LangArena · GitHub

2 Likes