Repository navigation
Conversation
* Follow on to astropy#3767, which is partially addressing astropy#3764 * Add scalar function to compute massive neutrino densities in integrals to get speedup for arbitrary number of neutrinos. This buys about 20% speedup. * Add 1, 2, and 3 massive neutrino specializations for much larger speedups by avoiding the (surprisingly) expensive numpy.sum operations. These should be by far the most common cases. * Some unit tests added for the new scalar functions
|
Again, I was surprised by just how expensive some numpy functions are that I thought were cheap (like The other way to go about this -- which maybe would be better -- would be Cython. But I decided this was simpler for a first attempt. |
|
@aconley - the three nearly-the-same functions seem a bit excessive. Is there really much of a speed gain between the 1-neutrino version and one that simply does a python |
|
@mhvk -- a straight python loop is about the same speed for 1 massive neutrino, but 10/20% slower for 2/3. Which maybe is close enough -- it's still a big speedup. I would probably prefer the single function approach, but the last pull taught me that other people seem to have a higher tolerance for code ugliness than I do, so I figured I'd start at full ugly for speed. (PS: nus can contain more than 3 neutrinos -- for the fans of sterile neutrinos out there). As for python To be precise, for a 3 element sum, python |
|
@aconley - I think at the few tens of % level, it is not worth making the code way more ugly... As for And |
|
Interesting! I'm starting to understand there's a lot more overhead in But note that unrolling still seems to win by ~ 50%. And PS: you shouldn't write is about 30% faster. |
|
@mhvk - alas, it appears I messed up while testing the pure python loop, and left part of the calculation out. The 3-headed version is more than 10-20% faster, it's more like 40-70%. astropy HEAD: 520ms 3-headed version: python loop: So, that's a 70% hit in the most common case, which isn't negligible. Now, the relatively clean loop version is still a good 3.5x faster than HEAD -- but the ugly-ass version is 6x faster. |
|
@mhvk -- ok, you'll like this. If I store a copy of the variable we need as a python list instead of a numpy array, then the pure python loop version is a) faster than the 3-headed version b) only weakly dependent on the number of neutrinos. So -- ditch numpy, get simpler (as in, only one version) and faster code. Hrm. |
|
@aconley - wow! Are you doing the equivalent of the following? |
|
Not quite... we do have to do some manipulation on the variable (_nu_y; it gets divided by 1+z and raised to various funny powers). So the idea is to also store an instance variable with _nu_y as a list (leaving a ndarray version for when people are actually calling on ndarray zs), and then use a python loop. There's still some ugliness -- you want a special case that is not in the thing being integrated for there being no massive nus -- but it's down to 2 additional private functions instead of 5. And the private functions are easier to understand. This is making me question everything I thought I knew about |
|
Btw one should be careful with caching - for the examples @mhvk showed, I get warnings like |
|
@astrofrog -- Yeah, I know. That can also reflect something being compiled to bytecode. So you do it multiple times, and the warning goes away. The timing tests I'm talking about are based on modifying |
…ndarray. * Store FLRW._nu_y as a python list in addition to ndarray. * Use the list version and a python loop in the scalar nu_relative_density calculation. This turns out to be quite a bit faster than using ndarrays, contrary to 'popular' wisdom. It also leads to much simpler code -- fewer special case functions -- than the previous versions of this pull. * Speedup is now fairly uniformly 6-7x over the old calculation, and this works for >3 massive neutrino species. In fact, this is faster than the previous 1, 2, 3 neutrino scalar special functions. * All unit tests still pass.
There was a problem hiding this comment.
Yeah, no kidding... not something I would have ever expected.
There was a problem hiding this comment.
Actually, not surprising at all now that I look at how this is used. When we're talking just (typically) 3 neutrino species, looping over a simple Python list (in Python) is going to involve far less overhead than looping (again, in Python) over a Numpy array.
There was a problem hiding this comment.
In other words (and I tested this) if you had say Neff=1000 all with different masses, using a Numpy ufunc in _massivenu_relative_density_scalar was about 2 orders of magnitude faster. But when you get down to tiny arrays of 3 or 4 elements the overhead of setting up the ufunc and returning arrays overtakes.
There was a problem hiding this comment.
Yeah, that's what I concluded. But that was entirely after the fact justification -- before I tried it I definitely expected numpy to be faster even for 3 or 4 elements. So, hey, learned something new!
|
Apologies for letting this languish for so long; new job, blah, blah. Among other things I was trying to make sure I could legally contribute to astropy... Anyways, I have now started on a cython version, which will probably make this pull obsolete. It's... a lot faster, although I haven't tried the neutrinos yet. |
|
Replacement cython version is #4127 |
|
No need for apologies--it's cool you gave the Cython version a try. |
|
#4127 replaces this, so I'm closing this one. |
integrals to get speedup for arbitrary number of neutrinos.
This buys about 20% speedup.
speedups by avoiding the (surprisingly) expensive numpy.sum
operations. These should be by far the most common cases.