-
Notifications
You must be signed in to change notification settings - Fork 283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Basic functional lazy saving. #5031
Conversation
@pp-mo We could also offer a similar pattern for parallel loading, akin to |
An object which mimics the data access of a netCDF4.Variable, and can be written to. | ||
It encapsulates the netcdf file and variable which are actually to be written to. | ||
This opens the file each time, to enable writing the data chunk, then closes it. | ||
TODO: could be improved with a caching scheme, but this just about works. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pp-mo Apart from caching, locking should be considered, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dead right. I had not understood much, which is why it did not work with 'distributed'.
Hopefully that is now fixed @ed654d ...
@pp-mo Kicking the tyres with this using |
Updated version should now work with 'distributed', I think. My testcode, running via distributed/slurm [click to expand...]
.. sample of resulting output [click to expand...]
|
Status summaryMy experience has been that the problems with 'distributed' mostly behaved just the same with a local 'processes' scheduler. The existing failing tests here appear due to some mock-ist testing, whereby a Mock is passed as a datapath, which can't be converted to an absolute filepath. So, the new code breaks that, but that should be easily fix-able. This whole idea is still somewhat experimental -- work to do, not least add some kind of testing. |
Some thoughts from offline discussion with @bjlittle ... This is certainly possible, but it's not nearly so crucial to have specialist support IMHO, because ... It's also unfortunate, but I think unavoidable, that such a "delayed load" cannot return a delayed per-input-cube, since that would involve most of the loading process at that point (to know how many cubes will result). Instead, it can really only return a CubeList, whose content wouldn't generally be guaranteed in advance. Also, of course, the large "cube.data" and aux-coords-like arrays would still remain lazy on initial load, so the whole thing doesn't necessarily give any more efficient loading than the normal way. |
is streamed directly into file variables during the save operation. | ||
If False, the file is created as normal, but computation and streaming of | ||
any lazy array content is instead deferred to :class:`dask.delayed` objects, | ||
which are held in a list in the saver 'delayed_writes' property. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This bit can probably be treated as internal/private detail now.
Probably better to make .deferred_writes
private, and ._deferred_save()
public.
@@ -2509,6 +2583,39 @@ def store(data, cf_var, fill_value): | |||
) | |||
warnings.warn(msg.format(cf_var.name, fill_value)) | |||
|
|||
def _deferred_save(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth making public?
Almost certainly better named 'delayed' than 'deferred' (!)
Thanks for all the good work @pp-mo! I tried this using a |
That is great news, thanks for testing !
Well, it's just a way of solving the problem. And I think a simple one 👍. N.B. the ".lock" files should be removing themselves. |
The .lock files are indeed removing themselves, but I have some doubts about this approach:
|
I would be happy to have a look at the code myself and see if we can figure it out together. Would that help? I will probably have time for that next week. |
Re: "if your script is killed halfway through" Re: race conditions I'm not saying there is not a better way, but I was more concerned about the performance/efficiency of the solution. So from a simplicity + generality PoV, 'filelock' is still winning for me ! |
P.S. a little searching turns up 'sherlock', which implements a generic multiprocessing-like interface to various solutions, including 'Redis' and 'filelock'. Well, I have at least heard of those ones. To be clear : I don't really like to include distributed-specific code, but maybe there are some good benefits? |
Hi @pp-mo, My apologies for being slow to get back to this. I promise I will look into the locking issue in more detail together with my colleague @fnattino and get back to you on the topic. Meanwhile, I was trying to integrate this into the ESMValCore and ran into an issue: >>> import iris.cube
>>> import numpy as np
>>> cube = iris.cube.Cube(np.array([1]))
>>> iris.save(cube, target='tmp.nc', compute=False)
[]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/bandela/src/scitools/iris/lib/iris/io/__init__.py", line 447, in save
result = saver(source, target, **kwargs)
File "/home/bandela/src/scitools/iris/lib/iris/fileformats/netcdf/saver.py", line 2917, in save
result = sman._deferred_save()
File "/home/bandela/src/scitools/iris/lib/iris/fileformats/netcdf/saver.py", line 2603, in _deferred_save
sources, targets = zip(*self.deferred_writes)
ValueError: not enough values to unpack (expected 2, got 0) it looks like the code in this branch does not work if the data is not lazy. I'm not sure what it is supposed to do if the data is not lazy, but a clear error message would be nice. Do you have ideas on this? |
I think if we are requested to do a deferred save, then we must do one even if the data is already in-memory. By my reading, these 2 lines are making the choice in the wrong order.
|
WARNING: So, expect more changes to come here. |
P.S. please don't worry about the timeliness. |
Hello @pp-mo, as @bouweandela mentioned I have been running some tests with the Iris lazy saving together with ESMValCore. Thanks for all the nice work, it indeed seems to work well with ESMValCore@dask-distributed. Just a couple of comments:
|
# Create a lock to satisfy the da.store call. | ||
# We need a serialisable lock for scheduling with processes or distributed. | ||
# See : https://github.com/dask/distributed/issues/780 | ||
# However, this does *not* imply safe access for file writing in parallel. | ||
# For that, DeferredSaveWrapper uses a filelock as well. | ||
lock = SerializableLock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you mention, the synchronisation of the parallel writing should be taken care of by the DeferredSaveWrapper, so one could set lock=False
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure! Just thought I would write this down, since it also took me a while to figure out from Xarray..
|
||
# Create a single delayed da.store operation to complete the file. | ||
sources, targets = zip(*self.deferred_writes) | ||
result = da.store(sources, targets, compute=False, lock=lock) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of the sources can be numpy array here, so one could do something like:
result = da.store(sources, targets, compute=False, lock=lock) | |
result = da.store([da.asarray(s) for s in sources], targets, compute=False, lock=lock) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment above
I think this is the same issue ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's also related to having real data. One needs to make sure these are converted to Dask arrays.
IIUC, "filelock" is simply a way of ensuring a mutex that is shared across possibly multiple systems or CPUS. So it uses the common filesystem as a communication mechanism between them, and connects any that use that same file path. |
Yes, I think this would be useful.
Yes, I agree. Still learning here !
Yes also agreed ! |
BTW my comment is a bit hidden above, but when I said ..
I really meant that, since it seems we will be adding file-system locking throughout, IMHO we will almost certainly follow xarray in using different locks for different schedulers. This will introduce more coupling and complexity, as I was complaining above, but it will also replace the use of 'filelock' which @bouweandela was concerned about. |
Just wanted to mention that while Xarray seems to support threading, multiprocessing and distributed as schedulers for writing NetCDFs, the multiprocessing implementation based on the |
That sounds good to me. If someone really wanted to use multiple processes on a single machine, they could just use the distributed |
Just FYI I have looked into this within work for #5191, and it is not that simple. So I'm still considering this an "optional extra" for purposes of #5191 |
Closing this now, in favour of #5191
|
@bjlittle @bouweandela following discussion yesterday, I couldn't resist having a bash at lazy saving.
So here it is!
This is a slightly crude first attempt, but seems to basically work.
In principle, addresses #4190
@bouweandela can you see if this actually functions for parallelising + whether it shows a benefit ?
Sorry again that I can't be there this morning
In yesterday's discussion, I got a much clearer idea of what is needed.
I think also @zklaus explained to me what the xarray implementation actually does.
So I've tried to copy that idea.
My simple test code [click to expand]
..and sample resulting output [click to expand]