Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH,API: Make the errstate/extobj a contextvar #23936

Merged
merged 12 commits into from
Jun 16, 2023

Conversation

seberg
Copy link
Member

@seberg seberg commented Jun 13, 2023

**This builds on gh-23922, so a draft. Also needs some release notes, but it is ready for review (the diff is just too large without gh-23992)**

This does a few changes:

  • Clean up a bit (a few commits to simplify)
  • Change the core to be a capsule which holds the bufsize. Then moves parsing fully to C (the split doesn't make life easier anyway). In short, it completely revamps most of the errstate handling internally.
  • More cleanup, hide away all those Python and C constants nobody should see.

In the end, things are now well encapsulated into extobj.c rather than sprinkled around. It

The choice of a capsule is a bit 🤷 a custom object may actually be almost as lean and neat because it removes some of that custom cleanup.

End-user effects:

  • np.errstate is now actually thread-safe and additionally context safe.
  • np.errstate cannot be entered twice (but that doesn't make sense). I made its attribute private also (but you shouldn't use them).
    • I do allow entering multiple times (if you exited first), because the random code somewhere did it.
  • Things are faster.
  • np.errstate is much faster (I think about 2.5-3 times), not sure if the __slots__ matter, but why not.

There is still a bunch of follow-ups that should happen eventually. The way the ufuncs use this API isn't adapted yet (and passes unused stuff maybe even, but maybe will add that part here). Mainly, we probably should get the extobj exactly once.

Closes gh-9444
Closes gh-19006

@seberg
Copy link
Member Author

seberg commented Jun 13, 2023

PyPy doesn't support PyContextVar_Resets()? @mattip I guess calling back into python is probably be the best choice (with an #ifdef guard)? Or I can expose the contextvar to Python, but I somewhat like it hidden away.
(emscript test is already fixed/skipped on other PR.)

@mattip
Copy link
Member

mattip commented Jun 14, 2023

Yay, less code and faster too! Does the speedup show up in the benchmarks?

You are using a capsule because it is easier than a typeobject? I guess that is fine, and it is an implementation detail we can change later.

PyPy doesn't support PyContextVar_Reset()?

No, as of now it does not. Basically PyPy implements all the contextvar C-API by calling back into the interpreter's _contextvars module, which in PyPy is implemented in pure python following the code in PEP 567. In fact, on PyPy it would be preferable to do as much of this in pure python as possible. Is there a reason you decided to implement this in C rather than in python?

@seberg
Copy link
Member Author

seberg commented Jun 14, 2023

Is there a reason you decided to implement this in C rather than in python?

I thought I would hide things, and changing the contextvar in C seemed fair (so that _seterrobj does both. It is arguably simpler (and even a bit faster).

I just did a change to expose it and at least do the contextvar.reset(token) in python. I need to create the capsule in C. Would it be better for PyPy if _seterrobj() just returned the capsule and we add it to the contextvar in Python? (i.e. switches between C/Python one less time?)

I think that would be fine also.

On the capsule: yes, its just slightly easier to implement, although means two objects are created (capsule struct + capsule). This is one of those things where cython would be nice (also since we could move np.errstate() to cython maybe. But I think its a better followup.

The optimization doesn't make sense and is incorrect to begin with.
It just stands in the way of moving to a contextvar.
(Maybe there is a way to optimize the contextvar access, but it has
to look differently and should be done after we got it.)
Mainly, pass the ufunc name everywhere explicitly, rather than
building an unnecessary tuple.
Move them to live exclusively in `extobj.c` source file where they
are actually used.
This fixes PyPy, which doesn't implement the full C-side API.
It is also actually faster probably...
@seberg seberg marked this pull request as ready for review June 14, 2023 07:33
@mattip
Copy link
Member

mattip commented Jun 14, 2023

Would it be better for PyPy if _seterrobj() just returned the capsule and we add it to the contextvar in Python?

I think so. Would it be less code?

@mattip
Copy link
Member

mattip commented Jun 14, 2023

Thinking about naming, calling it errstate is confusing. Buffsize is not really an error handling parameter. If we add a precision field, that will make the name less appropriate. But it might be too disruptive to change it to something else like ufuncstate

…uncs)

This may be a few nanoseconds slower in CPython, but should be better
for PyPy.  It is also maybe just a bit easier to grok since the reset
of the contextvar is in Python already anyway.
@seberg
Copy link
Member Author

seberg commented Jun 14, 2023

I think so. Would it be less code?

Done, doesn't make much difference code-wise, but since the reset is in Python now, it probably is a bit easier to grok anyway and I don't want to worry about the nanoseconds lost for CPython, to make this faster we would need to make np.errstate itself lighter mostly.

Thinking about naming, calling it errstate is confusing

Yeah, errstate is now a misnomer (more than before). I didn't add bufsize to errstate yet, but unless anyone doesn't like integrating it (it could be a distinct contextvar) my thinking currently is:

  • Keep errstate it is way too disruptive +to remove it
  • Add bufsize to errstate anyway (probably, I have no rush, but its scope is tied to it now.)
  • Maybe add ufuncstate as a (preferred?) alias to make it less of a misnomer if we grow it for precision/fast (and who knows, maybe also threads).

Copy link
Member

@mattip mattip left a comment

Choose a reason for hiding this comment

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

Nice. There is a segfault (maybe at shutdown?) and some other failures I didn't look at.

I found a few nitpicky typos that can be part of the next set of commits

doc/release/upcoming_changes/23936.improvement.rst Outdated Show resolved Hide resolved
numpy/core/src/umath/extobj.c Outdated Show resolved Hide resolved
numpy/core/src/umath/extobj.c Outdated Show resolved Hide resolved
@seberg
Copy link
Member Author

seberg commented Jun 14, 2023

I aborted the timing, the full thing takes quite long nowadays.

In any case, the only thing I could think of that I expected to slow down is:

In [1]: a = np.ones((2, 2))
In [2]: b = np.ones(2)
In [3]: %timeit a + b
632 ns ± 0.195 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

and it actually got faster with the change: to 619ns.

When you ignore FPEs (and they get floated) the speedup is at least 50ns in total.

The with np.errstate(4 kwargs): overhead on my computer changes from about 2.2us to 0.7us. Still the cost of a cheap ufunc call, but at least not 3 times it.

@seberg
Copy link
Member Author

seberg commented Jun 14, 2023

Cirrus CI got this weird error again (going to restart, since I don't think its related)

EDIT: There was a real bug. Although likely unrelated to the specific failure.

f2py/tests/test_return_real.py: 16 warnings
  /private/var/folders/76/zy5ktkns50v6gt5g8r0sf6sc0000gn/T/cibw-run-tqz8ywxt/cp310-macosx_arm64/venv-test/lib/python3.10/site-packages/numpy/f2py/tests/test_return_real.py:28: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)
    assert abs(t(array([234], "l")) - 234.0) <= err

f2py/tests/test_return_real.py: 16 warnings
  /private/var/folders/76/zy5ktkns50v6gt5g8r0sf6sc0000gn/T/cibw-run-tqz8ywxt/cp310-macosx_arm64/venv-test/lib/python3.10/site-packages/numpy/f2py/tests/test_return_real.py:29: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)
    assert abs(t(array([234], "B")) - 234.0) <= err

f2py/tests/test_return_real.py: 16 warnings
  /private/var/folders/76/zy5ktkns50v6gt5g8r0sf6sc0000gn/T/cibw-run-tqz8ywxt/cp310-macosx_arm64/venv-test/lib/python3.10/site-packages/numpy/f2py/tests/test_return_real.py:30: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)
    assert abs(t(array([234], "f")) - 234.0) <= err

