Repository navigation
Few optimizations for FITS tables - #4985
Conversation
| # lists of column/field definition common names and keyword names, make | ||
| # sure to preserve the one-to-one correspondence when updating the list(s). | ||
| # Use lists, instead of dictionaries so the names can be displayed in a | ||
| # preferred order. |
There was a problem hiding this comment.
nitpicking: change list to tuples in the comment, too.
| @@ -42,6 +42,8 @@ class StringIO(object): | |||
| elif six.PY2: | |||
There was a problem hiding this comment.
Not related to this PR, but I just notice that this logic here is not compatible with Python 4.
|
@pllim @MSeifert04 - With 3af359c I now cache the lists of attributes in @embray - Maybe, if you want have a look ? Master: This branch: |
| """Get or update local lists of column attributes""" | ||
| attrs = [name] if name is not None else KEYWORD_ATTRIBUTES | ||
| for name in attrs: | ||
| setattr(self, name + 's', self.__getattr__(name+'s')) |
There was a problem hiding this comment.
I'm surprised PEP8 test didn't complain about lack of white space around +. Since it didn't complain, I guess we can leave this as is.
There was a problem hiding this comment.
Fixed, though there is no real obligation for spaces around operators ;-)
If operators with different priorities are used, consider adding whitespace around the operators with the lowest priority(ies). Use your own judgment; however, never use more than one space, ...
|
Overall, the speed-up looks nice. Like you said, the tests passed. So unless someone takes this for a spin on a variety of FITS tables, it is good enough? |
|
Rebased to fix conflict with master. |
| attr.append(val) | ||
| else: | ||
| attr.append('') | ||
| attr.append(val if val is not None else '') |
There was a problem hiding this comment.
@astrofrog - I think so, but the original code uses is not None. I'm not sure if there are case where val could be a falsy value that should not be translated to a empty string. So, in doubt, better stick with the original implementation.
This is a micro-optimization but _is_int can be called many many times when loading FITS tables, so it can start to be meaningful.
8fc380a to
a3e8f7c
Compare
| if attr == 'name': | ||
| del self.names | ||
| elif attr == 'format': | ||
| del self.formats |
There was a problem hiding this comment.
Doesn't quite work--these also need to be invalidated when columns are added/removed. Otherwise I'm fine with the general concept. My preference would still be to not make any changes in the ColDefs class and instead update the FITS_rec class to be less reliant on the ColDefs object in general (an object I would like to get rid of, ultimately).
That said, in the interest of not wasting any more of your time on this and getting it finished, if you can just fix the column add/remove bug and ensure all the tests are passing this is definitely good enough for now! 👍
I also noticed you cleaned up a little bit of additional code rot--thanks for that.
There was a problem hiding this comment.
Thanks Erik ! The column addition/removal is now fixed.
When you say FITS_rec class to be less reliant on the ColDefs, I wonder for example why ``FITS_rec.namestries first to use_coldefs.names` and then `dtype.names`, and not the opposite ?
Based on our discussion here, I tried to make a few changes in fitsrec.py. This is on this gist, should I include this commit ?
- replace
self._coldefs.nameswithself.names - move indexing to
self._coldefswhen possible - and change the way to get names using
dtype.namesfirst
There was a problem hiding this comment.
When you say FITS_rec class to be less reliant on the ColDefs, I wonder for example why `FITS_rec.names tries first to use _coldefs.names and then dtype.names, and not the opposite ?
Exactly--I think the opposite makes more sense. The way it works currently may be a remnant of when the dtype fields and the "logical" columns represented by the ColDefs class were not guaranteed to be the same. The reason for this is the unfortunate existence in FITS of "zero width" columns which must be supported--it's rare but does occur in the wild. It used to be that the Numpy dtype could not accommodate that, so there were a lot of hacks to make all the columns (including zero-width ones) represented to the user, while only representing the non-zero columns in the actual dtype.
I've gotten a bunch of patches into Numpy, however, to allow fields with zero size to work in a consistent manner, so most of those hacks have been removed (there is still one remnant of this issue: numpy/numpy#6430 Though that reminds me, the fix for that will be in Numpy 1.12 so in principle it should be possible to avoid use of the realign_dtype hack for Numpy 1.12 and up).
The patch in the gist looks good to me. I haven't tested it, and can't say with certainty that there are some dark corners lurking where that would actually break. But I don't think there--it's definitely what I had in mind.
There was a problem hiding this comment.
Ok great ! I pushed the commit for fitsrec.
|
@saimn Since this is quite a small PR could you squash the commits into one and include a changelog entry (just to mention the improved runtime)? Maybe wait until the tests have finished to see if anything went wrong. I'm not sure this is strictly necessary here so maybe wait if someone else wants to second that. I'm really interested about the fitsrec-changes. I've experimented with them before and always got several failures, so I'm really interested to see if it works out. 👍 |
|
@MSeifert04 - I can probably squash 2 or 3 commits for things that are modified in another commit, but there are a number of commits I would like to keep as they address specific things and contain some information on the change. |
As a descriptor it is often called (for each attribute of the column). So instead of adding each time '_', add it once.
Getting names from dtype.names should be safe and avoids accessing _coldefs. Also use directly self.names, and change several _coldefs indexing to get the column before accessing an attribute.
52bd47b to
a7ae89e
Compare
|
Ok, squashed some commits, and added changelog. |
|
The build failure seems unrelated (something with a samp hub process: https://travis-ci.org/astropy/astropy/jobs/157994577#L2451). |
|
|
||
| - ``astropy.io.fits`` | ||
|
|
||
| - Optimized the loading of tables with many columns. [#4985] |
There was a problem hiding this comment.
I think "optimized" is a strong word. It's hardly optimal. You can just say "Performance improvements"
a7ae89e to
cf0b815
Compare
cf0b815 to
0a58c66
Compare
|
Great--thanks for putting up with my nitpicks. I think this is good! |
|
Got approval, so merging. Thanks, @saimn ! |
|
Thanks for merging :) |
|
Specifically, for this table I get the factor of 8 improvement: |
|
@taldcroft - This is typically the use case I was addressing here, tables with a many columns, so I'm glad it is useful to someone else :) |
|
Thanks @taldcroft for the testimonial--I bet it was this PR! |
I have FITS files with a lot of extensions, lot of tables with many columns (a few thousands sometimes ...) and it takes a significant time to load everything, so I tried to find some optimizations (btw snakeviz is very nice to look at profiling results !).
The changes here could be seen a micro-optimizations, but they concern methods which are called many times, so it makes a difference in the end.
_is_int(ncalls=1621317). Time after : 2.64 sColdefs.__getattr__(ncalls=2673). Time after : 1.73sColumnAttribute.__get__(ncalls=1626425). Time after : 1.6sMaybe @embray if you have some time ?