Skip to content

Faster fmpz/int conversion using from_bytes/to_bytes - #324

Merged
oscarbenjamin merged 4 commits into
flintlib:mainfrom
remyoudompheng:intconv
Jan 24, 2026
Merged

Faster fmpz/int conversion using from_bytes/to_bytes#324
oscarbenjamin merged 4 commits into
flintlib:mainfrom
remyoudompheng:intconv

Conversation

@remyoudompheng

@remyoudomphengremyoudompheng commented Sep 7, 2025

Copy link
Copy Markdown
Contributor

This is a proposal to handle issue #159 for Python versions below 3.14 (for example Ubuntu 24.04 LTS uses Python 3.12).
The idea is to use standard int methods (from_bytes, to_bytes) to obtain a binary serialization, then cast the byte array to ulong.

Special care is needed for big-endian platforms, however I am unable to test whether the patch proposal is correct for big-endian platforms. Let me know what would be the preferred approach.

Benchmarks done using %timeit in iPython with Python 3.13
AFAIK gmpy2 uses private Python stuff already so it might be difficult to do better.

Numbergmpy2 int(mpz)gmpy2 mpz(int)int(fmpz) 0.8.0fmpz(int) 0.8.0int(fmpz) PRfmpz(int) PR
3^10045.7 ns53.6 ns186 ns234 ns138 ns166 ns
3^100001.49 µs1.23 µs6.63 µs6.52 µs1.15 µs1.06 µs
-3^100001.50 µs1.23 µs6.63 µs6.60 µs1.24 µs1.23 µs

(edited for changes in commit f349183 for negative numbers)

@remyoudompheng

Copy link
Copy Markdown
ContributorAuthor

The function ulong_from_little_endian is endian-agnostic and was tested by forcing is_big_endian=1 on a little-endian system.

@remyoudompheng

remyoudompheng commented Sep 7, 2025

Copy link
Copy Markdown
ContributorAuthor

Since this PR seems to give performance on par with gmpy2 it is unclear whether PEP 757 will be even necessary. I have not been able to build #64 for comparison

Also, it should work with older Python versions.

@oscarbenjamin

Copy link
Copy Markdown
Collaborator

The PEP 757 interface is provided as CPython's best effort to give something efficient for this using public API in Python 3.14 onwards. I think that should be the baseline before trying any other approach.

@skirpichev

Copy link
Copy Markdown

Interesting, conversion to int seems asymptotically faster with this approach (c.f. using mpz_export). Perhaps, it could be a little better with new PyLong_*Bytes*() C-API. Below my benchmarks. I would appreciate if someone could repeat this on less noisy system.

AFAIK gmpy2 uses private Python stuff already so it might be difficult to do better.

No, gmpy2 uses PEP 757 API (using pythoncapi-compat for <3.14).

Import (int -> mpz)

Benchmarkgmpy2.mpzflint.fmpzPR324
1<<7321 ns475 ns: 1.48x slower443 ns: 1.38x slower
1<<38340 ns471 ns: 1.39x slower461 ns: 1.36x slower
1<<300611 ns3.69 us: 6.04x slower2.09 us: 3.41x slower
1<<30002.58 us10.3 us: 4.01x slower3.21 us: 1.25x slower
1<<100007.61 us30.1 us: 3.95x slowernot significant
Geometric mean(ref)2.87x slower1.51x slower

Export (mpz -> int)

Benchmarkgmpy2.mpzflint.fmpzPR324
1<<7158 ns177 ns: 1.12x slower173 ns: 1.09x slower
1<<38206 ns231 ns: 1.12x slower226 ns: 1.09x slower
1<<300617 ns2.52 us: 4.08x slower932 ns: 1.51x slower
1<<30002.54 us10.5 us: 4.14x slower2.33 us: 1.09x faster
1<<100007.61 us31.3 us: 4.12x slower6.26 us: 1.22x faster
Geometric mean(ref)2.44x slower1.06x slower

Could someone trigger CI tests in this pr (build logs are expired)?

benchmark scripts
# bench-import.pyimportosimportpyperf_T=os.getenv('_T')
if_T=="gmpy2.mpz":
fromgmpy2importmpzelif_T=="gmp.mpz":
fromgmpimportmpzelse:
fromflintimportfmpzasmpzcases= ['1<<7', '1<<38', '1<<300', '1<<3000', '1<<10000']
runner=pyperf.Runner()
forcincases:
i=eval(c)
runner.bench_func(c, mpz, i)
# bench-export.pyimportosimportpyperf_T=os.getenv('_T')
if_T=="gmpy2.mpz":
fromgmpy2importmpzelif_T=="gmp.mpz":
fromgmpimportmpzelse:
fromflintimportfmpzasmpzcases= ['1<<7', '1<<38', '1<<300', '1<<3000', '1<<10000']
runner=pyperf.Runner()
forcincases:
i=eval(c)
m=mpz(i)
runner.bench_func(c, int, m)

@oscarbenjamin

Copy link
Copy Markdown
Collaborator

gmpy2 uses PEP 757 API (using pythoncapi-compat for <3.14).

I think that python-flint should do the same.

@oscarbenjamin

Copy link
Copy Markdown
Collaborator

After timings above and in gh-358 I think it turns out that the approach in this PR is the best one for python-flint. For now it seems that PEP 757 along with GMP's import/export functions is slower than using to_bytes and from_bytes.

@oscarbenjamin

Copy link
Copy Markdown
Collaborator

CI checks here should pass after gh-359

@oscarbenjamin

Copy link
Copy Markdown
Collaborator

Okay, looks good. Well get this in for now and can consider later if any other approach seems to time faster.

@oscarbenjamin
oscarbenjamin merged commit 21d02ca into flintlib:mainJan 24, 2026
81 checks passed
Sign up for freeto 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.

3 participants

@remyoudompheng@oscarbenjamin@skirpichev