f2py/tests/test_return_real.py: 16 warnings
  /private/var/folders/76/zy5ktkns50v6gt5g8r0sf6sc0000gn/T/cibw-run-tqz8ywxt/cp310-macosx_arm64/venv-test/lib/python3.10/site-packages/numpy/f2py/tests/test_return_real.py:31: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)
    assert abs(t(array([234], "d")) - 234.0) <= err

linalg/tests/test_linalg.py::TestDet::test_types[complex64]
linalg/tests/test_linalg.py::TestDet::test_types[complex128]
  /private/var/folders/76/zy5ktkns50v6gt5g8r0sf6sc0000gn/T/cibw-run-tqz8ywxt/cp310-macosx_arm64/venv-test/lib/python3.10/site-packages/numpy/linalg/linalg.py:2175: RuntimeWarning: divide by zero encountered in det
    r = _umath_linalg.det(a, signature=signature)

linalg/tests/test_linalg.py::TestDet::test_types[complex64]
linalg/tests/test_linalg.py::TestDet::test_types[complex128]
  /private/var/folders/76/zy5ktkns50v6gt5g8r0sf6sc0000gn/T/cibw-run-tqz8ywxt/cp310-macosx_arm64/venv-test/lib/python3.10/site-packages/numpy/linalg/linalg.py:2175: RuntimeWarning: invalid value encountered in det
    r = _umath_linalg.det(a, signature=signature)

