Skip to content

Memory leak reading and copying bin table FITS files with io.fits - #520

Merged
embray merged 9 commits into
astropy:masterfrom
embray:issue-520
Jan 27, 2016
Merged

embray merged 9 commits into
astropy:masterfrom
embray:issue-520

Conversation

@embray

@embray embray commented Jul 17, 2013

Copy link
Copy Markdown
Member

The following code leaks memory when run (on python 2.7, astropy master, OS=linux)

import astropy.io.fits as pyfits

def readfile(filename, checkshape):
    hdus = pyfits.open(filename)
    # Without copy() there is no leak.
    dat = hdus[1].data.copy()
    hdus.close()

    for colname in dat.dtype.names:
        # When checkshape is False there is no leak
        if checkshape:
            dat[colname].shape == 0

# wget http://hea-www.harvard.edu/~aldcroft/tmp/memtest.fits.gz
filename = 'memtest.fits.gz' 
while True:
    print 'Reading'
    readfile(filename, checkshape=True)

I'm guessing that accessing the shape attribute is not really the problem but presumably it triggers some action that causes the leak. The workaround I used for this is effectively:

dat = np.array(hdus[1].data, copy=True)

(Though I discovered that pyfits is doing some nice magic with a FITS boolean-vector field, so in reality I had to do something a little more complicated).

@embray

embray commented Dec 3, 2012

Copy link
Copy Markdown
Member

I would note that doing something like:

hdus = pyfits.open(filename)
dat = hdus[1].data
hdus.close()

is deceptive, since accessing the .data attribute by default opens a mmap to the file, so it's not "closed" in the sense that there are no open handles to the file. I generally don't call .close() unless I'm opening the file in a writeable mode, and even then I generally try to use the with statement.

I have a feeling this might be fixed and is just waiting in the wings for Astropy in #495. I'll double check on that though.

@ghost ghost assigned embray Dec 3, 2012
@embray

embray commented Dec 3, 2012

Copy link
Copy Markdown
Member

Oh, it seems I also misread your example. I thought it said that with .copy() there is no leak. That's a little more odd.

@embray

embray commented Dec 3, 2012

Copy link
Copy Markdown
Member

Confirmed in normal PyFITS as well. Also worth noting this has nothing to do with the fact that the file is gzipped, as I tried unzipping it first and had the same behavior.

This is a bit bizarre. But the FITS_rec class is a complicated ugly beast at best. I'm guessing that the .copy() isn't getting a full deep-copy of every object that makes up the table.

@taldcroft

Copy link
Copy Markdown
Member Author

Sorry, I should have mentioned that I originally noted this issue in PyFITS. I originally used copy() for exactly the reason you said, namely that it seemed like a bad idea to directly reference to hdus[1].dat. The actual code where I noticed this collects up hundreds of such dat arrays and then stacks them up.

@embray

embray commented Jul 17, 2013

Copy link
Copy Markdown
Member

Apparently. this is still an issue. Sorry I haven't looked at this in so long. What triggers the issue has to do with accessing the individual columns of the table, not looking at their shapes. At least some of those columns need to be converted in the background from the native FITS format to a more useful format, and that happens in memory. That said, there still shouldn't be a leak. Somehow a reference cycle is getting made (thought I stomped those all out).

@embray

embray commented Jul 17, 2013

Copy link
Copy Markdown
Member

The memory leak when reading this file seems to be specifically related to the prevalence of character columns in this table; not a surprise I guess. Character columns are surprisingly painful to deal with.

embray added a commit to embray/astropy that referenced this pull request Jul 17, 2013
… cost of memory performance, because it requires copying every string column into memory when reading a table; however, this was already the case on Python 3. At some point it would be nice to develop a better workaround for that but it's outside the scope of this issue.
@embray

embray commented Jul 17, 2013

Copy link
Copy Markdown
Member

8 months later here's a probable fix for this. Unfortunately it's a sort of thing that's hard to write a test for (though maybe one day I will set up a test framework for reference cycle-related issues).

@mdboom

mdboom commented Jul 17, 2013

Copy link
Copy Markdown
Contributor

Very subtle!

@embray

embray commented Jul 17, 2013

Copy link
Copy Markdown
Member

I tried to find a solution that still avoided unnecessary copying when possible. And I'm sure it can be done, but the FITS_rec and Table code is such a minefield that anything I tried broke 5 other things.

One thing that would alleviate a lot of issues here, that I need to come back to another time, is a Numpy array type that will allow a bytes array to be viewed as a unicode array so long as the characters are decodable in a given encoding (especially ASCII). Currently it doesn't seem to be possible to do that in Numpy without copying as far as I've been able to figure out.

@mdboom

mdboom commented Jul 17, 2013

Copy link
Copy Markdown
Contributor

DyND will probably do that someday, when it's ready.

But in the shorter term, a bytes dtype with a declared encoding would be very cool, if it could be pulled off.

@embray

embray commented Jul 17, 2013

Copy link
Copy Markdown
Member

I can't keep up with all the stuff Contiuum is doing, but it still kind of irks me that they're doing it while basically drawing away from much new development on Numpy, many of the problems of which I think are fixable with a little love :/

@embray

embray commented Jul 17, 2013

Copy link
Copy Markdown
Member

Before I merge this I want to scrounge around for an older issue that reported performance issues with string columns in tables. I want to find that issue and make sure that this fix doesn't introduce a regression on that.

@taldcroft

Copy link
Copy Markdown
Member Author

@iguananaut - thanks for getting back to this. I haven't run into it again, so it's apparently in the corners a bit. Anyway, I haven't had a chance to test myself but assuming it plugs the leak for the example I gave then I'm good with this.

