Specialise GradedSpace functions based on storage type - #511
Open
borisdevos wants to merge 12 commits into
Open
Conversation
kshyatt
reviewed
Aug 21, 2026
| return i | ||
| end | ||
| _searchsortedfirst(v::Vector, k) = searchsortedfirst(v, k) | ||
| # function _searchsortedfirst(v::Vector, k) |
Codecov Report❌ Patch coverage is
... and 3 files with indirect coverage changes 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is somewhat connected to what I was doing in QuantumKitHub/TensorKitSectors.jl#106, but beneficial for all sector types. Since I've firsthand experienced how my code transitioned from being unusable to performing well by going from
NTuplestorage toSectorDictstorage, I thought it was about time to look at these storage paths.I had two options going into this. The one I'm still looking into is seeing whether there's a cutoff (or range) where
NTuplestorage severely starts underperforming. The other approach I'm taking in this PR is to specialiseGradedSpacefunctions and constructors based on their storage type. The overarching problems I tried to fix were the following:NTupleconstructor could take unboundedly long to compile for sector types with many sectorsNTupleversions of these functions were not making use of the fact that the sector types match, so there's efficient ways to accessing the sectors, knowing always how many there are as well.SectorDictversions of these functions were doing more per-pair dictionary work than the algorithm actually needs (extra hash lookups, double work, etc)Summary of changes I made:
GradedSpace{I, NTuple{N,Int}}constructor: the old constructor built up the dims tuple viaTupleTools.setindex, which fell back to Base'sntuple(f, Val(N)). This requires compiling this for every distinctN, which I found to scale terribly withN. I first tried building into a vector and then annotating the splat into a tuple, but it turns out that it has a cost that scales withN, which dominated for large enoughN. So now I directly convert the vector through a Base iterator-to-tuple constructor which Julia specialised to make faster depending on N. TheNTuplefuseandtruncate_spacemake use of this as well.dimspecialisation: in general I tried avoiding constructingsectors(V)where possible, and just directly checking theNTupledirectly (throughvalues(I)) or the pairs inSectorDict.⊕,⊖,infimum,supremumspecialisations:NTuplestorage: the two spaces here are always of the same sector type, so their tuples are aligned. I could just do the appropriatemapwithout looking up sectors. Againsectors(V)is the plague.SectorDictstorage: I made use of how the keys are sorted here to merge them in an appropriate way depending on the what the function actually wanted to achieve. These structurally looked the same, so I refactored them into_sortedmerge. This outperforms having to work directly with aSectorDict.fuseSectorDictpath: previously a bunch ofgets andsetindex!s were done in the double for-loop on theSectorDict, which accumulated inefficiently due to lookup cost for this type of dictionary. PlainDicts don't have this, so I just do the accumulation in this and then sort at the end. The complexity hasn't changed since there's still two for-loops, but there's a speedup.truncate_spaceSectorDictpath: same structure asfuseforSectorDicts, but now with vectors because you don't have to look up anything along the way.SectorDict's_searchsortedfirst: I looked into when this was implemented, and goes back to 2019 back when product sectors didn't even exist. So I guess back thenNwas always fairly small, and the linear search was more efficient. However, it seems now that's not particularly the case, so I took the liberty of having it default to Base's method.Benchmarks
I tested Julia 1.10.10 (LTS) and 1.12.6 (stable) since I think those are the two versions most people are on. For the
NTuplestorage sector types, I testedN = 2 / 8 / 64 / 256 / 1296withZ2Irrep / ZNIrrep{8} / Z4Irrep⊠^3 / Z4Irrep⊠^4 / ZNIrrep{6}⊠^6. I also testedN = 15625withZNIrrep{5}⊠^6where possible, which is important to mention. For theSectorDictI testedU1Irrepwith charges-6:6,-50:50, and-200:200(13/101/401 sectors).And here the many many numbers. I spared my sanity by having a robot friend write this in markdown.
Constructor compile time (the
Valeffect)Details
N=15625before does not complete (at least within 5 minutes on my laptop) on either version. After: 973 ms (1.10.10), 1.00 s (1.12.6).NTuple storage (after above compilation time)
Details
And now just the
N=15625case separately, also just after only as before doesn't finish:On the
⊖/1.12.6 number: the first call at this N takes ~290-353s on 1.12.6 specifically (reproduced 3×), but the second call in the same session takes ~1.2s, matching 1.10.10's steady-state ~1.1s for the same op at the same N. Only compiling the whole⊖method together on 1.12.6 is this slow. I didn't look deeper into this, also since its use-case is extremely limited for this large N.SectorDict U1Irrep
Details
(Negative) conclusions/remarks from the benchmarks:
Nthe constructor is still somewhat slower (0.45x-0.99x), but the alternative, per the first table, is to make largeNimpossible to compile in reasonable time.truncate_spaceis still slightly slower atN=2(0.29x/0.86x) but wins fromN=8up.NTuplefusescales the way it does withNI have no clue, but it's at least better for reasonable ranges.⊖on 1.12.6 is a one-time compile bottleneck, but afterwards does fine (see note above).SectorDictwins scale with sector count.All in all, these are improvements, notably the
SectorDict. So it makes you wonder if there's in fact some cutoffNabove which you want to saySizeUnknownto theSectorValues' length 🤔