Skip to content

Avoid loading entire dataset by getting the nbytes in an array - #7356

Merged
dcherian merged 7 commits into
pydata:mainfrom
hmaarrfk:avoid_memory_instantiation
Dec 12, 2022
Merged

Avoid loading entire dataset by getting the nbytes in an array#7356
dcherian merged 7 commits into
pydata:mainfrom
hmaarrfk:avoid_memory_instantiation

Conversation

@hmaarrfk

Copy link
Copy Markdown
Contributor

Using .data accidentally tries to load the whole lazy arrays into memory.

Sad.

  • Closes #xxxx
  • Tests added
  • User visible changes (including notable bug fixes) are documented in whats-new.rst
  • New functions/methods are listed in api.rst

@hmaarrfk
hmaarrfkforce-pushed the avoid_memory_instantiation branch from 353c9b8 to 4729a35CompareDecember 5, 2022 03:30
@hmaarrfk

Copy link
Copy Markdown
ContributorAuthor

I personally do not even think the hasattr is really that useful. You might as well use size and itemsize

@hmaarrfk
hmaarrfk marked this pull request as ready for review December 5, 2022 03:31
Using `.data` accidentally tries to load the whole lazy arrays into
memory.
Sad.
@hmaarrfk
hmaarrfkforce-pushed the avoid_memory_instantiation branch from 4729a35 to 8826c14CompareDecember 5, 2022 03:32
@hmaarrfk

Copy link
Copy Markdown
ContributorAuthor

Looking into the history a little more. I seem to be proposing to revert:
60f8c3d

I think this is important since many users have arrays that are larger than memory. For me, I found this bug when trying to access the number of bytes in a 16GB dataset that I'm trying to load on my wimpy laptop. Not fun to start swapping. I feel like others might be hitting this too.

xref:
#6797
#4842

@hmaarrfk
hmaarrfkforce-pushed the avoid_memory_instantiation branch from 5d3a5d3 to 1543c62CompareDecember 5, 2022 03:58
@hmaarrfk

Copy link
Copy Markdown
ContributorAuthor

I think that at the very lease, the current implementation works as well as the old one for arrays that are defined by the sparse package.

@hmaarrfk

Copy link
Copy Markdown
ContributorAuthor

It seems that checking hasattr on the _data variable achieves both purposes.

Comment threadxarray/tests/test_dataarray.py Outdated
@hmaarrfk
hmaarrfkforce-pushed the avoid_memory_instantiation branch from 1543c62 to a04ba20CompareDecember 5, 2022 12:41
@Illviljan

Copy link
Copy Markdown
Contributor

Is that test targetting your issue with RAM crashing the laptop? Shouldn't there be some check if the values were loaded?

How did you import your data? self.data looks like this:

@property
defdata(self) ->Any:
"""
The Variable's data as an array. The underlying array type
(e.g. dask, sparse, pint) is preserved.
See Also
--------
Variable.to_numpy
Variable.as_numpy
Variable.values
"""
ifis_duck_array(self._data):
returnself._data
else:
returnself.values

I was expecting your data to be a duck_array?

@hmaarrfk

Copy link
Copy Markdown
ContributorAuthor

No explicit test was added to ensure that the data wasn't loaded. I just experienced this bug enough (we would accidentally load 100GB files in our code base) that I knew exactly how to fix it.

If you want i can add a test to ensure that future optimizations to nbytes do not trigger a data load.

I was hoping the 1 line fix would be a shoe in.

@hmaarrfk

Copy link
Copy Markdown
ContributorAuthor

The data is loaded from an NetCDF store through open_dataset

@Illviljan

Copy link
Copy Markdown
Contributor

I'm not really opposed to this change, shape and dtype uses self._data aswell.

Without using chunks={} in open_dataset? I just find it a little odd that it's not a duck_array, what type is self._data?

This test just looked so similar to the tests in #6797. I think you can do a similar lazy test taking inspiration from:

deftest_lazy_array_wont_compute() ->None:
fromxarray.core.indexingimportLazilyIndexedArray
classLazilyIndexedArrayNotComputable(LazilyIndexedArray):
def__array__(self, dtype=None):
raiseNotImplementedError("Computing this array is not possible.")
arr=LazilyIndexedArrayNotComputable(np.array([1, 2]))
var=xr.DataArray(arr)
# These will crash if var.data are converted to numpy arrays:
var.__repr__()
var._repr_html_()

@hmaarrfk

Copy link
Copy Markdown
ContributorAuthor

Very smart test!

@hmaarrfk

Copy link
Copy Markdown
ContributorAuthor

Yes, without chunks of anything

Comment threadxarray/core/variable.py
Comment threadxarray/tests/test_dataarray.py Outdated

@dcheriandcherian left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. thanks!

@dcheriandcherian added the plan to merge Final call for comments label Dec 7, 2022
@dcheriandcherian changed the title Avoid instantiating entire dataset by getting the nbytes in an arrayAvoid loading entire dataset by getting the nbytes in an arrayDec 12, 2022
@dcherian
dcherian enabled auto-merge (squash) December 12, 2022 16:27
@dcherian
dcherian merged commit 021c73e into pydata:mainDec 12, 2022
@hmaarrfk

Copy link
Copy Markdown
ContributorAuthor

👍🏾

@hmaarrfk

Copy link
Copy Markdown
ContributorAuthor

Any chance of a release, this is quite breaking for large datasets that can only be out of memory.

dcherian added a commit to dcherian/xarray that referenced this pull request Jan 18, 2023
* main: (41 commits)
v2023.01.0 whats-new (pydata#7440)
explain keep_attrs in docstring of apply_ufunc (pydata#7445)
Add sentence to open_dataset docstring (pydata#7438)
pin scipy version in doc environment (pydata#7436)
Improve performance for backend datetime handling (pydata#7374)
fix typo (pydata#7433)
Add lazy backend ASV test (pydata#7426)
Pull Request Labeler - Workaround sync-labels bug (pydata#7431)
see also : groupby in resample doc and vice-versa (pydata#7425)
Some alignment optimizations (pydata#7382)
Make `broadcast` and `concat` work with the Array API (pydata#7387)
remove `numbagg` and `numba` from the upstream-dev CI (pydata#7416)
[pre-commit.ci] pre-commit autoupdate (pydata#7402)
Preserve original dtype when accessing MultiIndex levels (pydata#7393)
[pre-commit.ci] pre-commit autoupdate (pydata#7389)
[pre-commit.ci] pre-commit autoupdate (pydata#7360)
COMPAT: Adjust CFTimeIndex.get_loc for pandas 2.0 deprecation enforcement (pydata#7361)
Avoid loading entire dataset by getting the nbytes in an array (pydata#7356)
`keep_attrs` for pad (pydata#7267)
Bump pypa/gh-action-pypi-publish from 1.5.1 to 1.6.4 (pydata#7375)
...
@TomNicholas

Copy link
Copy Markdown
Member

This came up in the xarray office hours today, and I'm confused why this PR made any difference to the behavior at all? The .data property just points to ._data, so why would it matter which one we check?

@dcherian

dcherian commented Mar 17, 2023

Copy link
Copy Markdown
Contributor

Because we have lazy data reading functionality

importxarrayasxrds=xr.tutorial.open_dataset("air_temperature")
var=ds.air.variableprint(type(var._data)) # memory cached arrayprint(type(var._data.array.array)) # ah that's wrapping a lazy array, no data read in yetprint(var._data.size) # can access sizeprint(type(var._data.array.array)) # still a lazy array#.data forces a disk loadprint(type(var.data)) # oops disk-loadprint(type(var._data)) # "still memory cached array"print(type(var._data.array.array)) # but that's wrapping numpy data in memory
<class 'xarray.core.indexing.MemoryCachedArray'>
<class 'xarray.core.indexing.LazilyIndexedArray'>
3869000
<class 'xarray.core.indexing.LazilyIndexedArray'>
<class 'numpy.ndarray'>
<class 'xarray.core.indexing.MemoryCachedArray'>
<class 'numpy.ndarray'>

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plan to mergeFinal call for comments

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@hmaarrfk@Illviljan@TomNicholas@dcherian@headtr1ck