-
Notifications
You must be signed in to change notification settings - Fork 65
RFC: redesign of the flint wrapper #2058
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
base: master
Are you sure you want to change the base?
Conversation
The FLINT wrapper is inefficient when accessing elements of matrices or polynomials (or anything else nested). The problem is that to acesss `M[i, j]` we first create a new `fmpz` and then `fmpz_set` the elements. As a consequence, we now have a lot of `mat_entry_ptr` functions, which are very (very) unsafe and plenty of `ZZRingElemOrPtr` occurences. I had many discussions with @fieker and Dan over the last few years and here is one altenative design which is distilled from these discussions: ``` struct ZZRingElemDR ptr::Ptr{ZZRingElem} data #= can be anything where ptr points to (fmpz, fmpz_mat, fmpz_poly, ...) =# end ``` ("DR" stands for "done right", a tribute to Dan.) The idea is to have an (immutable) layer on top of the raw `fmpz`, which holds a pointer to an `fmpz` together with "data". The "data" part is only there to keep the memory alive for the sake of the GC. It can be a `ZZRingElem`, or a `ZZMatrix` or a `ZZPolyRingElem`, and so on. Together with some `Base.cconvert`, it is relatively easy to make this fly, see the diff: ``` julia> a = Nemo.ZZRingElemDR(ZZ(2)) 2 julia> -a -2 julia> a = Nemo.ZZRingElemDR(ZZ[1 2; 1 1], 1, 2) 2 julia> is_unit(a) false julia> Zx, x = ZZ["x"]; f = x^3 + 213*x + 1; julia> -Nemo.ZZRingElemDR(f, 1) -213 ``` The important point is that this is allocation free: ``` julia> M = ZZ[1 2; 3 4]; julia> @ballocated getindex(M, 1, 1) 16 julia> @ballocated Nemo.ZZRingElemDR(M, 1, 2) 0 ``` One drawback: This increases size of FLINT types. We add 16 bytes per object. For small `fmpz`, this makes them 3x larger.
How would this work for Nemo types that have a different memory layout than the FLINT type? eg a ZZPolyRingElem having the parent as a field? Edit: reading is hard. You already answered the question in the |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2058 +/- ##
==========================================
- Coverage 88.07% 88.03% -0.05%
==========================================
Files 100 100
Lines 36903 36913 +10
==========================================
- Hits 32503 32497 -6
- Misses 4400 4416 +16 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Good point! What to do with the parent depends a bit on how the bottom layer looks like: Whether it is parent-less or parent-full. If we do the cheap version of just putting a new layer on top of what we now have + renaming types and adding methods (but not changing the current things), the top layer would not need the parent information, since one can always reconstruct it from the data. If we rewrite/use the raw layer with parent-less raw objects, we need to put the parent in the top layer. |
We (@fingolfin @fieker and myself) decided to try this with |
Another interesting part would be implementing Maybe @thofma can play around with the options a bit when trying to make this work |
I'll keep copy and deepcopy the way they are. Where the pointer points to ( |
I adjusted a lot of code, until I realized that this approach has a serious drawback. One cannot actually swap matrix elements!
Also
won't work. I think this is a no-go in terms of replacing What I now realize is that I have written a GC-friendly version of
|
Motivation
The FLINT wrapper is inefficient when accessing elements of matrices or polynomials (or anything else nested). The problem is that to acesss
M[i, j]
we first create a newfmpz
and thenfmpz_set
the elements. As a consequence, we now have a lot ofmat_entry_ptr
functions, which are very (very) unsafe and plenty ofZZRingElemOrPtr
occurences.Possible solution
I had many discussions with @fieker and Dan over the last few years and here is one altenative design which is distilled from these discussions:
("DR" stands for "done right", a tribute to Dan.)
The idea is to have an (immutable) layer on top of the raw
fmpz
, which holds a pointer to anfmpz
together with "data". The "data" part is only there to keep the memory alive for the sake of the GC. It can be aZZRingElem
, or aZZMatrix
or aZZPolyRingElem
, and so on.Together with some
Base.cconvert
, it is relatively easy to make this fly, see the diff:The important point is that this is allocation free:
As @lgoettgens pointed out, for more complicated things the top layer "DR" also needs the parent in case the bottom layer goes parent-less.
What we gain
It will be easy to write generically fast code:
would be fast for any type (it is slow for FLINT types at the moment).
Drawback
This increases the size of FLINT types. We add 16 bytes per object. For small
fmpz
, this makes them 2x(?) times larger. (At the moment they should be 8 bytes for the pointer/small int + 8 bytes because mutable).How much do we have to rewrte? Is it breaking?
It should be relatively easy. One would rename the current
ZZRingElem
to something else, likeZZRingElemRaw
orfmpz
. Then renameZZRingElemDR
toZZRingElem
. With theBase.cconvert
trick, even the ccalls in Hecke/Oscar would be "easy" to adjust.It "should" not be too breaking.
Other advantage
We might even use this to have the raw flint layer (that can be constructed "automatically" from the headers as @lgoettgens demonstrated) and put all other things in the top layer (like parent objects etc).
CC: @fieker @fingolfin @lgoettgens