Allocating lots of tiny numpy arrays is expensive compared to most other operations being performed in numba routines. Discovered during #1566, which also shows one example where this optimization caused >10x speedup: #1566 (comment). (Some part of the speedup quoted there might be from fastmath=True, which should generally be avoided in uxarray, but the majority of it is due to avoiding construction of many tiny numpy arrays.)
Here are a few examples where this occurs in uxarray right now:
_compute_gradients_on_faces (in gradient.py) constructs np.zeros(3) and result of np.cross for many pairs of faces. Each of those is a tiny numpy array being constructed.
_sort_points_by_angle (in zonal.py) constructs np.array for every point.
_small_angle_of_2_vectors (in grid/utils.py) calls np.linalg.norm, which will create a numpy array if inputs weren't a numpy array already. It doesn't have a loop internally, but it is used inside loops in other functions (see thread of issue linked above), where the inputs are just vectors with 3 components, like [ux, uy, uz], with no extra dimensions (it isn't being used in a vectorized manner).
There may be more examples throughout the code. A complete fix to this issue should attempt to find and address all of them.
Suggested fix:
- Avoid constructing tiny numpy arrays inside numba functions. For example, work with ux, uy, uz components directly, using
value = (ux**2 + uy**2 + uz**2)**0.5, instead of using value = np.linalg.norm(u).
- Perhaps add a few helper functions, e.g.
value = _numba_norm3(ux, uy, uz) instead of value = (ux**2 + uy**2 + uz**2)**0.5. That could make the changes easier to implement (closer to existing syntax) and maintain (one source of truth for the norm function, easier to avoid easy-to-miss typos like (ux**2 + uy**2 + ux**2)**0.5).
Tagging @cmdupuis3 in case you want to claim this one after it gets triaged! (I'd also be happy to do it, but would certainly end up asking for you to review any related PRs.)
Allocating lots of tiny numpy arrays is expensive compared to most other operations being performed in numba routines. Discovered during #1566, which also shows one example where this optimization caused >10x speedup: #1566 (comment). (Some part of the speedup quoted there might be from fastmath=True, which should generally be avoided in uxarray, but the majority of it is due to avoiding construction of many tiny numpy arrays.)
Here are a few examples where this occurs in uxarray right now:
_compute_gradients_on_faces(in gradient.py) constructs np.zeros(3) and result of np.cross for many pairs of faces. Each of those is a tiny numpy array being constructed._sort_points_by_angle(in zonal.py) constructs np.array for every point._small_angle_of_2_vectors(in grid/utils.py) calls np.linalg.norm, which will create a numpy array if inputs weren't a numpy array already. It doesn't have a loop internally, but it is used inside loops in other functions (see thread of issue linked above), where the inputs are just vectors with 3 components, like [ux, uy, uz], with no extra dimensions (it isn't being used in a vectorized manner).There may be more examples throughout the code. A complete fix to this issue should attempt to find and address all of them.
Suggested fix:
value = (ux**2 + uy**2 + uz**2)**0.5, instead of usingvalue = np.linalg.norm(u).value = _numba_norm3(ux, uy, uz)instead ofvalue = (ux**2 + uy**2 + uz**2)**0.5. That could make the changes easier to implement (closer to existing syntax) and maintain (one source of truth for the norm function, easier to avoid easy-to-miss typos like(ux**2 + uy**2 + ux**2)**0.5).Tagging @cmdupuis3 in case you want to claim this one after it gets triaged! (I'd also be happy to do it, but would certainly end up asking for you to review any related PRs.)