brandon-rhodes / python-jplephem

Python version of NASA DE4xx ephemerides, the basis for the Astronomical Alamanac
MIT License
110 stars 29 forks source link

jplephem should cleanup resources better #24

Closed kghose closed 6 years ago

kghose commented 6 years ago

Use case

I have a file (ast343de430.bsp) containing about 300 bodies. I want to plot the positions of all the bodies at one given instant of time.

Problem

When I use the following code

import numpy as np
from jplephem.spk import SPK 
kernel = SPK.open('ast343de430.bsp')
pos = np.array([kernel[p].compute(2457061.5) for p in kernel.pairs.keys()])

I get: OSError: [Errno 24] Too many open files

Expected behavior

Each call to kernel[p].compute(2457061.5) should clean up such that we don't run into this issue.

Kudos

BTW, library writers often just get gripes from users, so I'd like to point out here that I really like jplephem and it is my go to Python solution for all things SPK related. Thank you for all the effort you have put into it!

brandon-rhodes commented 6 years ago

Thanks for the kudos, that was very nice of you to include! :)

The library is designed to open the file exactly once, when open() is called, so I'm surprised that you're getting a "too many open files" error — but maybe your system also returns than when some other limit is reached, like the number of memory maps? Because the library does map into memory each segment and then keeps it there, so that code like:

a = kernel[p].compute(t0)
a = kernel[p].compute(t1)

—doesn't have to do the expensive memory map operation twice but can keep using the same memory map over again.

How many segments does your kernel have?

What is the full traceback, so that we can see which line of code is erroring? That might tell us which resource is running out on your system.

kghose commented 6 years ago
In [50]: import numpy as np
    ...: from jplephem.spk import SPK 
    ...: kernel = SPK.open('ast343de430.bsp')
    ...: pos = np.array([kernel[p].compute(2457061.5) for p in kernel.pairs.keys()])
    ...: 
    ...: 
ERROR:root:Internal Python error in the inspect module.
Below is the traceback from this internal error.

Traceback (most recent call last):
  File "/Users/kghose/miniconda2/envs/kaggle/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 2963, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-50-08a22ec2a65b>", line 4, in <module>
    pos = np.array([kernel[p].compute(2457061.5) for p in kernel.pairs.keys()])
  File "<ipython-input-50-08a22ec2a65b>", line 4, in <listcomp>
    pos = np.array([kernel[p].compute(2457061.5) for p in kernel.pairs.keys()])
  File "/Users/kghose/miniconda2/envs/kaggle/lib/python3.6/site-packages/jplephem/spk.py", line 114, in compute
  File "/Users/kghose/miniconda2/envs/kaggle/lib/python3.6/site-packages/jplephem/spk.py", line 161, in generate
  File "/Users/kghose/miniconda2/envs/kaggle/lib/python3.6/site-packages/jplephem/spk.py", line 136, in _load
  File "/Users/kghose/miniconda2/envs/kaggle/lib/python3.6/site-packages/jplephem/daf.py", line 154, in map_array
  File "/Users/kghose/miniconda2/envs/kaggle/lib/python3.6/site-packages/jplephem/daf.py", line 112, in map_words
OSError: [Errno 24] Too many open files

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/kghose/miniconda2/envs/kaggle/lib/python3.6/site-packages/IPython/core/interactiveshell.py", line 1863, in showtraceback
    stb = value._render_traceback_()
AttributeError: 'OSError' object has no attribute '_render_traceback_'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/kghose/miniconda2/envs/kaggle/lib/python3.6/site-packages/IPython/core/ultratb.py", line 1095, in get_records
  File "/Users/kghose/miniconda2/envs/kaggle/lib/python3.6/site-packages/IPython/core/ultratb.py", line 311, in wrapped
  File "/Users/kghose/miniconda2/envs/kaggle/lib/python3.6/site-packages/IPython/core/ultratb.py", line 345, in _fixed_getinnerframes
  File "/Users/kghose/miniconda2/envs/kaggle/lib/python3.6/inspect.py", line 1480, in getinnerframes
  File "/Users/kghose/miniconda2/envs/kaggle/lib/python3.6/inspect.py", line 1438, in getframeinfo
  File "/Users/kghose/miniconda2/envs/kaggle/lib/python3.6/inspect.py", line 693, in getsourcefile
  File "/Users/kghose/miniconda2/envs/kaggle/lib/python3.6/inspect.py", line 722, in getmodule
  File "/Users/kghose/miniconda2/envs/kaggle/lib/python3.6/inspect.py", line 706, in getabsfile
  File "/Users/kghose/miniconda2/envs/kaggle/lib/python3.6/posixpath.py", line 374, in abspath
OSError: [Errno 24] Too many open files
kghose commented 6 years ago
In [2]: print(kernel)
File type DAF/SPK and format LTL-IEEE with 343 segments:
2287184.50..2688976.50  Sun (10) -> Unknown Target (2000276)
2287184.50..2688976.50  Sun (10) -> Unknown Target (2000145)
2287184.50..2688976.50  Sun (10) -> Unknown Target (2000268)
...
kghose commented 6 years ago
> launchctl limit maxfiles
    maxfiles    256            unlimited 

~Restricting the keys to the first 256 gets rid of this problem, naturally.~

Sorry, this number can vary - restricting it to the first 200 seems to always run. Getting closer to 256 starts to get me more and more failures (i.e. there is some variability in when it actually fails)

brandon-rhodes commented 6 years ago

Wow ­— 343 segments!

The library's current approach, of only mmap'ing the segments that the user tries to access, is designed to minimize the amount of the process's RAM image that's in use by the library in the case where someone only needs to use a few segements. On 32-bit machines and kernels with only a few huge segments, the trade-off seemed important.

But with modern 64-bit address spaces it probably would make more sense to simply mmap() the entire kernel, instead of making a separate map for each segment — and, of course, that approach would prevent you from running out of file descriptors in the case where the number of segments is large.

I wonder if I should switch the default behavior for everyone, or make the new behavior an option? I'm thinking of kernels like jup310.bsp whose 931M would be a pretty big dent in the address space of a 32-bit program.

brandon-rhodes commented 6 years ago

I've just released version 2.8, which should hopefully fix your problem. I'm going to close the issue, but feel free to re-open if you run into this problem again!