@embray

embray commented Jul 17, 2013

Copy link
Copy Markdown
Member

Yup, it works for your example. I just need to explore the performance impact of this fix a little more...

@embray

embray commented Jul 18, 2013

Copy link
Copy Markdown
Member

In light of this thread (https://groups.google.com/forum/#!topic/astropy-dev/zRIZ6rr4JPg) started by Demitri I'm going to put off merging this fix for now until I can spend a little more time exploring the performance implications of string columns in general. I think that his problem only should have occurred on Python 3 where string copying is guaranteed to occur. If it's happening on Python 2 then I'm at a loss...it's possible there are performance issues unrelated to strings that also need to be considered.

@embray embray modified the milestones: v0.3.2, v0.3.1 Feb 25, 2014
@embray embray modified the milestones: v1.0.0, v0.3.2 May 2, 2014
@embray

embray commented May 2, 2014

Copy link
Copy Markdown
Member

Probably going to shunt this off for a while. I'm working on some new approaches to handling FITS tables that will nullify this issue anyway.

@taldcroft

Copy link
Copy Markdown
Member Author

This hasn't actually come up in my daily work since then, even if it isn't fixed.

@astrofrog astrofrog removed this from the v1.0.0 milestone Jan 15, 2015
@astrofrog astrofrog assigned embray and unassigned embray Mar 21, 2015
@taldcroft

Copy link
Copy Markdown
Member Author

@embray - is this at all relevant any more? Can I just close this?

@embray

embray commented Jun 23, 2015

Copy link
Copy Markdown
Member

I think it's still relevant--it cuts to the core of Numpy's unicode issues.

@embray

embray commented Jan 21, 2016

Copy link
Copy Markdown
Member

I just pushed an entirely new fix for this issue (which depends on #4514).

Believe it or not, despite the age of this issue, the root problem was essentially the same (and has actually grown more severe, I think). The issue applies to both Python 2 and 3. This should fix the issue more thoroughly than my original fix, and without any noticeable performance impacts.

One thing I am still slightly mystified by is the role of .copy(). I feel like this issue should not have depended on it, but I could be wrong.

…, and I don't think it's needed at all. Furthermore, it issues UserWarnings about internal implementation stuff that users shouldn't have any knowledge of.
@embray embray added this to the v1.0.9 milestone Jan 21, 2016
@embray

embray commented Jan 21, 2016

Copy link
Copy Markdown
Member

Turns out this is actually far from the end of the reference cycles..... 😭

…self' as an immediate ancestor, but they may have a mutual ancestor, in which case the same policy should apply.
…other unresolved reference cycles involving FITS_recs. But this test demonstrates that the specific use case in astropy#520 is at least fixed.  I will open a separate issue for the other case.
… like there's a package for objgraph in anaconda
@embray

embray commented Jan 27, 2016

Copy link
Copy Markdown
Member

Okay, I'll probably merge this and open another issue for the other known memory leak. Fixing the other one is probably not worth the effort compared to deprecating the old FITS_rec / ColDefs interface in favor of full replacement with Table.

embray added a commit that referenced this pull request Jan 27, 2016
Memory leak reading and copying bin table FITS files with io.fits
@embray
embray merged commit b3c4515 into astropy:master Jan 27, 2016
@embray

embray commented Jan 27, 2016

Copy link
Copy Markdown
Member

Oops, I forgot to update the changelog entry again (I saw the changelog entries from #4514 and assumed I had).

@embray
embray deleted the issue-520 branch January 27, 2016 17:38
embray added a commit that referenced this pull request Jan 27, 2016
embray added a commit that referenced this pull request Jan 27, 2016
Memory leak reading and copying bin table FITS files with io.fits
embray added a commit that referenced this pull request Jan 27, 2016
embray added a commit that referenced this pull request Jan 27, 2016
Memory leak reading and copying bin table FITS files with io.fits
Conflicts:
	appveyor.yml
embray added a commit that referenced this pull request Jan 27, 2016
embray added a commit to embray/astropy that referenced this pull request Jan 28, 2016
…ibiting the reference leak. Remove the previous note in the test for astropy#520 about the existing reference leak, since it *shouldn't* apply any longer.
embray added a commit to embray/astropy that referenced this pull request Jan 28, 2016
…ibiting the reference leak. Remove the previous note in the test for astropy#520 about the existing reference leak, since it *shouldn't* apply any longer.
mvnnn pushed a commit to mvnnn/astropy that referenced this pull request Jan 30, 2016
…other unresolved reference cycles involving FITS_recs. But this test demonstrates that the specific use case in astropy#520 is at least fixed.  I will open a separate issue for the other case.
mvnnn pushed a commit to mvnnn/astropy that referenced this pull request Jan 30, 2016
mvnnn pushed a commit to mvnnn/astropy that referenced this pull request Jan 30, 2016
…ibiting the reference leak. Remove the previous note in the test for astropy#520 about the existing reference leak, since it *shouldn't* apply any longer.
dhomeier pushed a commit to dhomeier/astropy that referenced this pull request Jun 12, 2016
…other unresolved reference cycles involving FITS_recs. But this test demonstrates that the specific use case in astropy#520 is at least fixed.  I will open a separate issue for the other case.
dhomeier pushed a commit to dhomeier/astropy that referenced this pull request Jun 12, 2016
dhomeier pushed a commit to dhomeier/astropy that referenced this pull request Jun 12, 2016
…ibiting the reference leak. Remove the previous note in the test for astropy#520 about the existing reference leak, since it *shouldn't* apply any longer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants