Skip to content

Set write_empty_chunks to default to False - #853

Merged
joshmoore merged 14 commits into
zarr-developers:masterfrom
jni:default-write-empty-chunks-false
Feb 4, 2022
Merged

Set write_empty_chunks to default to False#853
joshmoore merged 14 commits into
zarr-developers:masterfrom
jni:default-write-empty-chunks-false

Conversation

@jni

@jnijni commented Oct 20, 2021

Copy link
Copy Markdown
Contributor

In #738, despite general agreement that most people expect write_empty_chunks to default to False, @d-v-b decided to go the safe, uncontroversial route to get the PR merged, and made the default value True. All this PR does is set the default to False.

Convenient links to discussion suggesting write_empty_chunks=False should be the default:

  • @meggartsays: "In Zarr.jl, not writing empty chunks is already the default [...] so I would be in favor of changing the default behavior in Python as well.
  • @shoyersays: "it is not clear to me why you would not want to do this. I think this could be the new default behavior."
  • @martindurantsays: "I am also surprised that this is not the default, and would agree that I can't see people wanting to use the current behaviour ever."

There were performance concerns with FSSpec bulk delete items, but it seems that they were addressed upstream.

TODO:

  • Add unit tests and/or doctests in docstrings
  • Add docstrings and API docs for any new/modified user-facing classes and functions
  • New/modified features documented in docs/tutorial.rst
  • Changes documented in docs/release.rst
  • GitHub Actions have all passed
  • Test coverage is 100% (Codecov passes)

@d-v-b

Copy link
Copy Markdown
Contributor

Sounds great to me :) Thanks for doing what I didn't have the courage to do.

@jni
jniforce-pushed the default-write-empty-chunks-false branch from d83df68 to f3280a7CompareOctober 21, 2021 00:22
@jakirkham

Copy link
Copy Markdown
Member

@joshmoore wdyt?

@rabernat

Copy link
Copy Markdown
Contributor

👍 from me. I think this is a much better default.

@jakirkham

Copy link
Copy Markdown
Member

There are some lines scattered throughout the tests like these

write_empty_chunks=kwargs.pop('write_empty_chunks', True)

write_empty_chunks=kwargs.pop('write_empty_chunks', True)

Do we want to update those as well?

@jni

jni commented Nov 30, 2021

Copy link
Copy Markdown
ContributorAuthor

Do we want to update those as well?

🤷 All this means is that more tests will run with the default True. This might be useful for historical reasons. Both True and False are explicitly tested so I don't think we need to worry.

@jakirkhamjakirkham mentioned this pull request Dec 1, 2021
@jni
jniforce-pushed the default-write-empty-chunks-false branch from 3e7dbea to 6b461a7CompareDecember 2, 2021 01:48
@jni

jni commented Dec 2, 2021

Copy link
Copy Markdown
ContributorAuthor

This is now rebased on the latest master.

@joshmoore

Copy link
Copy Markdown
Member

Per #901, @d-v-b was working on a blurb for blog/release notes/docs so that users are maximally informed of the change.

@joshmoore

Copy link
Copy Markdown
Member

Capturing initial results from @d-v-b here: "users with "dense" data will see a ~15% increase in write times"

@d-v-b

d-v-b commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

N.B. that result doesn't include compression time. I will provide improved benchmarking code later today with a more realistic workflow.

@d-v-b

d-v-b commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

some interesting benchmarking results. see this repo for code: https://github.com/d-v-b/zarr-benchmarks

Each test writes a 64 MB chunk 128 times with write_empty_chunks drawn from {True, False} and the write value drawn from {array of 0s, array of random ints} , i.e. 4 different conditions. The plots show histrograms with an X axis that is the write duration normalized to the median of the time required under the (write_empty_chunks=False, write_value=0) condition.

First, with no compressor:
image

Next, with Blosc compression:
image

And last, with GZip compression:
image

Takeaways:

  • The overhead associated with the emptiness checking is exposed with Blosc compression and no compressor.
  • With blosc, writing empty chunks is actually faster than an emptiness check -> no write.
  • With GZip, handling empty chunks is much faster with write_empty_chunks=False, and the overhead associated with emptiness checking is not evident when writing "dense" chunks.

Happy to hear suggestions for changing the benchmark / plotting. E.g., maybe we want to show absolute duration instead of relative durations.

@shoyer

Copy link
Copy Markdown
Contributor

What sort of local disk are you using? I can imagine very different results for SSD vs HDD vs cloud object stores. Extra copies are never desirable, but in most cases I would guess the overhead of checking is acceptable.

@d-v-b

d-v-b commented Dec 8, 2021

Copy link
Copy Markdown
Contributor

@shoyer this is on an SSD, so pretty ideal in terms of storage latency.

@d-v-b

d-v-b commented Dec 14, 2021

Copy link
Copy Markdown
Contributor

@joshmoore I added content to the tutorial docs, let me know if you want more or an expanded release note.

I'm not sure the benchmarks are that illuminating or surprising, so maybe we don't really need to involve them?

@joshmoore

Copy link
Copy Markdown
Member

The location looks good. Could I suggest we try to get two or four simple writes with the different flags and inputs. Something like:

data = np.random.randint(0, 255, size=shape, dtype=DTYPE)
arr = zarr.open(zarr.NestedDirectoryStore(store), write_empty_chunks=write_empty_chunks, ...)
arr[:] = 255

from https://github.com/d-v-b/zarr-benchmarks/blob/main/src/zarr-benchmarks/empty_chunks.py with some form of marker ("15% slower") for someone to see at a glance.

I'm not sure the benchmarks are that illuminating or surprising, so maybe we don't really need to involve them?

Agreed. Happy to have them in your repo. I find them pretty fascinating. ;)

@jakirkham

Copy link
Copy Markdown
Member

@d-v-b, did you have a chance to try the other flags Josh mentioned above? 🙂

@d-v-b

d-v-b commented Jan 5, 2022

Copy link
Copy Markdown
Contributor

I added a short benchmark example, but I can't build the docs for some reason (tox -e docs yields some inscrutable error from a different part of the codebase)

@d-v-b

d-v-b commented Jan 5, 2022

Copy link
Copy Markdown
Contributor

Oh and there's a new test failure in an unrelated part of the codebase. Not sure what's going on there.

@joshmoore

Copy link
Copy Markdown
Member

Oy vey. That's a new year for you. The new failures is related to the category filter. I don't remember anything that should impact it. Just starting to look at the docs failure.

(Thanks for the benchmark example!)

@joshmoore

Copy link
Copy Markdown
Member

Ok, @d-v-b, that should fix the docs build. Let's see what's failing on this run.

@jni

jni commented Jan 10, 2022

Copy link
Copy Markdown
ContributorAuthor

Hi both, I'm unclear about whether I need to do anything here, please ping me directly if you want me to update something. 😊

@joshmoore

Copy link
Copy Markdown
Member

Sorry for dropping the ball here. Don't think there's anything needed from your side, @jni. Just very unsure what's up with this failing test. Other than that, I'd very much like to get 2.11 out ASAP.

The call to `np.any(array)` in zarr.util.all_equal triggers the
following ValueError:
```
> return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
E ValueError: invalid literal for int() with base 10: 'baz'
```
Extending the catch block allows test_array_with_categorize_filter
to pass, but it's unclear if this points to a deeper issue.
@joshmoore

Copy link
Copy Markdown
Member

Ok. Pushed a proposed fix related to @d-v-b's all_equal.

Comment threadzarr/util.py
try:
return not np.any(array)
except TypeError: # pragma: no cover
except (TypeError, ValueError): # pragma: no cover

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What causes the ValueError? Is there a particular array type or value that np.any is raising on?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It occurs when a category value (baz) gets passed to int().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

from failing build
______________________ test_array_with_categorize_filter _______________________
def test_array_with_categorize_filter():
# setup
data = np.random.choice(['foo', 'bar', 'baz'], size=100)
flt = Categorize(dtype=data.dtype, labels=['foo', 'bar', 'baz'])
filters = [flt]
for compressor in compressors:
> a = array(data, chunks=5, compressor=compressor, filters=filters)
zarr/tests/test_filters.py:172: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ zarr/creation.py:366: in array
z[...] = data
zarr/core.py:1285: in __setitem__
self.set_basic_selection(pure_selection, value, fields=fields)
zarr/core.py:1380: in set_basic_selection
return self._set_basic_selection_nd(selection, value, fields=fields)
zarr/core.py:1680: in _set_basic_selection_nd
self._set_selection(indexer, value, fields=fields)
zarr/core.py:1732: in _set_selection
self._chunk_setitem(chunk_coords, chunk_selection, chunk_value, fields=fields)
zarr/core.py:1994: in _chunk_setitem
self._chunk_setitem_nosync(chunk_coords, chunk_selection, value,
zarr/core.py:2002: in _chunk_setitem_nosync
if (not self.write_empty_chunks) and all_equal(self.fill_value, cdata):
zarr/util.py:672: in all_equal
return not np.any(array)
<__array_function__ internals>:180: in any
???
/usr/share/miniconda/envs/minimal/lib/python3.10/site-packages/numpy/core/fromnumeric.py:2395: in any
return _wrapreduction(a, np.logical_or, 'any', axis, None, out,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ obj = array(['baz', 'baz', 'baz', 'bar', 'foo'], dtype='<U3')
ufunc = <ufunc 'logical_or'>, method = 'any', axis = None, dtype = None
out = None, kwargs = {'keepdims': <no value>, 'where': <no value>}
passkwargs = {}
def _wrapreduction(obj, ufunc, method, axis, dtype, out, **kwargs):
passkwargs = {k: v for k, v in kwargs.items()
if v is not np._NoValue}
if type(obj) is not mu.ndarray:
try:
reduction = getattr(obj, method)
except AttributeError:
pass
else:
# This branch is needed for reductions like any which don't
# support a dtype.
if dtype is not None:
return reduction(axis=axis, dtype=dtype, out=out, **passkwargs)
else:
return reduction(axis=axis, out=out, **passkwargs)
> return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
E ValueError: invalid literal for int() with base 10: 'baz'
/usr/share/miniconda/envs/minimal/lib/python3.10/site-packages/numpy/core/fromnumeric.py:86: ValueError

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Interesting. Looks like the error message and type changed from NumPy 1.21 to 1.22. Don't think that was intentional, but could be wrong. Raised issue ( numpy/numpy#20898 )

In any event this workaround seems reasonable in the interim

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great, thanks! Once this is green, I'll move forward with 2.11.0.

@jakirkham

Copy link
Copy Markdown
Member

Hi both, I'm unclear about whether I need to do anything here, please ping me directly if you want me to update something. 😊

If we can convince both you, @jni, and @d-v-b to write a few lines for the blogpost ( #901 ), that would be fantastic! 😃

Can just post in that issue if you like

@joshmoore

Copy link
Copy Markdown
Member

If we can convince both you, @jni, and @d-v-b to write a few lines for the blogpost ( #901 )

e.g. was just mentioning to @MSanKeys963 how cool the use case from https://forum.image.sc/t/data-store-and-library-backend-for-napari-plugin/61779 is ;)

(....still waiting on actions...)

@joshmoore

Copy link
Copy Markdown
Member

Trying to reopen to get tests to complete.

@joshmoorejoshmoore reopened this Jan 26, 2022
@joshmoore

Copy link
Copy Markdown
Member

I've not been able to get the 3.8 and 3.9 tests to run on this PR yet. (3-4 retries)

@jakirkham

Copy link
Copy Markdown
Member

Looks like some of the past commits in this PR (though not all) had the same issue

@joshmoore

Copy link
Copy Markdown
Member

Oddly py 3.7 is still spinning here compared to #951

@joshmoore

Copy link
Copy Markdown
Member

Power cycling for CI.

@joshmoorejoshmoore reopened this Feb 4, 2022
@joshmoore

Copy link
Copy Markdown
Member

Holy moly that was an adventure! Looks like we're good to move forward with 2.11. (🍺 🍸 or similar all around this weekend)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@jni@d-v-b@jakirkham@rabernat@joshmoore@shoyer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Set write_empty_chunks to default to False by jni · Pull Request #853 · zarr-developers/zarr-python · GitHub
Skip to content

Set write_empty_chunks to default to False - #853

Merged
joshmoore merged 14 commits into
zarr-developers:masterfrom
jni:default-write-empty-chunks-false
Feb 4, 2022
Merged

Set write_empty_chunks to default to False#853
joshmoore merged 14 commits into
zarr-developers:masterfrom
jni:default-write-empty-chunks-false

Conversation

@jni

@jnijni commented Oct 20, 2021

Copy link
Copy Markdown
Contributor

In #738, despite general agreement that most people expect write_empty_chunks to default to False, @d-v-b decided to go the safe, uncontroversial route to get the PR merged, and made the default value True. All this PR does is set the default to False.

Convenient links to discussion suggesting write_empty_chunks=False should be the default:

  • @meggartsays: "In Zarr.jl, not writing empty chunks is already the default [...] so I would be in favor of changing the default behavior in Python as well.
  • @shoyersays: "it is not clear to me why you would not want to do this. I think this could be the new default behavior."
  • @martindurantsays: "I am also surprised that this is not the default, and would agree that I can't see people wanting to use the current behaviour ever."

There were performance concerns with FSSpec bulk delete items, but it seems that they were addressed upstream.

TODO:

  • Add unit tests and/or doctests in docstrings
  • Add docstrings and API docs for any new/modified user-facing classes and functions
  • New/modified features documented in docs/tutorial.rst
  • Changes documented in docs/release.rst
  • GitHub Actions have all passed
  • Test coverage is 100% (Codecov passes)

@d-v-b

Copy link
Copy Markdown
Contributor

Sounds great to me :) Thanks for doing what I didn't have the courage to do.

@jni
jniforce-pushed the default-write-empty-chunks-false branch from d83df68 to f3280a7CompareOctober 21, 2021 00:22
@jakirkham

Copy link
Copy Markdown
Member

@joshmoore wdyt?

@rabernat

Copy link
Copy Markdown
Contributor

👍 from me. I think this is a much better default.

@jakirkham

Copy link
Copy Markdown
Member

There are some lines scattered throughout the tests like these

write_empty_chunks=kwargs.pop('write_empty_chunks', True)

write_empty_chunks=kwargs.pop('write_empty_chunks', True)

Do we want to update those as well?

@jni

jni commented Nov 30, 2021

Copy link
Copy Markdown
ContributorAuthor

Do we want to update those as well?

🤷 All this means is that more tests will run with the default True. This might be useful for historical reasons. Both True and False are explicitly tested so I don't think we need to worry.

@jakirkhamjakirkham mentioned this pull request Dec 1, 2021
@jni
jniforce-pushed the default-write-empty-chunks-false branch from 3e7dbea to 6b461a7CompareDecember 2, 2021 01:48
@jni

jni commented Dec 2, 2021

Copy link
Copy Markdown
ContributorAuthor

This is now rebased on the latest master.

@joshmoore

Copy link
Copy Markdown
Member

Per #901, @d-v-b was working on a blurb for blog/release notes/docs so that users are maximally informed of the change.

@joshmoore

Copy link
Copy Markdown
Member

Capturing initial results from @d-v-b here: "users with "dense" data will see a ~15% increase in write times"

@d-v-b

d-v-b commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

N.B. that result doesn't include compression time. I will provide improved benchmarking code later today with a more realistic workflow.

@d-v-b

d-v-b commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

some interesting benchmarking results. see this repo for code: https://github.com/d-v-b/zarr-benchmarks

Each test writes a 64 MB chunk 128 times with write_empty_chunks drawn from {True, False} and the write value drawn from {array of 0s, array of random ints} , i.e. 4 different conditions. The plots show histrograms with an X axis that is the write duration normalized to the median of the time required under the (write_empty_chunks=False, write_value=0) condition.

First, with no compressor:
image

Next, with Blosc compression:
image

And last, with GZip compression:
image

Takeaways:

  • The overhead associated with the emptiness checking is exposed with Blosc compression and no compressor.
  • With blosc, writing empty chunks is actually faster than an emptiness check -> no write.
  • With GZip, handling empty chunks is much faster with write_empty_chunks=False, and the overhead associated with emptiness checking is not evident when writing "dense" chunks.

Happy to hear suggestions for changing the benchmark / plotting. E.g., maybe we want to show absolute duration instead of relative durations.

@shoyer

Copy link
Copy Markdown
Contributor

What sort of local disk are you using? I can imagine very different results for SSD vs HDD vs cloud object stores. Extra copies are never desirable, but in most cases I would guess the overhead of checking is acceptable.

@d-v-b

d-v-b commented Dec 8, 2021

Copy link
Copy Markdown
Contributor

@shoyer this is on an SSD, so pretty ideal in terms of storage latency.

@d-v-b

d-v-b commented Dec 14, 2021

Copy link
Copy Markdown
Contributor

@joshmoore I added content to the tutorial docs, let me know if you want more or an expanded release note.

I'm not sure the benchmarks are that illuminating or surprising, so maybe we don't really need to involve them?

@joshmoore

Copy link
Copy Markdown
Member

The location looks good. Could I suggest we try to get two or four simple writes with the different flags and inputs. Something like:

data = np.random.randint(0, 255, size=shape, dtype=DTYPE)
arr = zarr.open(zarr.NestedDirectoryStore(store), write_empty_chunks=write_empty_chunks, ...)
arr[:] = 255

from https://github.com/d-v-b/zarr-benchmarks/blob/main/src/zarr-benchmarks/empty_chunks.py with some form of marker ("15% slower") for someone to see at a glance.

I'm not sure the benchmarks are that illuminating or surprising, so maybe we don't really need to involve them?

Agreed. Happy to have them in your repo. I find them pretty fascinating. ;)

@jakirkham

Copy link
Copy Markdown
Member

@d-v-b, did you have a chance to try the other flags Josh mentioned above? 🙂

@d-v-b

d-v-b commented Jan 5, 2022

Copy link
Copy Markdown
Contributor

I added a short benchmark example, but I can't build the docs for some reason (tox -e docs yields some inscrutable error from a different part of the codebase)

@d-v-b

d-v-b commented Jan 5, 2022

Copy link
Copy Markdown
Contributor

Oh and there's a new test failure in an unrelated part of the codebase. Not sure what's going on there.

@joshmoore

Copy link
Copy Markdown
Member

Oy vey. That's a new year for you. The new failures is related to the category filter. I don't remember anything that should impact it. Just starting to look at the docs failure.

(Thanks for the benchmark example!)

@joshmoore

Copy link
Copy Markdown
Member

Ok, @d-v-b, that should fix the docs build. Let's see what's failing on this run.

@jni

jni commented Jan 10, 2022

Copy link
Copy Markdown
ContributorAuthor

Hi both, I'm unclear about whether I need to do anything here, please ping me directly if you want me to update something. 😊

@joshmoore

Copy link
Copy Markdown
Member

Sorry for dropping the ball here. Don't think there's anything needed from your side, @jni. Just very unsure what's up with this failing test. Other than that, I'd very much like to get 2.11 out ASAP.

The call to `np.any(array)` in zarr.util.all_equal triggers the
following ValueError:
```
> return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
E ValueError: invalid literal for int() with base 10: 'baz'
```
Extending the catch block allows test_array_with_categorize_filter
to pass, but it's unclear if this points to a deeper issue.
@joshmoore

Copy link
Copy Markdown
Member

Ok. Pushed a proposed fix related to @d-v-b's all_equal.

Comment threadzarr/util.py
try:
return not np.any(array)
except TypeError: # pragma: no cover
except (TypeError, ValueError): # pragma: no cover

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What causes the ValueError? Is there a particular array type or value that np.any is raising on?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It occurs when a category value (baz) gets passed to int().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

from failing build
______________________ test_array_with_categorize_filter _______________________
def test_array_with_categorize_filter():
# setup
data = np.random.choice(['foo', 'bar', 'baz'], size=100)
flt = Categorize(dtype=data.dtype, labels=['foo', 'bar', 'baz'])
filters = [flt]
for compressor in compressors:
> a = array(data, chunks=5, compressor=compressor, filters=filters)
zarr/tests/test_filters.py:172: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ zarr/creation.py:366: in array
z[...] = data
zarr/core.py:1285: in __setitem__
self.set_basic_selection(pure_selection, value, fields=fields)
zarr/core.py:1380: in set_basic_selection
return self._set_basic_selection_nd(selection, value, fields=fields)
zarr/core.py:1680: in _set_basic_selection_nd
self._set_selection(indexer, value, fields=fields)
zarr/core.py:1732: in _set_selection
self._chunk_setitem(chunk_coords, chunk_selection, chunk_value, fields=fields)
zarr/core.py:1994: in _chunk_setitem
self._chunk_setitem_nosync(chunk_coords, chunk_selection, value,
zarr/core.py:2002: in _chunk_setitem_nosync
if (not self.write_empty_chunks) and all_equal(self.fill_value, cdata):
zarr/util.py:672: in all_equal
return not np.any(array)
<__array_function__ internals>:180: in any
???
/usr/share/miniconda/envs/minimal/lib/python3.10/site-packages/numpy/core/fromnumeric.py:2395: in any
return _wrapreduction(a, np.logical_or, 'any', axis, None, out,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ obj = array(['baz', 'baz', 'baz', 'bar', 'foo'], dtype='<U3')
ufunc = <ufunc 'logical_or'>, method = 'any', axis = None, dtype = None
out = None, kwargs = {'keepdims': <no value>, 'where': <no value>}
passkwargs = {}
def _wrapreduction(obj, ufunc, method, axis, dtype, out, **kwargs):
passkwargs = {k: v for k, v in kwargs.items()
if v is not np._NoValue}
if type(obj) is not mu.ndarray:
try:
reduction = getattr(obj, method)
except AttributeError:
pass
else:
# This branch is needed for reductions like any which don't
# support a dtype.
if dtype is not None:
return reduction(axis=axis, dtype=dtype, out=out, **passkwargs)
else:
return reduction(axis=axis, out=out, **passkwargs)
> return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
E ValueError: invalid literal for int() with base 10: 'baz'
/usr/share/miniconda/envs/minimal/lib/python3.10/site-packages/numpy/core/fromnumeric.py:86: ValueError

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Interesting. Looks like the error message and type changed from NumPy 1.21 to 1.22. Don't think that was intentional, but could be wrong. Raised issue ( numpy/numpy#20898 )

In any event this workaround seems reasonable in the interim

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great, thanks! Once this is green, I'll move forward with 2.11.0.

@jakirkham

Copy link
Copy Markdown
Member

Hi both, I'm unclear about whether I need to do anything here, please ping me directly if you want me to update something. 😊

If we can convince both you, @jni, and @d-v-b to write a few lines for the blogpost ( #901 ), that would be fantastic! 😃

Can just post in that issue if you like

@joshmoore

Copy link
Copy Markdown
Member

If we can convince both you, @jni, and @d-v-b to write a few lines for the blogpost ( #901 )

e.g. was just mentioning to @MSanKeys963 how cool the use case from https://forum.image.sc/t/data-store-and-library-backend-for-napari-plugin/61779 is ;)

(....still waiting on actions...)

@joshmoore

Copy link
Copy Markdown
Member

Trying to reopen to get tests to complete.

@joshmoorejoshmoore reopened this Jan 26, 2022
@joshmoore

Copy link
Copy Markdown
Member

I've not been able to get the 3.8 and 3.9 tests to run on this PR yet. (3-4 retries)

@jakirkham

Copy link
Copy Markdown
Member

Looks like some of the past commits in this PR (though not all) had the same issue

@joshmoore

Copy link
Copy Markdown
Member

Oddly py 3.7 is still spinning here compared to #951

@joshmoore

Copy link
Copy Markdown
Member

Power cycling for CI.

@joshmoorejoshmoore reopened this Feb 4, 2022
@joshmoore

Copy link
Copy Markdown
Member

Holy moly that was an adventure! Looks like we're good to move forward with 2.11. (🍺 🍸 or similar all around this weekend)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@jni@d-v-b@jakirkham@rabernat@joshmoore@shoyer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Set write_empty_chunks to default to False by jni · Pull Request #853 · zarr-developers/zarr-python · GitHub
Skip to content

Set write_empty_chunks to default to False - #853

Merged
joshmoore merged 14 commits into
zarr-developers:masterfrom
jni:default-write-empty-chunks-false
Feb 4, 2022
Merged

Set write_empty_chunks to default to False#853
joshmoore merged 14 commits into
zarr-developers:masterfrom
jni:default-write-empty-chunks-false

Conversation

@jni

@jnijni commented Oct 20, 2021

Copy link
Copy Markdown
Contributor

In #738, despite general agreement that most people expect write_empty_chunks to default to False, @d-v-b decided to go the safe, uncontroversial route to get the PR merged, and made the default value True. All this PR does is set the default to False.

Convenient links to discussion suggesting write_empty_chunks=False should be the default:

  • @meggartsays: "In Zarr.jl, not writing empty chunks is already the default [...] so I would be in favor of changing the default behavior in Python as well.
  • @shoyersays: "it is not clear to me why you would not want to do this. I think this could be the new default behavior."
  • @martindurantsays: "I am also surprised that this is not the default, and would agree that I can't see people wanting to use the current behaviour ever."

There were performance concerns with FSSpec bulk delete items, but it seems that they were addressed upstream.

TODO:

  • Add unit tests and/or doctests in docstrings
  • Add docstrings and API docs for any new/modified user-facing classes and functions
  • New/modified features documented in docs/tutorial.rst
  • Changes documented in docs/release.rst
  • GitHub Actions have all passed
  • Test coverage is 100% (Codecov passes)

@d-v-b

Copy link
Copy Markdown
Contributor

Sounds great to me :) Thanks for doing what I didn't have the courage to do.

@jni
jniforce-pushed the default-write-empty-chunks-false branch from d83df68 to f3280a7CompareOctober 21, 2021 00:22
@jakirkham

Copy link
Copy Markdown
Member

@joshmoore wdyt?

@rabernat

Copy link
Copy Markdown
Contributor

👍 from me. I think this is a much better default.

@jakirkham

Copy link
Copy Markdown
Member

There are some lines scattered throughout the tests like these

write_empty_chunks=kwargs.pop('write_empty_chunks', True)

write_empty_chunks=kwargs.pop('write_empty_chunks', True)

Do we want to update those as well?

@jni

jni commented Nov 30, 2021

Copy link
Copy Markdown
ContributorAuthor

Do we want to update those as well?

🤷 All this means is that more tests will run with the default True. This might be useful for historical reasons. Both True and False are explicitly tested so I don't think we need to worry.

@jakirkhamjakirkham mentioned this pull request Dec 1, 2021
@jni
jniforce-pushed the default-write-empty-chunks-false branch from 3e7dbea to 6b461a7CompareDecember 2, 2021 01:48
@jni

jni commented Dec 2, 2021

Copy link
Copy Markdown
ContributorAuthor

This is now rebased on the latest master.

@joshmoore

Copy link
Copy Markdown
Member

Per #901, @d-v-b was working on a blurb for blog/release notes/docs so that users are maximally informed of the change.

@joshmoore

Copy link
Copy Markdown
Member

Capturing initial results from @d-v-b here: "users with "dense" data will see a ~15% increase in write times"

@d-v-b

d-v-b commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

N.B. that result doesn't include compression time. I will provide improved benchmarking code later today with a more realistic workflow.

@d-v-b

d-v-b commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

some interesting benchmarking results. see this repo for code: https://github.com/d-v-b/zarr-benchmarks

Each test writes a 64 MB chunk 128 times with write_empty_chunks drawn from {True, False} and the write value drawn from {array of 0s, array of random ints} , i.e. 4 different conditions. The plots show histrograms with an X axis that is the write duration normalized to the median of the time required under the (write_empty_chunks=False, write_value=0) condition.

First, with no compressor:
image

Next, with Blosc compression:
image

And last, with GZip compression:
image

Takeaways:

  • The overhead associated with the emptiness checking is exposed with Blosc compression and no compressor.
  • With blosc, writing empty chunks is actually faster than an emptiness check -> no write.
  • With GZip, handling empty chunks is much faster with write_empty_chunks=False, and the overhead associated with emptiness checking is not evident when writing "dense" chunks.

Happy to hear suggestions for changing the benchmark / plotting. E.g., maybe we want to show absolute duration instead of relative durations.

@shoyer

Copy link
Copy Markdown
Contributor

What sort of local disk are you using? I can imagine very different results for SSD vs HDD vs cloud object stores. Extra copies are never desirable, but in most cases I would guess the overhead of checking is acceptable.

@d-v-b

d-v-b commented Dec 8, 2021

Copy link
Copy Markdown
Contributor

@shoyer this is on an SSD, so pretty ideal in terms of storage latency.

@d-v-b

d-v-b commented Dec 14, 2021

Copy link
Copy Markdown
Contributor

@joshmoore I added content to the tutorial docs, let me know if you want more or an expanded release note.

I'm not sure the benchmarks are that illuminating or surprising, so maybe we don't really need to involve them?

@joshmoore

Copy link
Copy Markdown
Member

The location looks good. Could I suggest we try to get two or four simple writes with the different flags and inputs. Something like:

data = np.random.randint(0, 255, size=shape, dtype=DTYPE)
arr = zarr.open(zarr.NestedDirectoryStore(store), write_empty_chunks=write_empty_chunks, ...)
arr[:] = 255

from https://github.com/d-v-b/zarr-benchmarks/blob/main/src/zarr-benchmarks/empty_chunks.py with some form of marker ("15% slower") for someone to see at a glance.

I'm not sure the benchmarks are that illuminating or surprising, so maybe we don't really need to involve them?

Agreed. Happy to have them in your repo. I find them pretty fascinating. ;)

@jakirkham

Copy link
Copy Markdown
Member

@d-v-b, did you have a chance to try the other flags Josh mentioned above? 🙂

@d-v-b

d-v-b commented Jan 5, 2022

Copy link
Copy Markdown
Contributor

I added a short benchmark example, but I can't build the docs for some reason (tox -e docs yields some inscrutable error from a different part of the codebase)

@d-v-b

d-v-b commented Jan 5, 2022

Copy link
Copy Markdown
Contributor

Oh and there's a new test failure in an unrelated part of the codebase. Not sure what's going on there.

@joshmoore

Copy link
Copy Markdown
Member

Oy vey. That's a new year for you. The new failures is related to the category filter. I don't remember anything that should impact it. Just starting to look at the docs failure.

(Thanks for the benchmark example!)

@joshmoore

Copy link
Copy Markdown
Member

Ok, @d-v-b, that should fix the docs build. Let's see what's failing on this run.

@jni

jni commented Jan 10, 2022

Copy link
Copy Markdown
ContributorAuthor

Hi both, I'm unclear about whether I need to do anything here, please ping me directly if you want me to update something. 😊

@joshmoore

Copy link
Copy Markdown
Member

Sorry for dropping the ball here. Don't think there's anything needed from your side, @jni. Just very unsure what's up with this failing test. Other than that, I'd very much like to get 2.11 out ASAP.

The call to `np.any(array)` in zarr.util.all_equal triggers the
following ValueError:
```
> return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
E ValueError: invalid literal for int() with base 10: 'baz'
```
Extending the catch block allows test_array_with_categorize_filter
to pass, but it's unclear if this points to a deeper issue.
@joshmoore

Copy link
Copy Markdown
Member

Ok. Pushed a proposed fix related to @d-v-b's all_equal.

Comment threadzarr/util.py
try:
return not np.any(array)
except TypeError: # pragma: no cover
except (TypeError, ValueError): # pragma: no cover

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What causes the ValueError? Is there a particular array type or value that np.any is raising on?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It occurs when a category value (baz) gets passed to int().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

from failing build
______________________ test_array_with_categorize_filter _______________________
def test_array_with_categorize_filter():
# setup
data = np.random.choice(['foo', 'bar', 'baz'], size=100)
flt = Categorize(dtype=data.dtype, labels=['foo', 'bar', 'baz'])
filters = [flt]
for compressor in compressors:
> a = array(data, chunks=5, compressor=compressor, filters=filters)
zarr/tests/test_filters.py:172: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ zarr/creation.py:366: in array
z[...] = data
zarr/core.py:1285: in __setitem__
self.set_basic_selection(pure_selection, value, fields=fields)
zarr/core.py:1380: in set_basic_selection
return self._set_basic_selection_nd(selection, value, fields=fields)
zarr/core.py:1680: in _set_basic_selection_nd
self._set_selection(indexer, value, fields=fields)
zarr/core.py:1732: in _set_selection
self._chunk_setitem(chunk_coords, chunk_selection, chunk_value, fields=fields)
zarr/core.py:1994: in _chunk_setitem
self._chunk_setitem_nosync(chunk_coords, chunk_selection, value,
zarr/core.py:2002: in _chunk_setitem_nosync
if (not self.write_empty_chunks) and all_equal(self.fill_value, cdata):
zarr/util.py:672: in all_equal
return not np.any(array)
<__array_function__ internals>:180: in any
???
/usr/share/miniconda/envs/minimal/lib/python3.10/site-packages/numpy/core/fromnumeric.py:2395: in any
return _wrapreduction(a, np.logical_or, 'any', axis, None, out,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ obj = array(['baz', 'baz', 'baz', 'bar', 'foo'], dtype='<U3')
ufunc = <ufunc 'logical_or'>, method = 'any', axis = None, dtype = None
out = None, kwargs = {'keepdims': <no value>, 'where': <no value>}
passkwargs = {}
def _wrapreduction(obj, ufunc, method, axis, dtype, out, **kwargs):
passkwargs = {k: v for k, v in kwargs.items()
if v is not np._NoValue}
if type(obj) is not mu.ndarray:
try:
reduction = getattr(obj, method)
except AttributeError:
pass
else:
# This branch is needed for reductions like any which don't
# support a dtype.
if dtype is not None:
return reduction(axis=axis, dtype=dtype, out=out, **passkwargs)
else:
return reduction(axis=axis, out=out, **passkwargs)
> return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
E ValueError: invalid literal for int() with base 10: 'baz'
/usr/share/miniconda/envs/minimal/lib/python3.10/site-packages/numpy/core/fromnumeric.py:86: ValueError

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Interesting. Looks like the error message and type changed from NumPy 1.21 to 1.22. Don't think that was intentional, but could be wrong. Raised issue ( numpy/numpy#20898 )

In any event this workaround seems reasonable in the interim

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great, thanks! Once this is green, I'll move forward with 2.11.0.

@jakirkham

Copy link
Copy Markdown
Member

Hi both, I'm unclear about whether I need to do anything here, please ping me directly if you want me to update something. 😊

If we can convince both you, @jni, and @d-v-b to write a few lines for the blogpost ( #901 ), that would be fantastic! 😃

Can just post in that issue if you like

@joshmoore

Copy link
Copy Markdown
Member

If we can convince both you, @jni, and @d-v-b to write a few lines for the blogpost ( #901 )

e.g. was just mentioning to @MSanKeys963 how cool the use case from https://forum.image.sc/t/data-store-and-library-backend-for-napari-plugin/61779 is ;)

(....still waiting on actions...)

@joshmoore

Copy link
Copy Markdown
Member

Trying to reopen to get tests to complete.

@joshmoorejoshmoore reopened this Jan 26, 2022
@joshmoore

Copy link
Copy Markdown
Member

I've not been able to get the 3.8 and 3.9 tests to run on this PR yet. (3-4 retries)

@jakirkham

Copy link
Copy Markdown
Member

Looks like some of the past commits in this PR (though not all) had the same issue

@joshmoore

Copy link
Copy Markdown
Member

Oddly py 3.7 is still spinning here compared to #951

@joshmoore

Copy link
Copy Markdown
Member

Power cycling for CI.

@joshmoorejoshmoore reopened this Feb 4, 2022
@joshmoore

Copy link
Copy Markdown
Member

Holy moly that was an adventure! Looks like we're good to move forward with 2.11. (🍺 🍸 or similar all around this weekend)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@jni@d-v-b@jakirkham@rabernat@joshmoore@shoyer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Set write_empty_chunks to default to False by jni · Pull Request #853 · zarr-developers/zarr-python · GitHub
Skip to content

Set write_empty_chunks to default to False - #853

Merged
joshmoore merged 14 commits into
zarr-developers:masterfrom
jni:default-write-empty-chunks-false
Feb 4, 2022
Merged

Set write_empty_chunks to default to False#853
joshmoore merged 14 commits into
zarr-developers:masterfrom
jni:default-write-empty-chunks-false

Conversation

@jni

@jnijni commented Oct 20, 2021

Copy link
Copy Markdown
Contributor

In #738, despite general agreement that most people expect write_empty_chunks to default to False, @d-v-b decided to go the safe, uncontroversial route to get the PR merged, and made the default value True. All this PR does is set the default to False.

Convenient links to discussion suggesting write_empty_chunks=False should be the default:

  • @meggartsays: "In Zarr.jl, not writing empty chunks is already the default [...] so I would be in favor of changing the default behavior in Python as well.
  • @shoyersays: "it is not clear to me why you would not want to do this. I think this could be the new default behavior."
  • @martindurantsays: "I am also surprised that this is not the default, and would agree that I can't see people wanting to use the current behaviour ever."

There were performance concerns with FSSpec bulk delete items, but it seems that they were addressed upstream.

TODO:

  • Add unit tests and/or doctests in docstrings
  • Add docstrings and API docs for any new/modified user-facing classes and functions
  • New/modified features documented in docs/tutorial.rst
  • Changes documented in docs/release.rst
  • GitHub Actions have all passed
  • Test coverage is 100% (Codecov passes)

@d-v-b

Copy link
Copy Markdown
Contributor

Sounds great to me :) Thanks for doing what I didn't have the courage to do.

@jni
jniforce-pushed the default-write-empty-chunks-false branch from d83df68 to f3280a7CompareOctober 21, 2021 00:22
@jakirkham

Copy link
Copy Markdown
Member

@joshmoore wdyt?

@rabernat

Copy link
Copy Markdown
Contributor

👍 from me. I think this is a much better default.

@jakirkham

Copy link
Copy Markdown
Member

There are some lines scattered throughout the tests like these

write_empty_chunks=kwargs.pop('write_empty_chunks', True)

write_empty_chunks=kwargs.pop('write_empty_chunks', True)

Do we want to update those as well?

@jni

jni commented Nov 30, 2021

Copy link
Copy Markdown
ContributorAuthor

Do we want to update those as well?

🤷 All this means is that more tests will run with the default True. This might be useful for historical reasons. Both True and False are explicitly tested so I don't think we need to worry.

@jakirkhamjakirkham mentioned this pull request Dec 1, 2021
@jni
jniforce-pushed the default-write-empty-chunks-false branch from 3e7dbea to 6b461a7CompareDecember 2, 2021 01:48
@jni

jni commented Dec 2, 2021

Copy link
Copy Markdown
ContributorAuthor

This is now rebased on the latest master.

@joshmoore

Copy link
Copy Markdown
Member

Per #901, @d-v-b was working on a blurb for blog/release notes/docs so that users are maximally informed of the change.

@joshmoore

Copy link
Copy Markdown
Member

Capturing initial results from @d-v-b here: "users with "dense" data will see a ~15% increase in write times"

@d-v-b

d-v-b commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

N.B. that result doesn't include compression time. I will provide improved benchmarking code later today with a more realistic workflow.

@d-v-b

d-v-b commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

some interesting benchmarking results. see this repo for code: https://github.com/d-v-b/zarr-benchmarks

Each test writes a 64 MB chunk 128 times with write_empty_chunks drawn from {True, False} and the write value drawn from {array of 0s, array of random ints} , i.e. 4 different conditions. The plots show histrograms with an X axis that is the write duration normalized to the median of the time required under the (write_empty_chunks=False, write_value=0) condition.

First, with no compressor:
image

Next, with Blosc compression:
image

And last, with GZip compression:
image

Takeaways:

  • The overhead associated with the emptiness checking is exposed with Blosc compression and no compressor.
  • With blosc, writing empty chunks is actually faster than an emptiness check -> no write.
  • With GZip, handling empty chunks is much faster with write_empty_chunks=False, and the overhead associated with emptiness checking is not evident when writing "dense" chunks.

Happy to hear suggestions for changing the benchmark / plotting. E.g., maybe we want to show absolute duration instead of relative durations.

@shoyer

Copy link
Copy Markdown
Contributor

What sort of local disk are you using? I can imagine very different results for SSD vs HDD vs cloud object stores. Extra copies are never desirable, but in most cases I would guess the overhead of checking is acceptable.

@d-v-b

d-v-b commented Dec 8, 2021

Copy link
Copy Markdown
Contributor

@shoyer this is on an SSD, so pretty ideal in terms of storage latency.

@d-v-b

d-v-b commented Dec 14, 2021

Copy link
Copy Markdown
Contributor

@joshmoore I added content to the tutorial docs, let me know if you want more or an expanded release note.

I'm not sure the benchmarks are that illuminating or surprising, so maybe we don't really need to involve them?

@joshmoore

Copy link
Copy Markdown
Member

The location looks good. Could I suggest we try to get two or four simple writes with the different flags and inputs. Something like:

data = np.random.randint(0, 255, size=shape, dtype=DTYPE)
arr = zarr.open(zarr.NestedDirectoryStore(store), write_empty_chunks=write_empty_chunks, ...)
arr[:] = 255

from https://github.com/d-v-b/zarr-benchmarks/blob/main/src/zarr-benchmarks/empty_chunks.py with some form of marker ("15% slower") for someone to see at a glance.

I'm not sure the benchmarks are that illuminating or surprising, so maybe we don't really need to involve them?

Agreed. Happy to have them in your repo. I find them pretty fascinating. ;)

@jakirkham

Copy link
Copy Markdown
Member

@d-v-b, did you have a chance to try the other flags Josh mentioned above? 🙂

@d-v-b

d-v-b commented Jan 5, 2022

Copy link
Copy Markdown
Contributor

I added a short benchmark example, but I can't build the docs for some reason (tox -e docs yields some inscrutable error from a different part of the codebase)

@d-v-b

d-v-b commented Jan 5, 2022

Copy link
Copy Markdown
Contributor

Oh and there's a new test failure in an unrelated part of the codebase. Not sure what's going on there.

@joshmoore

Copy link
Copy Markdown
Member

Oy vey. That's a new year for you. The new failures is related to the category filter. I don't remember anything that should impact it. Just starting to look at the docs failure.

(Thanks for the benchmark example!)

@joshmoore

Copy link
Copy Markdown
Member

Ok, @d-v-b, that should fix the docs build. Let's see what's failing on this run.

@jni

jni commented Jan 10, 2022

Copy link
Copy Markdown
ContributorAuthor

Hi both, I'm unclear about whether I need to do anything here, please ping me directly if you want me to update something. 😊

@joshmoore

Copy link
Copy Markdown
Member

Sorry for dropping the ball here. Don't think there's anything needed from your side, @jni. Just very unsure what's up with this failing test. Other than that, I'd very much like to get 2.11 out ASAP.

The call to `np.any(array)` in zarr.util.all_equal triggers the
following ValueError:
```
> return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
E ValueError: invalid literal for int() with base 10: 'baz'
```
Extending the catch block allows test_array_with_categorize_filter
to pass, but it's unclear if this points to a deeper issue.
@joshmoore

Copy link
Copy Markdown
Member

Ok. Pushed a proposed fix related to @d-v-b's all_equal.

Comment threadzarr/util.py
try:
return not np.any(array)
except TypeError: # pragma: no cover
except (TypeError, ValueError): # pragma: no cover

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What causes the ValueError? Is there a particular array type or value that np.any is raising on?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It occurs when a category value (baz) gets passed to int().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

from failing build
______________________ test_array_with_categorize_filter _______________________
def test_array_with_categorize_filter():
# setup
data = np.random.choice(['foo', 'bar', 'baz'], size=100)
flt = Categorize(dtype=data.dtype, labels=['foo', 'bar', 'baz'])
filters = [flt]
for compressor in compressors:
> a = array(data, chunks=5, compressor=compressor, filters=filters)
zarr/tests/test_filters.py:172: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ zarr/creation.py:366: in array
z[...] = data
zarr/core.py:1285: in __setitem__
self.set_basic_selection(pure_selection, value, fields=fields)
zarr/core.py:1380: in set_basic_selection
return self._set_basic_selection_nd(selection, value, fields=fields)
zarr/core.py:1680: in _set_basic_selection_nd
self._set_selection(indexer, value, fields=fields)
zarr/core.py:1732: in _set_selection
self._chunk_setitem(chunk_coords, chunk_selection, chunk_value, fields=fields)
zarr/core.py:1994: in _chunk_setitem
self._chunk_setitem_nosync(chunk_coords, chunk_selection, value,
zarr/core.py:2002: in _chunk_setitem_nosync
if (not self.write_empty_chunks) and all_equal(self.fill_value, cdata):
zarr/util.py:672: in all_equal
return not np.any(array)
<__array_function__ internals>:180: in any
???
/usr/share/miniconda/envs/minimal/lib/python3.10/site-packages/numpy/core/fromnumeric.py:2395: in any
return _wrapreduction(a, np.logical_or, 'any', axis, None, out,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ obj = array(['baz', 'baz', 'baz', 'bar', 'foo'], dtype='<U3')
ufunc = <ufunc 'logical_or'>, method = 'any', axis = None, dtype = None
out = None, kwargs = {'keepdims': <no value>, 'where': <no value>}
passkwargs = {}
def _wrapreduction(obj, ufunc, method, axis, dtype, out, **kwargs):
passkwargs = {k: v for k, v in kwargs.items()
if v is not np._NoValue}
if type(obj) is not mu.ndarray:
try:
reduction = getattr(obj, method)
except AttributeError:
pass
else:
# This branch is needed for reductions like any which don't
# support a dtype.
if dtype is not None:
return reduction(axis=axis, dtype=dtype, out=out, **passkwargs)
else:
return reduction(axis=axis, out=out, **passkwargs)
> return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
E ValueError: invalid literal for int() with base 10: 'baz'
/usr/share/miniconda/envs/minimal/lib/python3.10/site-packages/numpy/core/fromnumeric.py:86: ValueError

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Interesting. Looks like the error message and type changed from NumPy 1.21 to 1.22. Don't think that was intentional, but could be wrong. Raised issue ( numpy/numpy#20898 )

In any event this workaround seems reasonable in the interim

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great, thanks! Once this is green, I'll move forward with 2.11.0.

@jakirkham

Copy link
Copy Markdown
Member

Hi both, I'm unclear about whether I need to do anything here, please ping me directly if you want me to update something. 😊

If we can convince both you, @jni, and @d-v-b to write a few lines for the blogpost ( #901 ), that would be fantastic! 😃

Can just post in that issue if you like

@joshmoore

Copy link
Copy Markdown
Member

If we can convince both you, @jni, and @d-v-b to write a few lines for the blogpost ( #901 )

e.g. was just mentioning to @MSanKeys963 how cool the use case from https://forum.image.sc/t/data-store-and-library-backend-for-napari-plugin/61779 is ;)

(....still waiting on actions...)

@joshmoore

Copy link
Copy Markdown
Member

Trying to reopen to get tests to complete.

@joshmoorejoshmoore reopened this Jan 26, 2022
@joshmoore

Copy link
Copy Markdown
Member

I've not been able to get the 3.8 and 3.9 tests to run on this PR yet. (3-4 retries)

@jakirkham

Copy link
Copy Markdown
Member

Looks like some of the past commits in this PR (though not all) had the same issue

@joshmoore

Copy link
Copy Markdown
Member

Oddly py 3.7 is still spinning here compared to #951

@joshmoore

Copy link
Copy Markdown
Member

Power cycling for CI.

@joshmoorejoshmoore reopened this Feb 4, 2022
@joshmoore

Copy link
Copy Markdown
Member

Holy moly that was an adventure! Looks like we're good to move forward with 2.11. (🍺 🍸 or similar all around this weekend)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@jni@d-v-b@jakirkham@rabernat@joshmoore@shoyer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Set write_empty_chunks to default to False by jni · Pull Request #853 · zarr-developers/zarr-python · GitHub
Skip to content

Set write_empty_chunks to default to False - #853

Merged
joshmoore merged 14 commits into
zarr-developers:masterfrom
jni:default-write-empty-chunks-false
Feb 4, 2022
Merged

Set write_empty_chunks to default to False#853
joshmoore merged 14 commits into
zarr-developers:masterfrom
jni:default-write-empty-chunks-false

Conversation

@jni

@jnijni commented Oct 20, 2021

Copy link
Copy Markdown
Contributor

In #738, despite general agreement that most people expect write_empty_chunks to default to False, @d-v-b decided to go the safe, uncontroversial route to get the PR merged, and made the default value True. All this PR does is set the default to False.

Convenient links to discussion suggesting write_empty_chunks=False should be the default:

  • @meggartsays: "In Zarr.jl, not writing empty chunks is already the default [...] so I would be in favor of changing the default behavior in Python as well.
  • @shoyersays: "it is not clear to me why you would not want to do this. I think this could be the new default behavior."
  • @martindurantsays: "I am also surprised that this is not the default, and would agree that I can't see people wanting to use the current behaviour ever."

There were performance concerns with FSSpec bulk delete items, but it seems that they were addressed upstream.

TODO:

  • Add unit tests and/or doctests in docstrings
  • Add docstrings and API docs for any new/modified user-facing classes and functions
  • New/modified features documented in docs/tutorial.rst
  • Changes documented in docs/release.rst
  • GitHub Actions have all passed
  • Test coverage is 100% (Codecov passes)

@d-v-b

Copy link
Copy Markdown
Contributor

Sounds great to me :) Thanks for doing what I didn't have the courage to do.

@jni
jniforce-pushed the default-write-empty-chunks-false branch from d83df68 to f3280a7CompareOctober 21, 2021 00:22
@jakirkham

Copy link
Copy Markdown
Member

@joshmoore wdyt?

@rabernat

Copy link
Copy Markdown
Contributor

👍 from me. I think this is a much better default.

@jakirkham

Copy link
Copy Markdown
Member

There are some lines scattered throughout the tests like these

write_empty_chunks=kwargs.pop('write_empty_chunks', True)

write_empty_chunks=kwargs.pop('write_empty_chunks', True)

Do we want to update those as well?

@jni

jni commented Nov 30, 2021

Copy link
Copy Markdown
ContributorAuthor

Do we want to update those as well?

🤷 All this means is that more tests will run with the default True. This might be useful for historical reasons. Both True and False are explicitly tested so I don't think we need to worry.

@jakirkhamjakirkham mentioned this pull request Dec 1, 2021
@jni
jniforce-pushed the default-write-empty-chunks-false branch from 3e7dbea to 6b461a7CompareDecember 2, 2021 01:48
@jni

jni commented Dec 2, 2021

Copy link
Copy Markdown
ContributorAuthor

This is now rebased on the latest master.

@joshmoore

Copy link
Copy Markdown
Member

Per #901, @d-v-b was working on a blurb for blog/release notes/docs so that users are maximally informed of the change.

@joshmoore

Copy link
Copy Markdown
Member

Capturing initial results from @d-v-b here: "users with "dense" data will see a ~15% increase in write times"

@d-v-b

d-v-b commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

N.B. that result doesn't include compression time. I will provide improved benchmarking code later today with a more realistic workflow.

@d-v-b

d-v-b commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

some interesting benchmarking results. see this repo for code: https://github.com/d-v-b/zarr-benchmarks

Each test writes a 64 MB chunk 128 times with write_empty_chunks drawn from {True, False} and the write value drawn from {array of 0s, array of random ints} , i.e. 4 different conditions. The plots show histrograms with an X axis that is the write duration normalized to the median of the time required under the (write_empty_chunks=False, write_value=0) condition.

First, with no compressor:
image

Next, with Blosc compression:
image

And last, with GZip compression:
image

Takeaways:

  • The overhead associated with the emptiness checking is exposed with Blosc compression and no compressor.
  • With blosc, writing empty chunks is actually faster than an emptiness check -> no write.
  • With GZip, handling empty chunks is much faster with write_empty_chunks=False, and the overhead associated with emptiness checking is not evident when writing "dense" chunks.

Happy to hear suggestions for changing the benchmark / plotting. E.g., maybe we want to show absolute duration instead of relative durations.

@shoyer

Copy link
Copy Markdown
Contributor

What sort of local disk are you using? I can imagine very different results for SSD vs HDD vs cloud object stores. Extra copies are never desirable, but in most cases I would guess the overhead of checking is acceptable.

@d-v-b

d-v-b commented Dec 8, 2021

Copy link
Copy Markdown
Contributor

@shoyer this is on an SSD, so pretty ideal in terms of storage latency.

@d-v-b

d-v-b commented Dec 14, 2021

Copy link
Copy Markdown
Contributor

@joshmoore I added content to the tutorial docs, let me know if you want more or an expanded release note.

I'm not sure the benchmarks are that illuminating or surprising, so maybe we don't really need to involve them?

@joshmoore

Copy link
Copy Markdown
Member

The location looks good. Could I suggest we try to get two or four simple writes with the different flags and inputs. Something like:

data = np.random.randint(0, 255, size=shape, dtype=DTYPE)
arr = zarr.open(zarr.NestedDirectoryStore(store), write_empty_chunks=write_empty_chunks, ...)
arr[:] = 255

from https://github.com/d-v-b/zarr-benchmarks/blob/main/src/zarr-benchmarks/empty_chunks.py with some form of marker ("15% slower") for someone to see at a glance.

I'm not sure the benchmarks are that illuminating or surprising, so maybe we don't really need to involve them?

Agreed. Happy to have them in your repo. I find them pretty fascinating. ;)

@jakirkham

Copy link
Copy Markdown
Member

@d-v-b, did you have a chance to try the other flags Josh mentioned above? 🙂

@d-v-b

d-v-b commented Jan 5, 2022

Copy link
Copy Markdown
Contributor

I added a short benchmark example, but I can't build the docs for some reason (tox -e docs yields some inscrutable error from a different part of the codebase)

@d-v-b

d-v-b commented Jan 5, 2022

Copy link
Copy Markdown
Contributor

Oh and there's a new test failure in an unrelated part of the codebase. Not sure what's going on there.

@joshmoore

Copy link
Copy Markdown
Member

Oy vey. That's a new year for you. The new failures is related to the category filter. I don't remember anything that should impact it. Just starting to look at the docs failure.

(Thanks for the benchmark example!)

@joshmoore

Copy link
Copy Markdown
Member

Ok, @d-v-b, that should fix the docs build. Let's see what's failing on this run.

@jni

jni commented Jan 10, 2022

Copy link
Copy Markdown
ContributorAuthor

Hi both, I'm unclear about whether I need to do anything here, please ping me directly if you want me to update something. 😊

@joshmoore

Copy link
Copy Markdown
Member

Sorry for dropping the ball here. Don't think there's anything needed from your side, @jni. Just very unsure what's up with this failing test. Other than that, I'd very much like to get 2.11 out ASAP.

The call to `np.any(array)` in zarr.util.all_equal triggers the
following ValueError:
```
> return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
E ValueError: invalid literal for int() with base 10: 'baz'
```
Extending the catch block allows test_array_with_categorize_filter
to pass, but it's unclear if this points to a deeper issue.
@joshmoore

Copy link
Copy Markdown
Member

Ok. Pushed a proposed fix related to @d-v-b's all_equal.

Comment threadzarr/util.py
try:
return not np.any(array)
except TypeError: # pragma: no cover
except (TypeError, ValueError): # pragma: no cover

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What causes the ValueError? Is there a particular array type or value that np.any is raising on?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It occurs when a category value (baz) gets passed to int().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

from failing build
______________________ test_array_with_categorize_filter _______________________
def test_array_with_categorize_filter():
# setup
data = np.random.choice(['foo', 'bar', 'baz'], size=100)
flt = Categorize(dtype=data.dtype, labels=['foo', 'bar', 'baz'])
filters = [flt]
for compressor in compressors:
> a = array(data, chunks=5, compressor=compressor, filters=filters)
zarr/tests/test_filters.py:172: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ zarr/creation.py:366: in array
z[...] = data
zarr/core.py:1285: in __setitem__
self.set_basic_selection(pure_selection, value, fields=fields)
zarr/core.py:1380: in set_basic_selection
return self._set_basic_selection_nd(selection, value, fields=fields)
zarr/core.py:1680: in _set_basic_selection_nd
self._set_selection(indexer, value, fields=fields)
zarr/core.py:1732: in _set_selection
self._chunk_setitem(chunk_coords, chunk_selection, chunk_value, fields=fields)
zarr/core.py:1994: in _chunk_setitem
self._chunk_setitem_nosync(chunk_coords, chunk_selection, value,
zarr/core.py:2002: in _chunk_setitem_nosync
if (not self.write_empty_chunks) and all_equal(self.fill_value, cdata):
zarr/util.py:672: in all_equal
return not np.any(array)
<__array_function__ internals>:180: in any
???
/usr/share/miniconda/envs/minimal/lib/python3.10/site-packages/numpy/core/fromnumeric.py:2395: in any
return _wrapreduction(a, np.logical_or, 'any', axis, None, out,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ obj = array(['baz', 'baz', 'baz', 'bar', 'foo'], dtype='<U3')
ufunc = <ufunc 'logical_or'>, method = 'any', axis = None, dtype = None
out = None, kwargs = {'keepdims': <no value>, 'where': <no value>}
passkwargs = {}
def _wrapreduction(obj, ufunc, method, axis, dtype, out, **kwargs):
passkwargs = {k: v for k, v in kwargs.items()
if v is not np._NoValue}
if type(obj) is not mu.ndarray:
try:
reduction = getattr(obj, method)
except AttributeError:
pass
else:
# This branch is needed for reductions like any which don't
# support a dtype.
if dtype is not None:
return reduction(axis=axis, dtype=dtype, out=out, **passkwargs)
else:
return reduction(axis=axis, out=out, **passkwargs)
> return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
E ValueError: invalid literal for int() with base 10: 'baz'
/usr/share/miniconda/envs/minimal/lib/python3.10/site-packages/numpy/core/fromnumeric.py:86: ValueError

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Interesting. Looks like the error message and type changed from NumPy 1.21 to 1.22. Don't think that was intentional, but could be wrong. Raised issue ( numpy/numpy#20898 )

In any event this workaround seems reasonable in the interim

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great, thanks! Once this is green, I'll move forward with 2.11.0.

@jakirkham

Copy link
Copy Markdown
Member

Hi both, I'm unclear about whether I need to do anything here, please ping me directly if you want me to update something. 😊

If we can convince both you, @jni, and @d-v-b to write a few lines for the blogpost ( #901 ), that would be fantastic! 😃

Can just post in that issue if you like

@joshmoore

Copy link
Copy Markdown
Member

If we can convince both you, @jni, and @d-v-b to write a few lines for the blogpost ( #901 )

e.g. was just mentioning to @MSanKeys963 how cool the use case from https://forum.image.sc/t/data-store-and-library-backend-for-napari-plugin/61779 is ;)

(....still waiting on actions...)

@joshmoore

Copy link
Copy Markdown
Member

Trying to reopen to get tests to complete.

@joshmoorejoshmoore reopened this Jan 26, 2022
@joshmoore

Copy link
Copy Markdown
Member

I've not been able to get the 3.8 and 3.9 tests to run on this PR yet. (3-4 retries)

@jakirkham

Copy link
Copy Markdown
Member

Looks like some of the past commits in this PR (though not all) had the same issue

@joshmoore

Copy link
Copy Markdown
Member

Oddly py 3.7 is still spinning here compared to #951

@joshmoore

Copy link
Copy Markdown
Member

Power cycling for CI.

@joshmoorejoshmoore reopened this Feb 4, 2022
@joshmoore

Copy link
Copy Markdown
Member

Holy moly that was an adventure! Looks like we're good to move forward with 2.11. (🍺 🍸 or similar all around this weekend)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@jni@d-v-b@jakirkham@rabernat@joshmoore@shoyer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Set write_empty_chunks to default to False by jni · Pull Request #853 · zarr-developers/zarr-python · GitHub
Skip to content

Set write_empty_chunks to default to False - #853

Merged
joshmoore merged 14 commits into
zarr-developers:masterfrom
jni:default-write-empty-chunks-false
Feb 4, 2022
Merged

Set write_empty_chunks to default to False#853
joshmoore merged 14 commits into
zarr-developers:masterfrom
jni:default-write-empty-chunks-false

Conversation

@jni

@jnijni commented Oct 20, 2021

Copy link
Copy Markdown
Contributor

In #738, despite general agreement that most people expect write_empty_chunks to default to False, @d-v-b decided to go the safe, uncontroversial route to get the PR merged, and made the default value True. All this PR does is set the default to False.

Convenient links to discussion suggesting write_empty_chunks=False should be the default:

  • @meggartsays: "In Zarr.jl, not writing empty chunks is already the default [...] so I would be in favor of changing the default behavior in Python as well.
  • @shoyersays: "it is not clear to me why you would not want to do this. I think this could be the new default behavior."
  • @martindurantsays: "I am also surprised that this is not the default, and would agree that I can't see people wanting to use the current behaviour ever."

There were performance concerns with FSSpec bulk delete items, but it seems that they were addressed upstream.

TODO:

  • Add unit tests and/or doctests in docstrings
  • Add docstrings and API docs for any new/modified user-facing classes and functions
  • New/modified features documented in docs/tutorial.rst
  • Changes documented in docs/release.rst
  • GitHub Actions have all passed
  • Test coverage is 100% (Codecov passes)

@d-v-b

Copy link
Copy Markdown
Contributor

Sounds great to me :) Thanks for doing what I didn't have the courage to do.

@jni
jniforce-pushed the default-write-empty-chunks-false branch from d83df68 to f3280a7CompareOctober 21, 2021 00:22
@jakirkham

Copy link
Copy Markdown
Member

@joshmoore wdyt?

@rabernat

Copy link
Copy Markdown
Contributor

👍 from me. I think this is a much better default.

@jakirkham

Copy link
Copy Markdown
Member

There are some lines scattered throughout the tests like these

write_empty_chunks=kwargs.pop('write_empty_chunks', True)

write_empty_chunks=kwargs.pop('write_empty_chunks', True)

Do we want to update those as well?

@jni

jni commented Nov 30, 2021

Copy link
Copy Markdown
ContributorAuthor

Do we want to update those as well?

🤷 All this means is that more tests will run with the default True. This might be useful for historical reasons. Both True and False are explicitly tested so I don't think we need to worry.

@jakirkhamjakirkham mentioned this pull request Dec 1, 2021
@jni
jniforce-pushed the default-write-empty-chunks-false branch from 3e7dbea to 6b461a7CompareDecember 2, 2021 01:48
@jni

jni commented Dec 2, 2021

Copy link
Copy Markdown
ContributorAuthor

This is now rebased on the latest master.

@joshmoore

Copy link
Copy Markdown
Member

Per #901, @d-v-b was working on a blurb for blog/release notes/docs so that users are maximally informed of the change.

@joshmoore

Copy link
Copy Markdown
Member

Capturing initial results from @d-v-b here: "users with "dense" data will see a ~15% increase in write times"

@d-v-b

d-v-b commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

N.B. that result doesn't include compression time. I will provide improved benchmarking code later today with a more realistic workflow.

@d-v-b

d-v-b commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

some interesting benchmarking results. see this repo for code: https://github.com/d-v-b/zarr-benchmarks

Each test writes a 64 MB chunk 128 times with write_empty_chunks drawn from {True, False} and the write value drawn from {array of 0s, array of random ints} , i.e. 4 different conditions. The plots show histrograms with an X axis that is the write duration normalized to the median of the time required under the (write_empty_chunks=False, write_value=0) condition.

First, with no compressor:
image

Next, with Blosc compression:
image

And last, with GZip compression:
image

Takeaways:

  • The overhead associated with the emptiness checking is exposed with Blosc compression and no compressor.
  • With blosc, writing empty chunks is actually faster than an emptiness check -> no write.
  • With GZip, handling empty chunks is much faster with write_empty_chunks=False, and the overhead associated with emptiness checking is not evident when writing "dense" chunks.

Happy to hear suggestions for changing the benchmark / plotting. E.g., maybe we want to show absolute duration instead of relative durations.

@shoyer

Copy link
Copy Markdown
Contributor

What sort of local disk are you using? I can imagine very different results for SSD vs HDD vs cloud object stores. Extra copies are never desirable, but in most cases I would guess the overhead of checking is acceptable.

@d-v-b

d-v-b commented Dec 8, 2021

Copy link
Copy Markdown
Contributor

@shoyer this is on an SSD, so pretty ideal in terms of storage latency.

@d-v-b

d-v-b commented Dec 14, 2021

Copy link
Copy Markdown
Contributor

@joshmoore I added content to the tutorial docs, let me know if you want more or an expanded release note.

I'm not sure the benchmarks are that illuminating or surprising, so maybe we don't really need to involve them?

@joshmoore

Copy link
Copy Markdown
Member

The location looks good. Could I suggest we try to get two or four simple writes with the different flags and inputs. Something like:

data = np.random.randint(0, 255, size=shape, dtype=DTYPE)
arr = zarr.open(zarr.NestedDirectoryStore(store), write_empty_chunks=write_empty_chunks, ...)
arr[:] = 255

from https://github.com/d-v-b/zarr-benchmarks/blob/main/src/zarr-benchmarks/empty_chunks.py with some form of marker ("15% slower") for someone to see at a glance.

I'm not sure the benchmarks are that illuminating or surprising, so maybe we don't really need to involve them?

Agreed. Happy to have them in your repo. I find them pretty fascinating. ;)

@jakirkham

Copy link
Copy Markdown
Member

@d-v-b, did you have a chance to try the other flags Josh mentioned above? 🙂

@d-v-b

d-v-b commented Jan 5, 2022

Copy link
Copy Markdown
Contributor

I added a short benchmark example, but I can't build the docs for some reason (tox -e docs yields some inscrutable error from a different part of the codebase)

@d-v-b

d-v-b commented Jan 5, 2022

Copy link
Copy Markdown
Contributor

Oh and there's a new test failure in an unrelated part of the codebase. Not sure what's going on there.

@joshmoore

Copy link
Copy Markdown
Member

Oy vey. That's a new year for you. The new failures is related to the category filter. I don't remember anything that should impact it. Just starting to look at the docs failure.

(Thanks for the benchmark example!)

@joshmoore

Copy link
Copy Markdown
Member

Ok, @d-v-b, that should fix the docs build. Let's see what's failing on this run.

@jni

jni commented Jan 10, 2022

Copy link
Copy Markdown
ContributorAuthor

Hi both, I'm unclear about whether I need to do anything here, please ping me directly if you want me to update something. 😊

@joshmoore

Copy link
Copy Markdown
Member

Sorry for dropping the ball here. Don't think there's anything needed from your side, @jni. Just very unsure what's up with this failing test. Other than that, I'd very much like to get 2.11 out ASAP.

The call to `np.any(array)` in zarr.util.all_equal triggers the
following ValueError:
```
> return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
E ValueError: invalid literal for int() with base 10: 'baz'
```
Extending the catch block allows test_array_with_categorize_filter
to pass, but it's unclear if this points to a deeper issue.
@joshmoore

Copy link
Copy Markdown
Member

Ok. Pushed a proposed fix related to @d-v-b's all_equal.

Comment threadzarr/util.py
try:
return not np.any(array)
except TypeError: # pragma: no cover
except (TypeError, ValueError): # pragma: no cover

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What causes the ValueError? Is there a particular array type or value that np.any is raising on?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It occurs when a category value (baz) gets passed to int().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

from failing build
______________________ test_array_with_categorize_filter _______________________
def test_array_with_categorize_filter():
# setup
data = np.random.choice(['foo', 'bar', 'baz'], size=100)
flt = Categorize(dtype=data.dtype, labels=['foo', 'bar', 'baz'])
filters = [flt]
for compressor in compressors:
> a = array(data, chunks=5, compressor=compressor, filters=filters)
zarr/tests/test_filters.py:172: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ zarr/creation.py:366: in array
z[...] = data
zarr/core.py:1285: in __setitem__
self.set_basic_selection(pure_selection, value, fields=fields)
zarr/core.py:1380: in set_basic_selection
return self._set_basic_selection_nd(selection, value, fields=fields)
zarr/core.py:1680: in _set_basic_selection_nd
self._set_selection(indexer, value, fields=fields)
zarr/core.py:1732: in _set_selection
self._chunk_setitem(chunk_coords, chunk_selection, chunk_value, fields=fields)
zarr/core.py:1994: in _chunk_setitem
self._chunk_setitem_nosync(chunk_coords, chunk_selection, value,
zarr/core.py:2002: in _chunk_setitem_nosync
if (not self.write_empty_chunks) and all_equal(self.fill_value, cdata):
zarr/util.py:672: in all_equal
return not np.any(array)
<__array_function__ internals>:180: in any
???
/usr/share/miniconda/envs/minimal/lib/python3.10/site-packages/numpy/core/fromnumeric.py:2395: in any
return _wrapreduction(a, np.logical_or, 'any', axis, None, out,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ obj = array(['baz', 'baz', 'baz', 'bar', 'foo'], dtype='<U3')
ufunc = <ufunc 'logical_or'>, method = 'any', axis = None, dtype = None
out = None, kwargs = {'keepdims': <no value>, 'where': <no value>}
passkwargs = {}
def _wrapreduction(obj, ufunc, method, axis, dtype, out, **kwargs):
passkwargs = {k: v for k, v in kwargs.items()
if v is not np._NoValue}
if type(obj) is not mu.ndarray:
try:
reduction = getattr(obj, method)
except AttributeError:
pass
else:
# This branch is needed for reductions like any which don't
# support a dtype.
if dtype is not None:
return reduction(axis=axis, dtype=dtype, out=out, **passkwargs)
else:
return reduction(axis=axis, out=out, **passkwargs)
> return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
E ValueError: invalid literal for int() with base 10: 'baz'
/usr/share/miniconda/envs/minimal/lib/python3.10/site-packages/numpy/core/fromnumeric.py:86: ValueError

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Interesting. Looks like the error message and type changed from NumPy 1.21 to 1.22. Don't think that was intentional, but could be wrong. Raised issue ( numpy/numpy#20898 )

In any event this workaround seems reasonable in the interim

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great, thanks! Once this is green, I'll move forward with 2.11.0.

@jakirkham

Copy link
Copy Markdown
Member

Hi both, I'm unclear about whether I need to do anything here, please ping me directly if you want me to update something. 😊

If we can convince both you, @jni, and @d-v-b to write a few lines for the blogpost ( #901 ), that would be fantastic! 😃

Can just post in that issue if you like

@joshmoore

Copy link
Copy Markdown
Member

If we can convince both you, @jni, and @d-v-b to write a few lines for the blogpost ( #901 )

e.g. was just mentioning to @MSanKeys963 how cool the use case from https://forum.image.sc/t/data-store-and-library-backend-for-napari-plugin/61779 is ;)

(....still waiting on actions...)

@joshmoore

Copy link
Copy Markdown
Member

Trying to reopen to get tests to complete.

@joshmoorejoshmoore reopened this Jan 26, 2022
@joshmoore

Copy link
Copy Markdown
Member

I've not been able to get the 3.8 and 3.9 tests to run on this PR yet. (3-4 retries)

@jakirkham

Copy link
Copy Markdown
Member

Looks like some of the past commits in this PR (though not all) had the same issue

@joshmoore

Copy link
Copy Markdown
Member

Oddly py 3.7 is still spinning here compared to #951

@joshmoore

Copy link
Copy Markdown
Member

Power cycling for CI.

@joshmoorejoshmoore reopened this Feb 4, 2022
@joshmoore

Copy link
Copy Markdown
Member

Holy moly that was an adventure! Looks like we're good to move forward with 2.11. (🍺 🍸 or similar all around this weekend)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@jni@d-v-b@jakirkham@rabernat@joshmoore@shoyer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Set write_empty_chunks to default to False by jni · Pull Request #853 · zarr-developers/zarr-python · GitHub
Skip to content

Set write_empty_chunks to default to False - #853

Merged
joshmoore merged 14 commits into
zarr-developers:masterfrom
jni:default-write-empty-chunks-false
Feb 4, 2022
Merged

Set write_empty_chunks to default to False#853
joshmoore merged 14 commits into
zarr-developers:masterfrom
jni:default-write-empty-chunks-false

Conversation

@jni

@jnijni commented Oct 20, 2021

Copy link
Copy Markdown
Contributor

In #738, despite general agreement that most people expect write_empty_chunks to default to False, @d-v-b decided to go the safe, uncontroversial route to get the PR merged, and made the default value True. All this PR does is set the default to False.

Convenient links to discussion suggesting write_empty_chunks=False should be the default:

  • @meggartsays: "In Zarr.jl, not writing empty chunks is already the default [...] so I would be in favor of changing the default behavior in Python as well.
  • @shoyersays: "it is not clear to me why you would not want to do this. I think this could be the new default behavior."
  • @martindurantsays: "I am also surprised that this is not the default, and would agree that I can't see people wanting to use the current behaviour ever."

There were performance concerns with FSSpec bulk delete items, but it seems that they were addressed upstream.

TODO:

  • Add unit tests and/or doctests in docstrings
  • Add docstrings and API docs for any new/modified user-facing classes and functions
  • New/modified features documented in docs/tutorial.rst
  • Changes documented in docs/release.rst
  • GitHub Actions have all passed
  • Test coverage is 100% (Codecov passes)

@d-v-b

Copy link
Copy Markdown
Contributor

Sounds great to me :) Thanks for doing what I didn't have the courage to do.

@jni
jniforce-pushed the default-write-empty-chunks-false branch from d83df68 to f3280a7CompareOctober 21, 2021 00:22
@jakirkham

Copy link
Copy Markdown
Member

@joshmoore wdyt?

@rabernat

Copy link
Copy Markdown
Contributor

👍 from me. I think this is a much better default.

@jakirkham

Copy link
Copy Markdown
Member

There are some lines scattered throughout the tests like these

write_empty_chunks=kwargs.pop('write_empty_chunks', True)

write_empty_chunks=kwargs.pop('write_empty_chunks', True)

Do we want to update those as well?

@jni

jni commented Nov 30, 2021

Copy link
Copy Markdown
ContributorAuthor

Do we want to update those as well?

🤷 All this means is that more tests will run with the default True. This might be useful for historical reasons. Both True and False are explicitly tested so I don't think we need to worry.

@jakirkhamjakirkham mentioned this pull request Dec 1, 2021
@jni
jniforce-pushed the default-write-empty-chunks-false branch from 3e7dbea to 6b461a7CompareDecember 2, 2021 01:48
@jni

jni commented Dec 2, 2021

Copy link
Copy Markdown
ContributorAuthor

This is now rebased on the latest master.

@joshmoore

Copy link
Copy Markdown
Member

Per #901, @d-v-b was working on a blurb for blog/release notes/docs so that users are maximally informed of the change.

@joshmoore

Copy link
Copy Markdown
Member

Capturing initial results from @d-v-b here: "users with "dense" data will see a ~15% increase in write times"

@d-v-b

d-v-b commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

N.B. that result doesn't include compression time. I will provide improved benchmarking code later today with a more realistic workflow.

@d-v-b

d-v-b commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

some interesting benchmarking results. see this repo for code: https://github.com/d-v-b/zarr-benchmarks

Each test writes a 64 MB chunk 128 times with write_empty_chunks drawn from {True, False} and the write value drawn from {array of 0s, array of random ints} , i.e. 4 different conditions. The plots show histrograms with an X axis that is the write duration normalized to the median of the time required under the (write_empty_chunks=False, write_value=0) condition.

First, with no compressor:
image

Next, with Blosc compression:
image

And last, with GZip compression:
image

Takeaways:

  • The overhead associated with the emptiness checking is exposed with Blosc compression and no compressor.
  • With blosc, writing empty chunks is actually faster than an emptiness check -> no write.
  • With GZip, handling empty chunks is much faster with write_empty_chunks=False, and the overhead associated with emptiness checking is not evident when writing "dense" chunks.

Happy to hear suggestions for changing the benchmark / plotting. E.g., maybe we want to show absolute duration instead of relative durations.

@shoyer

Copy link
Copy Markdown
Contributor

What sort of local disk are you using? I can imagine very different results for SSD vs HDD vs cloud object stores. Extra copies are never desirable, but in most cases I would guess the overhead of checking is acceptable.

@d-v-b

d-v-b commented Dec 8, 2021

Copy link
Copy Markdown
Contributor

@shoyer this is on an SSD, so pretty ideal in terms of storage latency.

@d-v-b

d-v-b commented Dec 14, 2021

Copy link
Copy Markdown
Contributor

@joshmoore I added content to the tutorial docs, let me know if you want more or an expanded release note.

I'm not sure the benchmarks are that illuminating or surprising, so maybe we don't really need to involve them?

@joshmoore

Copy link
Copy Markdown
Member

The location looks good. Could I suggest we try to get two or four simple writes with the different flags and inputs. Something like:

data = np.random.randint(0, 255, size=shape, dtype=DTYPE)
arr = zarr.open(zarr.NestedDirectoryStore(store), write_empty_chunks=write_empty_chunks, ...)
arr[:] = 255

from https://github.com/d-v-b/zarr-benchmarks/blob/main/src/zarr-benchmarks/empty_chunks.py with some form of marker ("15% slower") for someone to see at a glance.

I'm not sure the benchmarks are that illuminating or surprising, so maybe we don't really need to involve them?

Agreed. Happy to have them in your repo. I find them pretty fascinating. ;)

@jakirkham

Copy link
Copy Markdown
Member

@d-v-b, did you have a chance to try the other flags Josh mentioned above? 🙂

@d-v-b

d-v-b commented Jan 5, 2022

Copy link
Copy Markdown
Contributor

I added a short benchmark example, but I can't build the docs for some reason (tox -e docs yields some inscrutable error from a different part of the codebase)

@d-v-b

d-v-b commented Jan 5, 2022

Copy link
Copy Markdown
Contributor

Oh and there's a new test failure in an unrelated part of the codebase. Not sure what's going on there.

@joshmoore

Copy link
Copy Markdown
Member

Oy vey. That's a new year for you. The new failures is related to the category filter. I don't remember anything that should impact it. Just starting to look at the docs failure.

(Thanks for the benchmark example!)

@joshmoore

Copy link
Copy Markdown
Member

Ok, @d-v-b, that should fix the docs build. Let's see what's failing on this run.

@jni

jni commented Jan 10, 2022

Copy link
Copy Markdown
ContributorAuthor

Hi both, I'm unclear about whether I need to do anything here, please ping me directly if you want me to update something. 😊

@joshmoore

Copy link
Copy Markdown
Member

Sorry for dropping the ball here. Don't think there's anything needed from your side, @jni. Just very unsure what's up with this failing test. Other than that, I'd very much like to get 2.11 out ASAP.

The call to `np.any(array)` in zarr.util.all_equal triggers the
following ValueError:
```
> return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
E ValueError: invalid literal for int() with base 10: 'baz'
```
Extending the catch block allows test_array_with_categorize_filter
to pass, but it's unclear if this points to a deeper issue.
@joshmoore

Copy link
Copy Markdown
Member

Ok. Pushed a proposed fix related to @d-v-b's all_equal.

Comment threadzarr/util.py
try:
return not np.any(array)
except TypeError: # pragma: no cover
except (TypeError, ValueError): # pragma: no cover

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What causes the ValueError? Is there a particular array type or value that np.any is raising on?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It occurs when a category value (baz) gets passed to int().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

from failing build
______________________ test_array_with_categorize_filter _______________________
def test_array_with_categorize_filter():
# setup
data = np.random.choice(['foo', 'bar', 'baz'], size=100)
flt = Categorize(dtype=data.dtype, labels=['foo', 'bar', 'baz'])
filters = [flt]
for compressor in compressors:
> a = array(data, chunks=5, compressor=compressor, filters=filters)
zarr/tests/test_filters.py:172: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ zarr/creation.py:366: in array
z[...] = data
zarr/core.py:1285: in __setitem__
self.set_basic_selection(pure_selection, value, fields=fields)
zarr/core.py:1380: in set_basic_selection
return self._set_basic_selection_nd(selection, value, fields=fields)
zarr/core.py:1680: in _set_basic_selection_nd
self._set_selection(indexer, value, fields=fields)
zarr/core.py:1732: in _set_selection
self._chunk_setitem(chunk_coords, chunk_selection, chunk_value, fields=fields)
zarr/core.py:1994: in _chunk_setitem
self._chunk_setitem_nosync(chunk_coords, chunk_selection, value,
zarr/core.py:2002: in _chunk_setitem_nosync
if (not self.write_empty_chunks) and all_equal(self.fill_value, cdata):
zarr/util.py:672: in all_equal
return not np.any(array)
<__array_function__ internals>:180: in any
???
/usr/share/miniconda/envs/minimal/lib/python3.10/site-packages/numpy/core/fromnumeric.py:2395: in any
return _wrapreduction(a, np.logical_or, 'any', axis, None, out,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ obj = array(['baz', 'baz', 'baz', 'bar', 'foo'], dtype='<U3')
ufunc = <ufunc 'logical_or'>, method = 'any', axis = None, dtype = None
out = None, kwargs = {'keepdims': <no value>, 'where': <no value>}
passkwargs = {}
def _wrapreduction(obj, ufunc, method, axis, dtype, out, **kwargs):
passkwargs = {k: v for k, v in kwargs.items()
if v is not np._NoValue}
if type(obj) is not mu.ndarray:
try:
reduction = getattr(obj, method)
except AttributeError:
pass
else:
# This branch is needed for reductions like any which don't
# support a dtype.
if dtype is not None:
return reduction(axis=axis, dtype=dtype, out=out, **passkwargs)
else:
return reduction(axis=axis, out=out, **passkwargs)
> return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
E ValueError: invalid literal for int() with base 10: 'baz'
/usr/share/miniconda/envs/minimal/lib/python3.10/site-packages/numpy/core/fromnumeric.py:86: ValueError

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Interesting. Looks like the error message and type changed from NumPy 1.21 to 1.22. Don't think that was intentional, but could be wrong. Raised issue ( numpy/numpy#20898 )

In any event this workaround seems reasonable in the interim

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great, thanks! Once this is green, I'll move forward with 2.11.0.

@jakirkham

Copy link
Copy Markdown
Member

Hi both, I'm unclear about whether I need to do anything here, please ping me directly if you want me to update something. 😊

If we can convince both you, @jni, and @d-v-b to write a few lines for the blogpost ( #901 ), that would be fantastic! 😃

Can just post in that issue if you like

@joshmoore

Copy link
Copy Markdown
Member

If we can convince both you, @jni, and @d-v-b to write a few lines for the blogpost ( #901 )

e.g. was just mentioning to @MSanKeys963 how cool the use case from https://forum.image.sc/t/data-store-and-library-backend-for-napari-plugin/61779 is ;)

(....still waiting on actions...)

@joshmoore

Copy link
Copy Markdown
Member

Trying to reopen to get tests to complete.

@joshmoorejoshmoore reopened this Jan 26, 2022
@joshmoore

Copy link
Copy Markdown
Member

I've not been able to get the 3.8 and 3.9 tests to run on this PR yet. (3-4 retries)

@jakirkham

Copy link
Copy Markdown
Member

Looks like some of the past commits in this PR (though not all) had the same issue

@joshmoore

Copy link
Copy Markdown
Member

Oddly py 3.7 is still spinning here compared to #951

@joshmoore

Copy link
Copy Markdown
Member

Power cycling for CI.

@joshmoorejoshmoore reopened this Feb 4, 2022
@joshmoore

Copy link
Copy Markdown
Member

Holy moly that was an adventure! Looks like we're good to move forward with 2.11. (🍺 🍸 or similar all around this weekend)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@jni@d-v-b@jakirkham@rabernat@joshmoore@shoyer
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Set write_empty_chunks to default to False by jni · Pull Request #853 · zarr-developers/zarr-python · GitHub
Skip to content

Set write_empty_chunks to default to False - #853

Merged
joshmoore merged 14 commits into
zarr-developers:masterfrom
jni:default-write-empty-chunks-false
Feb 4, 2022
Merged

Set write_empty_chunks to default to False#853
joshmoore merged 14 commits into
zarr-developers:masterfrom
jni:default-write-empty-chunks-false

Conversation

@jni

@jnijni commented Oct 20, 2021

Copy link
Copy Markdown
Contributor

In #738, despite general agreement that most people expect write_empty_chunks to default to False, @d-v-b decided to go the safe, uncontroversial route to get the PR merged, and made the default value True. All this PR does is set the default to False.

Convenient links to discussion suggesting write_empty_chunks=False should be the default:

  • @meggartsays: "In Zarr.jl, not writing empty chunks is already the default [...] so I would be in favor of changing the default behavior in Python as well.
  • @shoyersays: "it is not clear to me why you would not want to do this. I think this could be the new default behavior."
  • @martindurantsays: "I am also surprised that this is not the default, and would agree that I can't see people wanting to use the current behaviour ever."

There were performance concerns with FSSpec bulk delete items, but it seems that they were addressed upstream.

TODO:

  • Add unit tests and/or doctests in docstrings
  • Add docstrings and API docs for any new/modified user-facing classes and functions
  • New/modified features documented in docs/tutorial.rst
  • Changes documented in docs/release.rst
  • GitHub Actions have all passed
  • Test coverage is 100% (Codecov passes)

@d-v-b

Copy link
Copy Markdown
Contributor

Sounds great to me :) Thanks for doing what I didn't have the courage to do.

@jni
jniforce-pushed the default-write-empty-chunks-false branch from d83df68 to f3280a7CompareOctober 21, 2021 00:22
@jakirkham

Copy link
Copy Markdown
Member

@joshmoore wdyt?

@rabernat

Copy link
Copy Markdown
Contributor

👍 from me. I think this is a much better default.

@jakirkham

Copy link
Copy Markdown
Member

There are some lines scattered throughout the tests like these

write_empty_chunks=kwargs.pop('write_empty_chunks', True)

write_empty_chunks=kwargs.pop('write_empty_chunks', True)

Do we want to update those as well?

@jni

jni commented Nov 30, 2021

Copy link
Copy Markdown
ContributorAuthor

Do we want to update those as well?

🤷 All this means is that more tests will run with the default True. This might be useful for historical reasons. Both True and False are explicitly tested so I don't think we need to worry.

@jakirkhamjakirkham mentioned this pull request Dec 1, 2021
@jni
jniforce-pushed the default-write-empty-chunks-false branch from 3e7dbea to 6b461a7CompareDecember 2, 2021 01:48
@jni

jni commented Dec 2, 2021

Copy link
Copy Markdown
ContributorAuthor

This is now rebased on the latest master.

@joshmoore

Copy link
Copy Markdown
Member

Per #901, @d-v-b was working on a blurb for blog/release notes/docs so that users are maximally informed of the change.

@joshmoore

Copy link
Copy Markdown
Member

Capturing initial results from @d-v-b here: "users with "dense" data will see a ~15% increase in write times"

@d-v-b

d-v-b commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

N.B. that result doesn't include compression time. I will provide improved benchmarking code later today with a more realistic workflow.

@d-v-b

d-v-b commented Dec 7, 2021

Copy link
Copy Markdown
Contributor

some interesting benchmarking results. see this repo for code: https://github.com/d-v-b/zarr-benchmarks

Each test writes a 64 MB chunk 128 times with write_empty_chunks drawn from {True, False} and the write value drawn from {array of 0s, array of random ints} , i.e. 4 different conditions. The plots show histrograms with an X axis that is the write duration normalized to the median of the time required under the (write_empty_chunks=False, write_value=0) condition.

First, with no compressor:
image

Next, with Blosc compression:
image

And last, with GZip compression:
image

Takeaways:

  • The overhead associated with the emptiness checking is exposed with Blosc compression and no compressor.
  • With blosc, writing empty chunks is actually faster than an emptiness check -> no write.
  • With GZip, handling empty chunks is much faster with write_empty_chunks=False, and the overhead associated with emptiness checking is not evident when writing "dense" chunks.

Happy to hear suggestions for changing the benchmark / plotting. E.g., maybe we want to show absolute duration instead of relative durations.

@shoyer

Copy link
Copy Markdown
Contributor

What sort of local disk are you using? I can imagine very different results for SSD vs HDD vs cloud object stores. Extra copies are never desirable, but in most cases I would guess the overhead of checking is acceptable.

@d-v-b

d-v-b commented Dec 8, 2021

Copy link
Copy Markdown
Contributor

@shoyer this is on an SSD, so pretty ideal in terms of storage latency.

@d-v-b

d-v-b commented Dec 14, 2021

Copy link
Copy Markdown
Contributor

@joshmoore I added content to the tutorial docs, let me know if you want more or an expanded release note.

I'm not sure the benchmarks are that illuminating or surprising, so maybe we don't really need to involve them?

@joshmoore

Copy link
Copy Markdown
Member

The location looks good. Could I suggest we try to get two or four simple writes with the different flags and inputs. Something like:

data = np.random.randint(0, 255, size=shape, dtype=DTYPE)
arr = zarr.open(zarr.NestedDirectoryStore(store), write_empty_chunks=write_empty_chunks, ...)
arr[:] = 255

from https://github.com/d-v-b/zarr-benchmarks/blob/main/src/zarr-benchmarks/empty_chunks.py with some form of marker ("15% slower") for someone to see at a glance.

I'm not sure the benchmarks are that illuminating or surprising, so maybe we don't really need to involve them?

Agreed. Happy to have them in your repo. I find them pretty fascinating. ;)

@jakirkham

Copy link
Copy Markdown
Member

@d-v-b, did you have a chance to try the other flags Josh mentioned above? 🙂

@d-v-b

d-v-b commented Jan 5, 2022

Copy link
Copy Markdown
Contributor

I added a short benchmark example, but I can't build the docs for some reason (tox -e docs yields some inscrutable error from a different part of the codebase)

@d-v-b

d-v-b commented Jan 5, 2022

Copy link
Copy Markdown
Contributor

Oh and there's a new test failure in an unrelated part of the codebase. Not sure what's going on there.

@joshmoore

Copy link
Copy Markdown
Member

Oy vey. That's a new year for you. The new failures is related to the category filter. I don't remember anything that should impact it. Just starting to look at the docs failure.

(Thanks for the benchmark example!)

@joshmoore

Copy link
Copy Markdown
Member

Ok, @d-v-b, that should fix the docs build. Let's see what's failing on this run.

@jni

jni commented Jan 10, 2022

Copy link
Copy Markdown
ContributorAuthor

Hi both, I'm unclear about whether I need to do anything here, please ping me directly if you want me to update something. 😊

@joshmoore

Copy link
Copy Markdown
Member

Sorry for dropping the ball here. Don't think there's anything needed from your side, @jni. Just very unsure what's up with this failing test. Other than that, I'd very much like to get 2.11 out ASAP.

The call to `np.any(array)` in zarr.util.all_equal triggers the
following ValueError:
```
> return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
E ValueError: invalid literal for int() with base 10: 'baz'
```
Extending the catch block allows test_array_with_categorize_filter
to pass, but it's unclear if this points to a deeper issue.
@joshmoore

Copy link
Copy Markdown
Member

Ok. Pushed a proposed fix related to @d-v-b's all_equal.

Comment threadzarr/util.py
try:
return not np.any(array)
except TypeError: # pragma: no cover
except (TypeError, ValueError): # pragma: no cover

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What causes the ValueError? Is there a particular array type or value that np.any is raising on?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It occurs when a category value (baz) gets passed to int().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

from failing build
______________________ test_array_with_categorize_filter _______________________
def test_array_with_categorize_filter():
# setup
data = np.random.choice(['foo', 'bar', 'baz'], size=100)
flt = Categorize(dtype=data.dtype, labels=['foo', 'bar', 'baz'])
filters = [flt]
for compressor in compressors:
> a = array(data, chunks=5, compressor=compressor, filters=filters)
zarr/tests/test_filters.py:172: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ zarr/creation.py:366: in array
z[...] = data
zarr/core.py:1285: in __setitem__
self.set_basic_selection(pure_selection, value, fields=fields)
zarr/core.py:1380: in set_basic_selection
return self._set_basic_selection_nd(selection, value, fields=fields)
zarr/core.py:1680: in _set_basic_selection_nd
self._set_selection(indexer, value, fields=fields)
zarr/core.py:1732: in _set_selection
self._chunk_setitem(chunk_coords, chunk_selection, chunk_value, fields=fields)
zarr/core.py:1994: in _chunk_setitem
self._chunk_setitem_nosync(chunk_coords, chunk_selection, value,
zarr/core.py:2002: in _chunk_setitem_nosync
if (not self.write_empty_chunks) and all_equal(self.fill_value, cdata):
zarr/util.py:672: in all_equal
return not np.any(array)
<__array_function__ internals>:180: in any
???
/usr/share/miniconda/envs/minimal/lib/python3.10/site-packages/numpy/core/fromnumeric.py:2395: in any
return _wrapreduction(a, np.logical_or, 'any', axis, None, out,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ obj = array(['baz', 'baz', 'baz', 'bar', 'foo'], dtype='<U3')
ufunc = <ufunc 'logical_or'>, method = 'any', axis = None, dtype = None
out = None, kwargs = {'keepdims': <no value>, 'where': <no value>}
passkwargs = {}
def _wrapreduction(obj, ufunc, method, axis, dtype, out, **kwargs):
passkwargs = {k: v for k, v in kwargs.items()
if v is not np._NoValue}
if type(obj) is not mu.ndarray:
try:
reduction = getattr(obj, method)
except AttributeError:
pass
else:
# This branch is needed for reductions like any which don't
# support a dtype.
if dtype is not None:
return reduction(axis=axis, dtype=dtype, out=out, **passkwargs)
else:
return reduction(axis=axis, out=out, **passkwargs)
> return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
E ValueError: invalid literal for int() with base 10: 'baz'
/usr/share/miniconda/envs/minimal/lib/python3.10/site-packages/numpy/core/fromnumeric.py:86: ValueError

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Interesting. Looks like the error message and type changed from NumPy 1.21 to 1.22. Don't think that was intentional, but could be wrong. Raised issue ( numpy/numpy#20898 )

In any event this workaround seems reasonable in the interim

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great, thanks! Once this is green, I'll move forward with 2.11.0.

@jakirkham

Copy link
Copy Markdown
Member

Hi both, I'm unclear about whether I need to do anything here, please ping me directly if you want me to update something. 😊

If we can convince both you, @jni, and @d-v-b to write a few lines for the blogpost ( #901 ), that would be fantastic! 😃

Can just post in that issue if you like

@joshmoore

Copy link
Copy Markdown
Member

If we can convince both you, @jni, and @d-v-b to write a few lines for the blogpost ( #901 )

e.g. was just mentioning to @MSanKeys963 how cool the use case from https://forum.image.sc/t/data-store-and-library-backend-for-napari-plugin/61779 is ;)

(....still waiting on actions...)

@joshmoore

Copy link
Copy Markdown
Member

Trying to reopen to get tests to complete.

@joshmoorejoshmoore reopened this Jan 26, 2022
@joshmoore

Copy link
Copy Markdown
Member

I've not been able to get the 3.8 and 3.9 tests to run on this PR yet. (3-4 retries)

@jakirkham

Copy link
Copy Markdown
Member

Looks like some of the past commits in this PR (though not all) had the same issue

@joshmoore

Copy link
Copy Markdown
Member

Oddly py 3.7 is still spinning here compared to #951

@joshmoore

Copy link
Copy Markdown
Member

Power cycling for CI.

@joshmoorejoshmoore reopened this Feb 4, 2022
@joshmoore

Copy link
Copy Markdown
Member

Holy moly that was an adventure! Looks like we're good to move forward with 2.11. (🍺 🍸 or similar all around this weekend)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@jni@d-v-b@jakirkham@rabernat@joshmoore@shoyer