linalg/tests/test_linalg.py::TestDet::test_types[complex64]
linalg/tests/test_linalg.py::TestDet::test_types[complex128]
  /private/var/folders/76/zy5ktkns50v6gt5g8r0sf6sc0000gn/T/cibw-run-tqz8ywxt/cp310-macosx_arm64/venv-test/lib/python3.10/site-packages/numpy/linalg/linalg.py:2115: RuntimeWarning: divide by zero encountered in slogdet
    sign, logdet = _umath_linalg.slogdet(a, signature=signature)

linalg/tests/test_linalg.py::TestDet::test_types[complex64]
linalg/tests/test_linalg.py::TestDet::test_types[complex128]
  /private/var/folders/76/zy5ktkns50v6gt5g8r0sf6sc0000gn/T/cibw-run-tqz8ywxt/cp310-macosx_arm64/venv-test/lib/python3.10/site-packages/numpy/linalg/linalg.py:2115: RuntimeWarning: invalid value encountered in slogdet
    sign, logdet = _umath_linalg.slogdet(a, signature=signature)

-- Docs: https://docs.pytest.org/en/stable/warnings.html
=========================== short test summary info ============================
FAILED typing/tests/test_typing.py::test_code_runs[ufunc_config] - ValueError...
FAILED typing/tests/test_typing.py::test_code_runs[simple_py3] - ValueError: ...
FAILED typing/tests/test_typing.py::test_code_runs[random] - ValueError: PyCa...
FAILED typing/tests/test_typing.py::test_code_runs[index_tricks] - ValueError...
FAILED typing/tests/test_typing.py::test_code_runs[numeric] - ValueError: PyC...
FAILED typing/tests/test_typing.py::test_code_runs[simple] - ValueError: PyCa...
FAILED typing/tests/test_typing.py::test_code_runs[mod] - ValueError: PyCapsu...
FAILED typing/tests/test_typing.py::test_code_runs[arrayprint] - ValueError: ...
FAILED typing/tests/test_typing.py::test_code_runs[ufunclike] - ValueError: P...
FAILED typing/tests/test_typing.py::test_code_runs[bitwise_ops] - ValueError:...
10 failed, 35030 passed, 212 skipped, 38 xfailed, 9 xpassed, 349 warnings in 386.36s (0:06:26)
 a=
 a=

                                                                     ✕ 397.86s
Error: Command ['/bin/sh', '-c', 'bash /private/var/folders/76/zy5ktkns50v6gt5g8r0sf6sc0000gn/T/cirrus-ci-build/tools/wheels/cibw_test_command.sh /private/var/folders/76/zy5ktkns50v6gt5g8r0sf6sc0000gn/T/cirrus-ci-build'] failed with code 1. None

Impressive how few tests failed, but tests *did* fail and they
didn't segfault :)
Copy link
Member

@mattip mattip left a comment

Choose a reason for hiding this comment

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

LGTM. I will leave it a bit so others can have a look.

@mattip
Copy link
Member

mattip commented Jun 14, 2023

In the previous PR there are still traces of the removed interfaces in the documentation. I see a warning when building the docs:

WARNING: [autosummary] failed to import numpy.geterrobj.
Possible hints:
* ModuleNotFoundError: No module named 'numpy.geterrobj'
* AttributeError: module 'numpy' has no attribute 'geterrobj'
* ImportError: 
WARNING: [autosummary] failed to import numpy.seterrobj.

I think that is coming from doc/source/reference/routines.err.rst

Could you remove them in this PR or should I make another to do it?

@charris charris merged commit 8d1de4c into numpy:main Jun 16, 2023
58 checks passed
@charris
Copy link
Member

charris commented Jun 16, 2023

Thanks Sebastian.

@charris
Copy link
Member

charris commented Jun 16, 2023

Could you remove them in this PR or should I make another to do it?

I removed them, see #23964.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants