4

I'm using numpy v1.18.2 in some simulations, and using inbuilt functions such as np.unique, np.diff and np.interp. I'm using these functions on standard objects, i.e lists or numpy arrays.

When I checked with cProfile, I saw that these functions make a call to an built-in method numpy.core._multiarray_umath.implement_array_function and that this method accounts for 32.5% of my runtime! To my understanding this is a wrapper that performs some checks to make sure the that the arguments passed to the function are compatible with the function.

I have two questions:

  1. Is this function (implement_array_function) actually taking up so much time or is it actually the operations I'm doing (np.unique, np.diff, np.interp) that is actually taking up all this time? That is, am I misinterpreting the cProfile output? I was confused by the hierarchical output of snakeviz. Please see snakeviz output here and details for the function here.
  2. Is there any way to disable it/bypass it, since the inputs need not be checked each time as the arguments I pass to these numpy functions are already controlled in my code? I am hoping that this will give me a performance improvement.

I already saw this question (what is numpy.core._multiarray_umath.implement_array_function and why it costs lots of time?), but I was not able to understand what exactly the function is or does. I also tried to understand NEP 18, but couldn't make out how to exactly solve the issue. Please fill in any gaps in my knowledge and correct any misunderstandings. Also I'd appreciate if someone can explain this to me like I'm 5 (r/explainlikeimfive/) instead of assuming developer level knowledge of python.

Rithwik
  • 1,128
  • 1
  • 9
  • 28
  • 1
    What's there to solve? `NEP18` discusses performance. It estimates that this dispatcher adds 1 (maybe 2) microseconds per call. It's not adding 800s to your total time; the functions that pass through it take up that time (cumulatively). You can't change this behavior (except may by reverting to a much earlier version of `numpy`. – hpaulj May 24 '20 at 15:34
  • Ah, I guess it's like I thought it was, and it was actually the original functions that take up most of the time. I was confused by the snakeviz plot, because it looked like the dispatcher took up extra time. Sorry, but it wasn't exactly that there was anything to solve per se, but I needed some guidance as I wasn't sure if my understanding was correct. `NEP18` was also a bit confusing and technical for me. – Rithwik May 24 '20 at 17:36
  • I also realized that I should have posted that stats table from the snakeviz output also. I have added it to the [question](https://imgur.com/Zy4Gy73). Like you said the `tottime` is way lesser (~73 sec) and its the `cumtime` that adds up to ~800 secs. However my code does have million-to-billion calls and the total-time does come up to around a minute. It would have been nice if one could disable this behavior without having to reverting to an earlier version. So hope you understand that I was looking for some these answers and some reassurance, as my knowledge was not sufficient. – Rithwik May 24 '20 at 17:46

1 Answers1

2

All the information below is taken from NEP 18.

Is this function (implement_array_function) actually taking up so much time or is it actually the operations I'm doing (np.unique, np.diff, np.interp) that is actually taking up all this time?

As @hpaulj correctly mentioned in the comment, the overhead of the dispatcher adds 2-3 microseconds to each numpy function call. This will probably be shorten to 0.5-1 microseconds once it is implemented in C. See here.

Is there any way to disable it/bypass it

Yes, from NumPy 1.17, you can set the environment variable NUMPY_EXPERIMENTAL_ARRAY_FUNCTION to 0 (before importing numpy) and this will disable the use of implement_array_function (See here). Something like

import os
os.environ['NUMPY_EXPERIMENTAL_ARRAY_FUNCTION'] = '0'
import numpy as np

However, disabling it probably would not give you any notable performance improvement as the overhead of it is just few microseconds, and this will be the default in later numpy version too.

bingung
  • 145
  • 1
  • 6