Repository: numpy/numpy

Issue:
searchsorted() docs misleading on relative performance
> This function is a faster version of the builtin python bisect.bisect_left (side='left') and bisect.bisect_right (side='right') functions

No, it's not faster for basic cases.

### Reproducing code example:
Python bisect is 3x faster than searchsorted:
```python
from bisect import bisect_right
from numpy import searchsorted

l = list(range(1024 * 8))
m = int(len(l)/3)
%timeit bisect_right(l, m)

a = np.asarray(l)
%timeit searchsorted(a, m)
```
```
444 ns ± 14.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
1.57 µs ± 13.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
```

For reference, numba's implementation of `searchsorted()` is a little faster than bisect:
```python
from numba import njit

@njit
def searchsorted_jit(_a, _v):
    return searchsorted(_a, _v)

searchsorted_jit(a, m)
%timeit searchsorted_jit(a, m)
```
```
391 ns ± 2.93 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
```

### Numpy/Python version information:
```
1.16.2 3.7.3 (default, Mar 27 2019, 09:23:39)
[Clang 10.0.0 (clang-1000.11.45.5)]
```

searchsorted() docs misleading on relative performance
> This function is a faster version of the builtin python bisect.bisect_left (side='left') and bisect.bisect_right (side='right') functions

No, it's not faster for basic cases.

### Reproducing code example:
Python bisect is 3x faster than searchsorted:
```python
from bisect import bisect_right
from numpy import searchsorted

l = list(range(1024 * 8))
m = int(len(l)/3)
%timeit bisect_right(l, m)

a = np.asarray(l)
%timeit searchsorted(a, m)
```
```
444 ns ± 14.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
1.57 µs ± 13.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
```

For reference, numba's implementation of `searchsorted()` is a little faster than bisect:
```python
from numba import njit

@njit
def searchsorted_jit(_a, _v):
    return searchsorted(_a, _v)

searchsorted_jit(a, m)
%timeit searchsorted_jit(a, m)
```
```
391 ns ± 2.93 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
```

### Numpy/Python version information:
```
1.16.2 3.7.3 (default, Mar 27 2019, 09:23:39)
[Clang 10.0.0 (clang-1000.11.45.5)]
```



Discussion:
Here is a more fair test with random search value.  Python bisect is still 1.5x faster.

```python
from bisect import bisect_right
from numpy import searchsorted
from random import randint
from functools import partial
from numba import njit

l = list(range(1024 * 8))
r = partial(randint, -1, len(l))
%timeit bisect_right(l, r())

a = np.asarray(l)
%timeit searchsorted(a, r())

@njit
def searchsorted_jit(_a, _v):
    return searchsorted(_a, _v)

searchsorted_jit(a, r())
%timeit searchsorted_jit(a, r())
```
```
2.17 µs ± 61.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
3.35 µs ± 26.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
2.05 µs ± 23.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
```

`searchsorted()` is faster for large arrays (e.g., 1e7 elements) or multiple search keys. But yes, bisect is quite fast. Python is often faster than NumPy for small arrays or scalars.

Note that most of what you're measuring is the overhead of a handful of Python function calls. If you want maximum performance, you should always prefer the methods on NumPy arrays, which are defined entirely in C, e.g., on my machine:
```
In [49]: %timeit np.searchsorted(a, m)
1.9 µs ± 29.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [50]: %timeit a.searchsorted(m)
566 ns ± 13.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
```

But frankly, if you care about one microsecond of overhead you shouldn't be writing your inner loop in Python, anyways :).
Thank you, I wasn't aware that method call vs. top level package call were such different paths.

I still take issue with the statement "This function is a faster version of the builtin python bisect..." however, and suggest removing "faster".  The break even point on my machine isn't until array size of 256K.
PRs with clarifications of imprecise docstrings are welcome. 
The thing were numpy would shine is when the needle has many elements, since there the complexity actually grows with N and overheads get better.
> If you want maximum performance, you should always prefer the methods on NumPy arrays

In light of this, the apparently arbitrary decision of which operations are duplicated as array methods is frustrating.   For example, there are no methods for `diff()`, `insert()`, `delete()`...
Here is a more fair test with random search value.  Python bisect is still 1.5x faster.

```python
from bisect import bisect_right
from numpy import searchsorted
from random import randint
from functools import partial
from numba import njit

l = list(range(1024 * 8))
r = partial(randint, -1, len(l))
%timeit bisect_right(l, r())

a = np.asarray(l)
%timeit searchsorted(a, r())

@njit
def searchsorted_jit(_a, _v):
    return searchsorted(_a, _v)

searchsorted_jit(a, r())
%timeit searchsorted_jit(a, r())
```
```
2.17 µs ± 61.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
3.35 µs ± 26.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
2.05 µs ± 23.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
```

`searchsorted()` is faster for large arrays (e.g., 1e7 elements) or multiple search keys. But yes, bisect is quite fast. Python is often faster than NumPy for small arrays or scalars.

Note that most of what you're measuring is the overhead of a handful of Python function calls. If you want maximum performance, you should always prefer the methods on NumPy arrays, which are defined entirely in C, e.g., on my machine:
```
In [49]: %timeit np.searchsorted(a, m)
1.9 µs ± 29.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [50]: %timeit a.searchsorted(m)
566 ns ± 13.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
```

But frankly, if you care about one microsecond of overhead you shouldn't be writing your inner loop in Python, anyways :).
Thank you, I wasn't aware that method call vs. top level package call were such different paths.

I still take issue with the statement "This function is a faster version of the builtin python bisect..." however, and suggest removing "faster".  The break even point on my machine isn't until array size of 256K.
PRs with clarifications of imprecise docstrings are welcome. 
The thing were numpy would shine is when the needle has many elements, since there the complexity actually grows with N and overheads get better.
> If you want maximum performance, you should always prefer the methods on NumPy arrays

In light of this, the apparently arbitrary decision of which operations are duplicated as array methods is frustrating.   For example, there are no methods for `diff()`, `insert()`, `delete()`...

Resolving patch:
diff --git a/numpy/core/fromnumeric.py b/numpy/core/fromnumeric.py
--- a/numpy/core/fromnumeric.py
+++ b/numpy/core/fromnumeric.py
@@ -1279,7 +1279,7 @@ def searchsorted(a, v, side='left', sorter=None):
     As of NumPy 1.4.0 `searchsorted` works with real/complex arrays containing
     `nan` values. The enhanced sort order is documented in `sort`.
 
-    This function is a faster version of the builtin python `bisect.bisect_left`
+    This function uses the same algorithm as the builtin python `bisect.bisect_left`
     (``side='left'``) and `bisect.bisect_right` (``side='right'``) functions,
     which is also vectorized in the `v` argument.
 


Test patch: