-
Notifications
You must be signed in to change notification settings - Fork 162
Improvements for sparse modules from #4951 #4955
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
Improvements for sparse modules from #4951 #4955
Conversation
|
Ugh. I think using So turning everything into a symbol make this quite a bit slower (because those symbols are stored in a global table, and are made "unique" by doing a lookup there). |
|
Triage suggested that we keep the comparison of symbols to check equality (and in hashing). The current implementation can cater for that, but a decision needs to be made. @fingolfin , @fieker ... |
339c818 to
8d585af
Compare
| SA = get_attribute!(generators, :sparse_transformation_matrix) do | ||
| A = get_attribute(generators, :transformation_matrix) | ||
| A === nothing && error("No transformation matrix in the Gröbner basis.") | ||
| sparse_matrix(A) | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hannes14 : This is a temporary workaround to do things with sparse matrices for the lifting. Did you already look into providing methods to get a sparse data structure from Singular directly? Then I could adapt this PR to use that one directly.
|
I think, it's still quite annoying that we loose so much time in computing and comparing monomial orderings. Look at this: R, (x, y) = QQ[:x, :y]
F = free_module(R, 10000)
I, _ = sub(F, [x*F[1]])
II = I.sub
@time y*F[1] in IIFor the last line I get julia> @time y*F[1] in II
5.605941 seconds (90.35 M allocations: 3.587 GiB, 13.49% gc time, 1.32% compilation time)
false3.5G for a trivial computation! Using This PR contains some changes so that at least |
| function in(a::FreeModElem, M::SubModuleOfFreeModule) | ||
| F = ambient_free_module(M) | ||
| return iszero(reduce(a, standard_basis(M, ordering=default_ordering(F)))) | ||
| return iszero(reduce(a, standard_basis(M, ordering=nothing))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I think, the glitch was here. Look at this:
R, (x, y) = QQ[:x, :y]
F = free_module(R, 10000)
I, _ = sub(F, [x*F[1]])
d = Dict{ModuleOrdering, Any}()
ord = default_ordering(F);
d[ord] = standard_basis(I, ordering=ord);
@time d[ord]I repeatedly get
julia> @time d[ord];
0.447075 secondsprobably because of repeated hashing/canonical_matrix. The changes here do some dirty short-wiring of the dictionary lookup by checking whether any of the keys is === to ord. But that's O(n) and not O(1) as usual lookup of keys in dictionaries. So far we hardly ever have more than one groebner basis in any module, but still, it's not elegant what we do.
From the hacky changes here we get the following speedups.
R, (x, y) = QQ[:x, :y]
F = free_module(R, 10000)
I, _ = sub(F, [x*F[1]])
II = I.sub
@time coordinates(x*y*F[1], I)Before this PR:
julia> @time coordinates(x*y*F[1], I)
0.883578 seconds (10.29 k allocations: 400.250 KiB)
Sparse row with positions [1] and values QQMPolyRingElem[y]With this PR:
julia> @time coordinates(x*y*F[1], I)
0.000574 seconds (10.21 k allocations: 397.438 KiB)
Sparse row with positions [1] and values QQMPolyRingElem[y]Edit: It turns out that the glitch was not that canonical_matrix was computed over and over again, but that the already cached canonical_matrix was hashed over and over again (which takes ~0.44s). See the timings of the other post below for details.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #4955 +/- ##
==========================================
- Coverage 84.88% 84.81% -0.08%
==========================================
Files 697 698 +1
Lines 94190 94291 +101
==========================================
+ Hits 79956 79971 +15
- Misses 14234 14320 +86
🚀 New features to boost your workflow:
|
|
Tests pass. From my side, this is good to go for the moment (if people are OK with switching from |
|
This PR now seems to contain several other optimizations that are not related to lazyness of the symbols anymore. Could you try to split them into a different PR? I think most of the other optimizations should be a clear gain and thus could be merged independent of the discussion around symbols in this PR. And it would make it a lot easier to review these two different things independently of each other. |
|
As remarked in my edit at the beginning: This contains the changes and improvements for the modules from #4951, so this is already split off from something. Should I really chop it into even smaller pieces? Edit: And I think, there's not much to be discussed anymore about the symbols, is there? I mean: Now this does not change anything about the user-facing functionality anymore (comparison and hashing work as before), but things are a lot faster. So controversies should be over (hopefully). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks fine to me now.
@fingolfin : All your objectons and nitpicks have been dealt with, as far as I see.
|
Thanks for the review, @afkafkafk13 ! As discussed with @jankoboehm yesterday, this should not be merged before #4915 and rebased after that. |
|
Thank you for making this dependency explicit and setting the label 'DO NOT MERGE'. |
Co-authored-by: Max Horn <[email protected]>
f48ad1c to
cc20a07
Compare
|
@jankoboehm : This is now rebased and ready to merge from my side. I remember, you had some concerns about the use of orderings. Let me summarize what I did here. The hashing of monomial orderings for modules is still quite expensive. By now, we store the R, (x, y) = QQ[:x, :y]
F = free_module(R, 10000)
I, _ = sub(F, [x*F[1]])
II = I.subNow the first call for hashing the default ordering: julia> @time hash(default_ordering(I))
5.390386 seconds (90.44 M allocations: 3.588 GiB, 11.91% gc time)This needs to compute the julia> @time hash(default_ordering(I))
0.450113 seconds (1 allocation: 16 bytes)Still almost half a second! If we do this to look up a groebner basis for a So I thought: If lookups of groebner bases or any creation and comparison of monomial orderings can possibly be avoided, we should do it. Many functions involving the I think, this does not need to interfere with any foundational design decisions. But please, let me know if you think differently! Either way: We should really do something about not using module orderings in dictionaries. But |
|
I am ok with the |
|
I introduced now two more fields in the |
|
Thank you! Relative to our discussion earlier today: Do we really need the nothing option? Could we not just use the new fields (and also name them like that) for the Gröbner basis wrt the default ordering. The default ordering is stored in the ambient free module, so could just do a === check in the standard_basis command? That could then give an improvement also in many other situations. Or do I miss something? |
I did not follow all the details here but if it makes it stable across version this should be fine. |
|
Thank you! Yes it should make it stable. Shall we rename dummy into default? |
No. That would suggest that we find the gb for the |
|
Good to go from my side. |
|
Thanks, any sounds better than dummy. I would indeed have used it only for the default ordering, but this way is also fine. Without interference it will only be used for the default. So this is good to go from my side. |
This turned out to be a bottleneck in the buildup to #4951 . To demonstrate the effect, try the following:
Before:
After:
Edit: This now contains further patches for bottlenecks snooped up during work on #4951 . Some of these are rather hotfixes and should still be pulled straight at some point. But everything works for the moment!
Controversial things to be discussed:
This would have to go if we lazify them.Changed back to using symbols for comparison and hashing. This can probably still be sped up according to @fingolfin 's comment below.generator_symbolswas replaced bysymbols. This is not super necessary, but I was unaware of the existence ofgenerator_symbolsand started working with the other function.generator_symbolsis now left as an alias forsymbols.lift_std(see below andlift_stdshould use sparse matrices #4925 ).