Hi all, Well, this is something that we've discussed for a while and I think generally has consensus already, but I figured I'd write it down anyway to make sure. There's a rendered version here: https://github.com/njsmith/numpy/blob/nep-0015-merge-multiarray-umath/doc/ne... ----- ============================ Merging multiarray and umath ============================ :Author: Nathaniel J. Smith <njs@pobox.com> :Status: Draft :Type: Standards Track :Created: 2018-02-22 Abstract -------- Let's merge ``numpy.core.multiarray`` and ``numpy.core.umath`` into a single extension module, and deprecate ``np.set_numeric_ops``. Background ---------- Currently, numpy's core C code is split between two separate extension modules. ``numpy.core.multiarray`` is built from ``numpy/core/src/multiarray/*.c``, and contains the core array functionality (in particular, the ``ndarray`` object). ``numpy.core.umath`` is built from ``numpy/core/src/umath/*.c``, and contains the ufunc machinery. These two modules each expose their own separate C API, accessed via ``import_multiarray()`` and ``import_umath()`` respectively. The idea is that they're supposed to be independent modules, with ``multiarray`` as a lower-level layer with ``umath`` built on top. In practice this has turned out to be problematic. First, the layering isn't perfect: when you write ``ndarray + ndarray``, this invokes ``ndarray.__add__``, which then calls the ufunc ``np.add``. This means that ``ndarray`` needs to know about ufuncs – so instead of a clean layering, we have a circular dependency. To solve this, ``multiarray`` exports a somewhat terrifying function called ``set_numeric_ops``. The bootstrap procedure each time you ``import numpy`` is: 1. ``multiarray`` and its ``ndarray`` object are loaded, but arithmetic operations on ndarrays are broken. 2. ``umath`` is loaded. 3. ``set_numeric_ops`` is used to monkeypatch all the methods like ``ndarray.__add__`` with objects from ``umath``. In addition, ``set_numeric_ops`` is exposed as a public API, ``np.set_numeric_ops``. Furthermore, even when this layering does work, it ends up distorting the shape of our public ABI. In recent years, the most common reason for adding new functions to ``multiarray``\'s "public" ABI is not that they really need to be public or that we expect other projects to use them, but rather just that we need to call them from ``umath``. This is extremely unfortunate, because it makes our public ABI unnecessarily large, and since we can never remove things from it then this creates an ongoing maintenance burden. The way C works, you can have internal API that's visible to everything inside the same extension module, or you can have a public API that everyone can use; you can't have an API that's visible to multiple extension modules inside numpy, but not to external users. We've also increasingly been putting utility code into ``numpy/core/src/private/``, which now contains a bunch of files which are ``#include``\d twice, once into ``multiarray`` and once into ``umath``. This is pretty gross, and is purely a workaround for these being separate C extensions. Proposed changes ---------------- This NEP proposes three changes: 1. We should start building ``numpy/core/src/multiarray/*.c`` and ``numpy/core/src/umath/*.c`` together into a single extension module. 2. Instead of ``set_numeric_ops``, we should use some new, private API to set up ``ndarray.__add__`` and friends. 3. We should deprecate, and eventually remove, ``np.set_numeric_ops``. Non-proposed changes -------------------- We don't necessarily propose to throw away the distinction between multiarray/ and umath/ in terms of our source code organization: internal organization is useful! We just want to build them together into a single extension module. Of course, this does open the door for potential future refactorings, which we can then evaluate based on their merits as they come up. It also doesn't propose that we break the public C ABI. We should continue to provide ``import_multiarray()`` and ``import_umath()`` functions – it's just that now both ABIs will ultimately be loaded from the same C library. Due to how ``import_multiarray()`` and ``import_umath()`` are written, we'll also still need to have modules called ``numpy.core.multiarray`` and ``numpy.core.umath``, and they'll need to continue to export ``_ARRAY_API`` and ``_UFUNC_API`` objects – but we can make one or both of these modules be tiny shims that simply re-export the magic API object from where-ever it's actually defined. (See ``numpy/core/code_generators/generate_{numpy,ufunc}_api.py`` for details of how these imports work.) Backward compatibility ---------------------- The only compatibility break is the deprecation of ``np.set_numeric_ops``. Alternatives ------------ n/a Discussion ---------- TBD Copyright --------- This document has been placed in the public domain. -- Nathaniel J. Smith -- https://vorpus.org
This means that ndarray needs to know about ufuncs – so instead of a clean layering, we have a circular dependency. Perhaps we should split ndarray into a base_ndarray class with no arithmetic support (*add*, sum, etc), and then provide an ndarray subclass from umath instead (either the separate extension, or just a different set of files) On Thu, 8 Mar 2018 at 08:25 Nathaniel Smith <njs@pobox.com> wrote:
Hi all,
Well, this is something that we've discussed for a while and I think generally has consensus already, but I figured I'd write it down anyway to make sure.
There's a rendered version here:
https://github.com/njsmith/numpy/blob/nep-0015-merge-multiarray-umath/doc/ne...
-----
============================ Merging multiarray and umath ============================
:Author: Nathaniel J. Smith <njs@pobox.com> :Status: Draft :Type: Standards Track :Created: 2018-02-22
Abstract --------
Let's merge ``numpy.core.multiarray`` and ``numpy.core.umath`` into a single extension module, and deprecate ``np.set_numeric_ops``.
Background ----------
Currently, numpy's core C code is split between two separate extension modules.
``numpy.core.multiarray`` is built from ``numpy/core/src/multiarray/*.c``, and contains the core array functionality (in particular, the ``ndarray`` object).
``numpy.core.umath`` is built from ``numpy/core/src/umath/*.c``, and contains the ufunc machinery.
These two modules each expose their own separate C API, accessed via ``import_multiarray()`` and ``import_umath()`` respectively. The idea is that they're supposed to be independent modules, with ``multiarray`` as a lower-level layer with ``umath`` built on top. In practice this has turned out to be problematic.
First, the layering isn't perfect: when you write ``ndarray + ndarray``, this invokes ``ndarray.__add__``, which then calls the ufunc ``np.add``. This means that ``ndarray`` needs to know about ufuncs – so instead of a clean layering, we have a circular dependency. To solve this, ``multiarray`` exports a somewhat terrifying function called ``set_numeric_ops``. The bootstrap procedure each time you ``import numpy`` is:
1. ``multiarray`` and its ``ndarray`` object are loaded, but arithmetic operations on ndarrays are broken.
2. ``umath`` is loaded.
3. ``set_numeric_ops`` is used to monkeypatch all the methods like ``ndarray.__add__`` with objects from ``umath``.
In addition, ``set_numeric_ops`` is exposed as a public API, ``np.set_numeric_ops``.
Furthermore, even when this layering does work, it ends up distorting the shape of our public ABI. In recent years, the most common reason for adding new functions to ``multiarray``\'s "public" ABI is not that they really need to be public or that we expect other projects to use them, but rather just that we need to call them from ``umath``. This is extremely unfortunate, because it makes our public ABI unnecessarily large, and since we can never remove things from it then this creates an ongoing maintenance burden. The way C works, you can have internal API that's visible to everything inside the same extension module, or you can have a public API that everyone can use; you can't have an API that's visible to multiple extension modules inside numpy, but not to external users.
We've also increasingly been putting utility code into ``numpy/core/src/private/``, which now contains a bunch of files which are ``#include``\d twice, once into ``multiarray`` and once into ``umath``. This is pretty gross, and is purely a workaround for these being separate C extensions.
Proposed changes ----------------
This NEP proposes three changes:
1. We should start building ``numpy/core/src/multiarray/*.c`` and ``numpy/core/src/umath/*.c`` together into a single extension module.
2. Instead of ``set_numeric_ops``, we should use some new, private API to set up ``ndarray.__add__`` and friends.
3. We should deprecate, and eventually remove, ``np.set_numeric_ops``.
Non-proposed changes --------------------
We don't necessarily propose to throw away the distinction between multiarray/ and umath/ in terms of our source code organization: internal organization is useful! We just want to build them together into a single extension module. Of course, this does open the door for potential future refactorings, which we can then evaluate based on their merits as they come up.
It also doesn't propose that we break the public C ABI. We should continue to provide ``import_multiarray()`` and ``import_umath()`` functions – it's just that now both ABIs will ultimately be loaded from the same C library. Due to how ``import_multiarray()`` and ``import_umath()`` are written, we'll also still need to have modules called ``numpy.core.multiarray`` and ``numpy.core.umath``, and they'll need to continue to export ``_ARRAY_API`` and ``_UFUNC_API`` objects – but we can make one or both of these modules be tiny shims that simply re-export the magic API object from where-ever it's actually defined. (See ``numpy/core/code_generators/generate_{numpy,ufunc}_api.py`` for details of how these imports work.)
Backward compatibility ----------------------
The only compatibility break is the deprecation of ``np.set_numeric_ops``.
Alternatives ------------
n/a
Discussion ----------
TBD
Copyright ---------
This document has been placed in the public domain.
-- Nathaniel J. Smith -- https://vorpus.org _______________________________________________ NumPy-Discussion mailing list NumPy-Discussion@python.org https://mail.python.org/mailman/listinfo/numpy-discussion
On Thu, Mar 8, 2018 at 12:47 AM, Eric Wieser <wieser.eric+numpy@gmail.com> wrote:
This means that ndarray needs to know about ufuncs – so instead of a clean layering, we have a circular dependency.
Perhaps we should split ndarray into a base_ndarray class with no arithmetic support (add, sum, etc), and then provide an ndarray subclass from umath instead (either the separate extension, or just a different set of files)
This just seems like adding more complexity because we can, though? -n -- Nathaniel J. Smith -- https://vorpus.org
Hi, long time ago I wrote a wrapper to to use optimised and parallelized math functions from Intels vector math library geggo/uvml: Provide vectorized math function (MKL) for numpy <https://github.com/geggo/uvml> I found it useful to inject (some of) the fast methods into numpy via np.set_num_ops(), to gain more performance without changing my programs. While this original project is outdated, I can imagine that a centralised way to swap the implementation of math functions is useful. Therefor I suggest to keep np.set_num_ops(), but admittedly I do not understand all the technical implications of the proposed change. best Gregor
On Thu, Mar 8, 2018 at 2:52 AM, Gregor Thalhammer < gregor.thalhammer@gmail.com> wrote:
Hi,
long time ago I wrote a wrapper to to use optimised and parallelized math functions from Intels vector math library geggo/uvml: Provide vectorized math function (MKL) for numpy <https://github.com/geggo/uvml>
I found it useful to inject (some of) the fast methods into numpy via np.set_num_ops(), to gain more performance without changing my programs.
I think that was much of the original motivation for `set_num_ops` back in the Numeric days, where there was little commonality among platforms and getting hold of optimized libraries was very much an individual thing. The former cblas module, now merged with multiarray, was present for the same reasons.
While this original project is outdated, I can imagine that a centralised way to swap the implementation of math functions is useful. Therefor I suggest to keep np.set_num_ops(), but admittedly I do not understand all the technical implications of the proposed change.
I suppose we could set it up to detect and use an external library during compilation. The CBLAS implementations currently do that and should pick up the MKL version when available. Where are the MKL functions you used presented? That is an admittedly lower level interface, however. Chuck
On Thu, Mar 8, 2018 at 9:20 AM, Charles R Harris <charlesr.harris@gmail.com> wrote:
On Thu, Mar 8, 2018 at 2:52 AM, Gregor Thalhammer < gregor.thalhammer@gmail.com> wrote:
Hi,
long time ago I wrote a wrapper to to use optimised and parallelized math functions from Intels vector math library geggo/uvml: Provide vectorized math function (MKL) for numpy <https://github.com/geggo/uvml>
I found it useful to inject (some of) the fast methods into numpy via np.set_num_ops(), to gain more performance without changing my programs.
I think that was much of the original motivation for `set_num_ops` back in the Numeric days, where there was little commonality among platforms and getting hold of optimized libraries was very much an individual thing. The former cblas module, now merged with multiarray, was present for the same reasons.
While this original project is outdated, I can imagine that a centralised way to swap the implementation of math functions is useful. Therefor I suggest to keep np.set_num_ops(), but admittedly I do not understand all the technical implications of the proposed change.
I suppose we could set it up to detect and use an external library during compilation. The CBLAS implementations currently do that and should pick up the MKL version when available. Where are the MKL functions you used presented? That is an admittedly lower level interface, however.
Note that Intel is also working to support NumPy and intends to use the Intel optimizations as part of that. Chuck
On 08.03.2018 17:20, Charles R Harris wrote:
On Thu, Mar 8, 2018 at 2:52 AM, Gregor Thalhammer <gregor.thalhammer@gmail.com <mailto:gregor.thalhammer@gmail.com>> wrote:
Hi,
long time ago I wrote a wrapper to to use optimised and parallelized math functions from Intels vector math library geggo/uvml: Provide vectorized math function (MKL) for numpy <https://github.com/geggo/uvml>
I found it useful to inject (some of) the fast methods into numpy via np.set_num_ops(), to gain more performance without changing my programs.
I think that was much of the original motivation for `set_num_ops` back in the Numeric days, where there was little commonality among platforms and getting hold of optimized libraries was very much an individual thing. The former cblas module, now merged with multiarray, was present for the same reasons.
While this original project is outdated, I can imagine that a centralised way to swap the implementation of math functions is useful. Therefor I suggest to keep np.set_num_ops(), but admittedly I do not understand all the technical implications of the proposed change.
I suppose we could set it up to detect and use an external library during compilation. The CBLAS implementations currently do that and should pick up the MKL version when available. Where are the MKL functions you used presented? That is an admittedly lower level interface, however.
Chuck
As the functions of the different libraries have vastly different accuracies you want to be able to exchange numeric ops at runtime or at least during load time (like our cblas) and not limit yourself one compile time defined set of functions. Keeping set_numeric_ops would be preferable to me. Though I am not clear on why the two things are connected? Why can't we keep set_numeric_ops and merge multiarray and umath into one shared object?
On Fri, Mar 9, 2018 at 3:33 AM, Julian Taylor <jtaylor.debian@googlemail.com> wrote:
As the functions of the different libraries have vastly different accuracies you want to be able to exchange numeric ops at runtime or at least during load time (like our cblas) and not limit yourself one compile time defined set of functions. Keeping set_numeric_ops would be preferable to me.
Though I am not clear on why the two things are connected? Why can't we keep set_numeric_ops and merge multiarray and umath into one shared object?
I think I addressed both of these topics here? https://mail.python.org/pipermail/numpy-discussion/2018-March/077777.html Looking again now, I see that we actually *do* have an explicit API for monkeypatching ufuncs: https://docs.scipy.org/doc/numpy/reference/c-api.ufunc.html#c.PyUFunc_Replac... So this seems to be a strictly more general/powerful/useful version of set_numeric_ops... I added some discussion to the NEP: https://github.com/numpy/numpy/pull/10704/commits/4c4716ee0b3bc51d5be9baa891... -n -- Nathaniel J. Smith -- https://vorpus.org
On Thu, Mar 8, 2018 at 4:52 AM, Gregor Thalhammer <gregor.thalhammer@gmail.com> wrote:
Hi,
long time ago I wrote a wrapper to to use optimised and parallelized math functions from Intels vector math library geggo/uvml: Provide vectorized math function (MKL) for numpy
I found it useful to inject (some of) the fast methods into numpy via np.set_num_ops(), to gain more performance without changing my programs.
While this original project is outdated, I can imagine that a centralised way to swap the implementation of math functions is useful. Therefor I suggest to keep np.set_num_ops(), but admittedly I do not understand all the technical implications of the proposed change.
There may still be a case for being able to swap out the functions that do the actual work, i.e., the parts of the ufuncs that are called once any conversion to ndarray has been done. -- Marten
On Thu, Mar 8, 2018 at 1:52 AM, Gregor Thalhammer <gregor.thalhammer@gmail.com> wrote:
Hi,
long time ago I wrote a wrapper to to use optimised and parallelized math functions from Intels vector math library geggo/uvml: Provide vectorized math function (MKL) for numpy
I found it useful to inject (some of) the fast methods into numpy via np.set_num_ops(), to gain more performance without changing my programs.
While this original project is outdated, I can imagine that a centralised way to swap the implementation of math functions is useful. Therefor I suggest to keep np.set_num_ops(), but admittedly I do not understand all the technical implications of the proposed change.
The main part of the proposal is to merge the two libraries; the question of whether to deprecate set_numeric_ops is a bit separate. There's no technical obstacle to keeping it, except the usual issue of having more cruft to maintain :-). It's usually true that any monkeypatching interface will be useful to someone under some circumstances, but we usually don't consider this a good enough reason on its own to add and maintain these kinds of interfaces. And an unfortunate side-effect of these kinds of hacky interfaces is that they can end up removing the pressure to solve problems properly. In this case, better solutions would include: - Adding support for accelerated vector math libraries to NumPy directly (e.g. MKL, yeppp) - Overriding the inner loops inside ufuncs like numpy.add that np.ndarray.__add__ ultimately calls. This would speed up all addition (whether or not it uses Python + syntax), would be a more general solution (e.g. you could monkeypatch np.exp to use MKL's fast vectorized exp), would let you skip reimplementing all the tricky shared bits of the ufunc logic, etc. Conceptually it's not even very hacky, because we allow you add new loops to existing ufuncs; making it possible to replace existing loops wouldn't be a big stretch. (In fact it's possible that we already allow this; I haven't checked.) So I still lean towards deprecating set_numeric_ops. It's not the most crucial part of the proposal though; if it turns out to be too controversial then I'll take it out. -n -- Nathaniel J. Smith -- https://vorpus.org
On Thu, Mar 8, 2018 at 1:52 AM, Gregor Thalhammer <gregor.thalhammer@gmail.com> wrote:
Hi,
long time ago I wrote a wrapper to to use optimised and parallelized math functions from Intels vector math library geggo/uvml: Provide vectorized math function (MKL) for numpy
I found it useful to inject (some of) the fast methods into numpy via np.set_num_ops(), to gain more performance without changing my programs.
While this original project is outdated, I can imagine that a centralised way to swap the implementation of math functions is useful. Therefor I suggest to keep np.set_num_ops(), but admittedly I do not understand all
the
technical implications of the proposed change.
The main part of the proposal is to merge the two libraries; the question of whether to deprecate set_numeric_ops is a bit separate. There's no technical obstacle to keeping it, except the usual issue of having more cruft to maintain :-).
It's usually true that any monkeypatching interface will be useful to someone under some circumstances, but we usually don't consider this a good enough reason on its own to add and maintain these kinds of interfaces. And an unfortunate side-effect of these kinds of hacky interfaces is that they can end up removing the pressure to solve problems properly. In this case, better solutions would include:
- Adding support for accelerated vector math libraries to NumPy directly (e.g. MKL, yeppp)
I just want to bring the Sleef <https://github.com/shibatch/sleef>
2018-03-09 2:06 GMT+01:00 Nathaniel Smith <njs@pobox.com>: library for vectorized math (C99) into the discussion. Recently a new version with a stabilized API has been provided by its authors. The library is now well documented http://sleef.org and available under the permissive boost license. A runtime CPU dispatcher is used for the different SIMD variants (SSE2, AVX, AVX2, FMA ...) However, I never understand how a vectorized math library can be easily used with numpy arrays in all manners (strided arrays i.e.).
- Overriding the inner loops inside ufuncs like numpy.add that np.ndarray.__add__ ultimately calls. This would speed up all addition (whether or not it uses Python + syntax), would be a more general solution (e.g. you could monkeypatch np.exp to use MKL's fast vectorized exp), would let you skip reimplementing all the tricky shared bits of the ufunc logic, etc. Conceptually it's not even very hacky, because we allow you add new loops to existing ufuncs; making it possible to replace existing loops wouldn't be a big stretch. (In fact it's possible that we already allow this; I haven't checked.)
So I still lean towards deprecating set_numeric_ops. It's not the most crucial part of the proposal though; if it turns out to be too controversial then I'll take it out.
-n
-- Nathaniel J. Smith -- https://vorpus.org _______________________________________________ NumPy-Discussion mailing list NumPy-Discussion@python.org https://mail.python.org/mailman/listinfo/numpy-discussion
Am 09.03.2018 um 02:06 schrieb Nathaniel Smith <njs@pobox.com>:
On Thu, Mar 8, 2018 at 1:52 AM, Gregor Thalhammer <gregor.thalhammer@gmail.com <mailto:gregor.thalhammer@gmail.com>> wrote:
Hi,
long time ago I wrote a wrapper to to use optimised and parallelized math functions from Intels vector math library geggo/uvml: Provide vectorized math function (MKL) for numpy
I found it useful to inject (some of) the fast methods into numpy via np.set_num_ops(), to gain more performance without changing my programs.
While this original project is outdated, I can imagine that a centralised way to swap the implementation of math functions is useful. Therefor I suggest to keep np.set_num_ops(), but admittedly I do not understand all the technical implications of the proposed change.
The main part of the proposal is to merge the two libraries; the question of whether to deprecate set_numeric_ops is a bit separate. There's no technical obstacle to keeping it, except the usual issue of having more cruft to maintain :-).
It's usually true that any monkeypatching interface will be useful to someone under some circumstances, but we usually don't consider this a good enough reason on its own to add and maintain these kinds of interfaces. And an unfortunate side-effect of these kinds of hacky interfaces is that they can end up removing the pressure to solve problems properly. In this case, better solutions would include:
- Adding support for accelerated vector math libraries to NumPy directly (e.g. MKL, yeppp)
- Overriding the inner loops inside ufuncs like numpy.add that np.ndarray.__add__ ultimately calls. This would speed up all addition (whether or not it uses Python + syntax), would be a more general solution (e.g. you could monkeypatch np.exp to use MKL's fast vectorized exp), would let you skip reimplementing all the tricky shared bits of the ufunc logic, etc. Conceptually it's not even very hacky, because we allow you add new loops to existing ufuncs; making it possible to replace existing loops wouldn't be a big stretch. (In fact it's possible that we already allow this; I haven't checked.)
So I still lean towards deprecating set_numeric_ops. It's not the most crucial part of the proposal though; if it turns out to be too controversial then I'll take it out.
Dear Nathaniel, since you referred to your reply in your latest post in this thread I comment here. First, I agree that set_numeric_ops() is not very important for replacing numpy math functions with faster implementations, mostly because this covers only the basic operations (+, *, boolean operations), which are fast anyhow, only pow can be accelerated by a substantial factor. I also agree that adding support for optimised math function libraries directly to numpy might be a better solution than patching numpy. But in the past there have been a couple of proposals to add fast vectorised math functions directly to numpy, e.g. for a GSoC project. There have always been long discussions about maintainability, testing, vendor lock-in, free versus non-free software — all attempts failed. Only the Intel accelerated Python distribution claims that it boosted performance for transcendental functions, but I do not know how they achieved this and if this could be integrated in the official numpy. Therefor I think there is some need for an „official“ way to swap numpy math functions at the user (Python) level at runtime. As Julian commented, you want this flexibility because of speed and accuracy trade-offs. Just replacing the inner loop might be an alternative way, but I am not sure. Many optimised vector math libraries require contiguous arrays, so they don’t fulfil the expectations numpy has for an inner loop. So you would need to allocate memory, copy, and free memory for each call to the inner loop. I image this gives quite some overhead you could avoid by a completely custom ufunc. On the other hand, setting up a ufunc from inner loop functions is easy, you can reuse all the numpy machinery. I disagree with you that you have to reimplement the whole ufunc machinery if you swap math functions at the ufunc level. Stupid question: how to get the first argument of int PyUFunc_ReplaceLoopBySignature(PyUFuncObject <https://docs.scipy.org/doc/numpy/reference/c-api.types-and-structures.html#c.PyUFuncObject>* ufunc, e.g. for np.add ? So, please consider this when refactoring/redesigning the ufunc module. Gregor
-n
-- Nathaniel J. Smith -- https://vorpus.org <https://vorpus.org/> _______________________________________________ NumPy-Discussion mailing list NumPy-Discussion@python.org <mailto:NumPy-Discussion@python.org> https://mail.python.org/mailman/listinfo/numpy-discussion <https://mail.python.org/mailman/listinfo/numpy-discussion>
I think part of the problem is that ufuncs actually have two parts: a generic interface, which turns all its arguments into ndarray (or calls `__array_ufunc__`) and an ndarray-specific implementation of the given function (partially, just the iterator, partially the inner loop). The latter could logically be moved to `ndarray.__array_ufunc__` (and thus to `multiarray`). In that case, `umath` would hardly depend on `multiarray` any more. But perhaps this is a bit besides the point: building the two at the same time would go a long way to making it easier to do a move like the above. -- Marten
Hi Nathanial, I looked through the revised text at https://github.com/numpy/numpy/pull/10704 and think it covers things well; any improvements on the organisation I can think of would seem to start with doing the merge anyway (e.g., I quite like Eric Wieser's suggested base ndarray class; the additional bits that implement operators might quite easily become useful for duck arrays). One request: can it be part of the NEP to aim to document the organisation of the whole more clearly? For me at least, one of the big hurdles to trying to contribute to the C code has been the absence of a mental picture of how it all hangs together. All the best, Marten
On Thu, Mar 8, 2018 at 1:25 AM, Nathaniel Smith <njs@pobox.com> wrote:
Hi all,
Well, this is something that we've discussed for a while and I think generally has consensus already, but I figured I'd write it down anyway to make sure.
There's a rendered version here: https://github.com/njsmith/numpy/blob/nep-0015-merge- multiarray-umath/doc/neps/nep-0015-merge-multiarray-umath.rst
-----
============================ Merging multiarray and umath ============================
:Author: Nathaniel J. Smith <njs@pobox.com> :Status: Draft :Type: Standards Track :Created: 2018-02-22
Abstract --------
Let's merge ``numpy.core.multiarray`` and ``numpy.core.umath`` into a single extension module, and deprecate ``np.set_numeric_ops``.
Background ----------
Currently, numpy's core C code is split between two separate extension modules.
``numpy.core.multiarray`` is built from ``numpy/core/src/multiarray/*.c``, and contains the core array functionality (in particular, the ``ndarray`` object).
``numpy.core.umath`` is built from ``numpy/core/src/umath/*.c``, and contains the ufunc machinery.
These two modules each expose their own separate C API, accessed via ``import_multiarray()`` and ``import_umath()`` respectively. The idea is that they're supposed to be independent modules, with ``multiarray`` as a lower-level layer with ``umath`` built on top. In practice this has turned out to be problematic.
First, the layering isn't perfect: when you write ``ndarray + ndarray``, this invokes ``ndarray.__add__``, which then calls the ufunc ``np.add``. This means that ``ndarray`` needs to know about ufuncs – so instead of a clean layering, we have a circular dependency. To solve this, ``multiarray`` exports a somewhat terrifying function called ``set_numeric_ops``. The bootstrap procedure each time you ``import numpy`` is:
1. ``multiarray`` and its ``ndarray`` object are loaded, but arithmetic operations on ndarrays are broken.
2. ``umath`` is loaded.
3. ``set_numeric_ops`` is used to monkeypatch all the methods like ``ndarray.__add__`` with objects from ``umath``.
In addition, ``set_numeric_ops`` is exposed as a public API, ``np.set_numeric_ops``.
Furthermore, even when this layering does work, it ends up distorting the shape of our public ABI. In recent years, the most common reason for adding new functions to ``multiarray``\'s "public" ABI is not that they really need to be public or that we expect other projects to use them, but rather just that we need to call them from ``umath``. This is extremely unfortunate, because it makes our public ABI unnecessarily large, and since we can never remove things from it then this creates an ongoing maintenance burden. The way C works, you can have internal API that's visible to everything inside the same extension module, or you can have a public API that everyone can use; you can't have an API that's visible to multiple extension modules inside numpy, but not to external users.
We've also increasingly been putting utility code into ``numpy/core/src/private/``, which now contains a bunch of files which are ``#include``\d twice, once into ``multiarray`` and once into ``umath``. This is pretty gross, and is purely a workaround for these being separate C extensions.
Proposed changes ----------------
This NEP proposes three changes:
1. We should start building ``numpy/core/src/multiarray/*.c`` and ``numpy/core/src/umath/*.c`` together into a single extension module.
2. Instead of ``set_numeric_ops``, we should use some new, private API to set up ``ndarray.__add__`` and friends.
3. We should deprecate, and eventually remove, ``np.set_numeric_ops``.
Non-proposed changes --------------------
We don't necessarily propose to throw away the distinction between multiarray/ and umath/ in terms of our source code organization: internal organization is useful! We just want to build them together into a single extension module. Of course, this does open the door for potential future refactorings, which we can then evaluate based on their merits as they come up.
It also doesn't propose that we break the public C ABI. We should continue to provide ``import_multiarray()`` and ``import_umath()`` functions – it's just that now both ABIs will ultimately be loaded from the same C library. Due to how ``import_multiarray()`` and ``import_umath()`` are written, we'll also still need to have modules called ``numpy.core.multiarray`` and ``numpy.core.umath``, and they'll need to continue to export ``_ARRAY_API`` and ``_UFUNC_API`` objects – but we can make one or both of these modules be tiny shims that simply re-export the magic API object from where-ever it's actually defined. (See ``numpy/core/code_generators/generate_{numpy,ufunc}_api.py`` for details of how these imports work.)
Backward compatibility ----------------------
The only compatibility break is the deprecation of ``np.set_numeric_ops``.
Alternatives ------------
n/a
Discussion ----------
TBD
Copyright ---------
This document has been placed in the public domain.
If we accept this NEP, I'd like to get it done soon, preferably and the next few months, so that it is finished before we drop Python 2.7 support. That will make maintenance of the NumPy long term support release through 2019 easier. Chuck
On Mar 12, 2018 12:02, "Charles R Harris" <charlesr.harris@gmail.com> wrote: If we accept this NEP, I'd like to get it done soon, preferably and the next few months, so that it is finished before we drop Python 2.7 support. That will make maintenance of the NumPy long term support release through 2019 easier. The reason you're seeing this spurt of activity on NEPs and NEP infrastructure from people at Berkeley is that we're preparing for the upcoming arrival of full time devs on the numpy grant. (More announcements there soon.) So if it's accepted then I don't think there will be any problem getting it implemented by then. -n
On Mon, Mar 12, 2018 at 1:25 PM, Nathaniel Smith <njs@pobox.com> wrote:
On Mar 12, 2018 12:02, "Charles R Harris" <charlesr.harris@gmail.com> wrote:
If we accept this NEP, I'd like to get it done soon, preferably and the next few months, so that it is finished before we drop Python 2.7 support. That will make maintenance of the NumPy long term support release through 2019 easier.
The reason you're seeing this spurt of activity on NEPs and NEP infrastructure from people at Berkeley is that we're preparing for the upcoming arrival of full time devs on the numpy grant. (More announcements there soon.) So if it's accepted then I don't think there will be any problem getting it implemented by then.
Depends on background. Even the best developers need some time to come up to speed on a new project ... Chuck
participants (7)
-
Carl Kleffner
-
Charles R Harris
-
Eric Wieser
-
Gregor Thalhammer
-
Julian Taylor
-
Marten van Kerkwijk
-
Nathaniel Smith