Python big integer vs. Julia big integer arithmetic

You can, but it’s not recommended because it can lead to really bad code. But if necessary for performance, it’s totally possible.

Does gmp come with a kind of API being an executable file which can be used as an interpreter to perform operations on in “gmp language” provided commands? This will skip the overhead of using “all of the languages” and the intermediate step of translating the own syntax into the syntax of gmp and then cope with adapting the response to the format of own object structures to return the result.

I don’t think so. It comes as a library which is callable from other languages. Btw, there is a tool doing big integers which is not using gmp, I think. The linux utility dc:

dc -e '3 1000000 ^ 102 % p'
1 Like

By dismissing things you don’t understand as “magic”, you are letting ignorance steer you away from standard practice toward fallacious practices you’re not aware you also do not understand. Even if we eliminated all the mysterious tools by running executables, which is the endpoint of using the GMP C libraries directly, you would be measuring a microsecond-scale call with a millisecond-scale timer, very likely on an operating system with millisecond-scale jitter skewing your timings completely out of your control. Skepticism is supposed to be applied to oneself as much as others.

With the exception of 1 moderator’s participation, none of us are authorities in any sense. Multiple commenters explained your single function call benchmarks are completely dominated by other factors, especially the session startup time that we all acknowledge takes longer than the average interactive runtime language. Independent confirmation and unanimous admission to a shortcoming of scripts in a command line demonstrates reproducibility and good faith from the language’s users. Dismissing them without taking the same effort to understand them is unreasonable.

14 Likes

Pointing out that there is an issue with wondering about the timings is sure valuable to resolve the initial confusion, but … does not actually explain anything valuable about Python big integer vs. Julia big integer arithmetic. Nitpicking on the confusion and mistaken initial motivation to ask the question is only of very limited value. Explanations of the differences (Are there any at all? Or do all the “languages” including Julia finally run the same gmp code under the hood?) would be much better contributions to the topic … refraining from taking too much attention to the person asking a question and focusing on the subject as such, wouldn’t it?

That is an odd interpretation of people naturally responding to your repeated discussion and benchmarks of things unrelated to arbitrary integer arithmetic in either Julia or Python.

A language-specific forum is not a very good place to compare all languages. As an occasional Python user, I can tell you that when I checked a few years back, CPython’s int implementation did not use GMP. I can also tell you that wrappers by other languages can have their own idiosyncracies that end up affecting performance, though that would be avoided. However, if you want a proper discussion of CPython’s implementation, it’s best to reach out to a Python forum rather than demand all the answers here. I expect that with enough learning from many languages’ communities, you could do better comparisons.

3 Likes

It’s certainly possible to mutate BigInt, but Julia still doesn’t provide public API to do so explicitly. Several packages do provide such API, MutableArithmetics.jl most prominently, but those packages are necessarily implemented using Julia-private API.

It’s not a language issue, BTW, just API design.

2 Likes

The comparison below appears a much better approach to see the performance of big integer arithmetic in Julia and Python, doesn’t it?

~ $ time julia -t1 --startup=no --compile=min -e ''

real	0m0.198s
user	0m0.139s
sys	0m0.061s
~ $ time python -c ''

real	0m0.038s
user	0m0.034s
sys	0m0.004s

~ $ time python -c 'from gmpy2 import mpz; x=mpz(3)**100000000; y=x%102; print(y)'
69

real	0m1.043s
user	0m0.983s
sys	0m0.060s
~ $ time julia -t1 --startup=no --compile=min -e 'x=big(3)^100000000; y=x%102; println(y)'
69

real	0m1.291s
user	0m1.250s
sys	0m0.164s

Does it now allow the conclusion that the performance is comparable?

Python’s built-in integer arithmetic runs on this example to slow to obtain a result within some seconds …

According to ChatGPT:
Python’s built-in integers (int) are of unlimited precision, and since Python 3, they are implemented using a library called libmpdec, which is a high-performance arbitrary-precision decimal library. This implementation is different from GMP (GNU Multiple Precision Arithmetic Library), which is specifically designed for high-performance operations on large integers, rational numbers, and floating-point numbers.

The gmpy2 module uses the GMP library and is for really big integers much faster than native built-in Python implementation of big integer arithmetic.

What about Julia? What is used “under the hood”?

how many times does this question need to be asked and answered?

9 Likes

Hardly. Use the benchmark methods inside the language if you want to measure the performance of some operation in the language. The shell time command includes all sorts of other things, like starting the program, parsing, compiling/byte-compiling, and printing the result, and whatever, in addition to the arithmetic operations.

5 Likes

Maybe also worth mentioning that, at least for GitHub - JuliaCI/BenchmarkTools.jl: A benchmarking framework for the Julia language, in addition to being open source there is an actual paper https://arxiv.org/pdf/1608.04295 describing how the “magic” all works.

8 Likes

No, you’re still doing the misguided practice of subtracting separate overhead timings and timing printing, and you added more issues:

  • went back to the slower inefficient implementation in an attempt to outweigh the system variations and limited timer resolution. This is the wrong approach to benchmarking.
  • I can’t be certain, but you appear to be using the startup=no and compile=min arguments in an attempt to reduce the aforementioned session startup and compilation latency. That is not what those arguments do, you only managed to make the JIT compiler suboptimal in return for smaller code size.
  • Now you’re timing the load of gmpy2 on top of the CPython interpreter, which adds latency and no longer measures the int implementation.

Benchmarking libraries exist to remove or amortize these incomparable factors. BenchmarkTools.jl is still the go-to Julia package, ask a Python forum for their go-to packages. I used IPython kernel magics but there is likely an equivalent or better package. With your current understanding of either language or the topic at hand, I highly doubt you’ll arrive at a benchmarking technique as reliable as the available libraries, and your time would be better spent learning those with an open mind.

3 Likes

As stevengj has said, I believe it is a simple %timeit from ipython:

In [1]: %timeit pow(3,100000000000,102)
2.82 µs ± 10.4 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

For comparison, I get this in Julia

julia> @btime powermod(3,100000000000,102)
  749.686 ns (0 allocations: 0 bytes)
69
3 Likes

That is one of the kernel magics I mentioned, it’s good enough for many people in practice including me, but I personally hesitate to end a recommendation there because of the reliance on IPython. I’m sure people who don’t use Jupyter or Spyder have something else, could just be some practices with the standard library timeit for all I know.

1 Like

OK … it seems that Julia is four times faster … but … doesn’t it measure which optimization parameter were used to compile the Python C-code into an executable? Or how much the used CPU is adapted to the needs of LLVM, so that with another CPU and Python executable the outcome can be fully another one?

It seems that without knowledge about the used algorithm it would be hard to tell anything, so it is necessary to look at the C or assembly code used to implement an algorithm to see where the difference in timing comes from. Four times faster needs four times less CPU instructions to achieve the same result … doesn’t it?

julia> @btime powermod(3,100000000000,102)
ERROR: LoadError: UndefVarError: `@btime` not defined
in expression starting at REPL[1]:1

What do I need to do to be able to reproduce your result?

using BenchmarkTools

4 Likes

Four times faster needs four times less CPU instructions to achieve the same result … doesn’t it?

Or it uses instructions that take longer, or it is botlenecked on memory speed, or branch mis-prediction, or any of about 10000 possibilities.

8 Likes
julia> using BenchmarkTools

julia> @btime powermod(3,100000000000,102)
  1.107 μs (0 allocations: 0 bytes)
69

ipython3 (python3.10):

In [1]: %timeit pow(3,100000000000,102)
2.01 µs ± 5.43 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

ipython3 (python3.13):

In [2]: %timeit pow(3,100000000000,102)
1.37 μs ± 5.74 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

Until the details of the question are improved to a degree that makes an answer which provides enough information along answering the question really useful …