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

Use PARI implementation of Frobenius morphism #35316

Merged
merged 1 commit into from
Apr 6, 2023

Conversation

remyoudompheng
Copy link
Contributor

📚 Description

This pull request uses PARI fffrobenius and ffmap to implement the p-th power map for Finite fields using the PARI implementation (issue #4625).

It does not seem necessary to implement a similar thing for the Givaro implementation (where exponentiation is very fast) or the NTL implementation (only for characteristic 2).

📝 Checklist

  • I have made sure that the title is self-explanatory and the description concisely explains the PR.
  • I have linked an issue or discussion.
  • I have created tests covering the changes.
  • I have updated the documentation accordingly.

@remyoudompheng
Copy link
Contributor Author

As opposed to suggestions in the linked issue, it does not use a matrix method, although this could be implemented concisely using this snippet:

# Cache these elements
ap = (K.gen().frobenius()).powers(degree)
# Compute
def frobenius(x): sum(z * a for z, a in zip(x.list(), ap))

The linear method needs to cache O(d) field elements, whereas PARI field morphism are defined by the image of the single generator, using less memory. For practical field sizes, the difference remains small and both methods are vastly more efficient than exponentiation, except for extremely small characteristics or very large extension degrees.

Performance numbers are as follows: for a 16-bit prime, the simple exponentiation is faster than PARI field morphisms only for degrees > 128.

p size = 128 bits, degree=2
old 625 loops, best of 3: 205 μs per loop
new 625 loops, best of 3: 3.69 μs per loop

p size = 128 bits, degree=64
old 25 loops, best of 3: 21.1 ms per loop
new 125 loops, best of 3: 2.44 ms per loop

p size = 1024 bits, degree=2
old  125 loops, best of 3: 4.07 ms per loop
new 625 loops, best of 3: 5.06 μs per loop

p size = 1024 bits, degree=128
old 5 loops, best of 3: 3.23 s per loop
new 5 loops, best of 3: 65.5 ms per loop

src/sage/rings/finite_rings/element_pari_ffelt.pyx Outdated Show resolved Hide resolved
src/sage/rings/finite_rings/element_pari_ffelt.pyx Outdated Show resolved Hide resolved
src/sage/rings/finite_rings/element_pari_ffelt.pyx Outdated Show resolved Hide resolved
src/sage/rings/finite_rings/finite_field_pari_ffelt.py Outdated Show resolved Hide resolved
src/sage/rings/finite_rings/finite_field_pari_ffelt.py Outdated Show resolved Hide resolved
src/sage/rings/finite_rings/finite_field_pari_ffelt.py Outdated Show resolved Hide resolved
…lements

PARI implements Frobenius morphisms as field morphisms defined by the
image of the generator. Caching these morphisms requires O(degree)
field elements in memory and computation requires O(sqrt(degree)) field
multiplications instead of O(degree * log p) for the exponentiation method.

For very small powers or very large extension degrees, the power method
will be used for the p-th power Frobenius.
@remyoudompheng
Copy link
Contributor Author

Updated with review comments and updated commit message (Brent-Kung complexity is sqrt(degree))

@github-actions
Copy link

Documentation preview for this PR is ready! 🎉
Built with commit: bd225fd

Copy link
Collaborator

@tscrim tscrim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. LGTM.

@vbraun vbraun merged commit be279b5 into sagemath:develop Apr 6, 2023
@mkoeppe mkoeppe added this to the sage-10.0 milestone Apr 7, 2023
vbraun pushed a commit that referenced this pull request Apr 13, 2023
    
### 📚 Description

Calling repeatedly PARI fffrobenius function involves redundant
computations that can be avoided by reusing already computed powers.

This is a follow-up to #35316 improving the performance of Frobenius on
first call:
```
sage: p = next_prime(2**120)
....: K = GF(p**120, 'a')
....: x = K.random_element()
sage: %time _ = [x.frobenius(i) for i in (0, 20, 40, 60, 80, 100)]
Sage 9.8
CPU times: user 9.73 s, sys: 8 ms, total: 9.73 s
Sage 10 beta 8
CPU times: user 6.84 s, sys: 194 µs, total: 6.84 s
After patch
CPU times: user 681 ms, sys: 0 ns, total: 681 ms
When cached (10.0beta8)
30.9 ms ± 155 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

sage: K = GF(5**240, 'a')
....: x = K.random_element()
sage: %time _ = [x.frobenius(i) for i in range(240)]
Sage 9.8
CPU times: user 1.72 s, sys: 1.12 ms, total: 1.73 s
Sage 10.0beta8
CPU times: user 1.02 s, sys: 1.02 ms, total: 1.02 s
After patch
CPU times: user 219 ms, sys: 12 µs, total: 219 ms
When cached (10.0beta8)
168 ms ± 391 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
```
### 📝 Checklist

- [x] The title is concise, informative, and self-explanatory.
- [x] The description explains in detail what this PR is about.
- [ ] I have linked a relevant issue or discussion.
- [ ] I have created tests covering the changes.
- [ ] I have updated the documentation accordingly.
    
URL: #35456
Reported by: Rémy Oudompheng
Reviewer(s): Marc Mezzarobba
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants