Repository: numpy/numpy

Issue:
einsum in numpy 1.15 is almost 30 times slower than numpy 1.14!
<!-- Please describe the issue in detail here, and fill in the fields below -->

einsum is significantly slower in numpy 1.15 (when compared to numpy 1.14). Could be related to [#11686](https://github.com/numpy/numpy/issues/11686). I am not sure... I am using python 3.7.0 in Arch Linux.

I realize that 1.15 does bring some improvements to einsum in general; but, for this specific code, it is very slow!

### Reproducing code example:

<!-- A short code example that reproduces the problem/missing feature. It should be
self-contained, i.e., possible to run as-is via 'python myproblem.py' -->

```python
import numpy as np
import time

print(np.version.version)

b = np.random.random((5, 2))
t = np.random.random((5, 5, 2))
p = np.random.random((2, 5))

t1 = time.time()
for _ in range(int(1e6)):
    out = np.einsum('ij,ixy,ji->xy', b, t, p)
t2 = time.time()
print(t2-t1)
```

<!-- Writing the output from my computer below -->

### Output 1:
1.15.0
129.36364936828613

### Output 2:
1.14.5
4.071631908416748



Discussion:
Verified. @dgasmith Thoughts?
With `optimize=False`, one regains the factor 30 in speed. 

For two matrices, things are still OK, so it just seems we need to be more conservative with using `optimize` (or, better, optimize the optimization!).
`optimize_arg = kwargs.pop('optimize', len(operands) > 3)` reads like the optimization is reenabled in 1.15, was that actually intentional? I don't recall what the problems where with the differences. One could try to estimate the problem size and skip optimization for small ones, but at least doing it in python, I wouldn't be so sure it can manage, the factor is so large because the problem is so small of course.

EDIT: That reads mangled up. What I meant is, if we try to optimize the problem size in python, it might end up eating almost as much time as it does not (I assume it will drop to some kind of cache fast anyway here). The arrays here seem pretty small, so overheads might add up fast.

EDIT2: OK nvm, we have no cache right now, maybe that could be an option if this is a problem? Doesn't help on the first run, but if you count a few µs as expensive, you probably are doing many of them.
Hmm, that was #10359. The default was made `optimize=False` in #10403, but only for 1.14.x, might want to revisit that and disable it by default in master.  @mhvk any reason not to leave optimization disabled by default? Anyone currently using 1.14 has to set optimize if they want it, so nothing is lost by continuing that.
#10359 was just a quick fix for the two-argument case (and thus for `matmul`) - I have no objection to leaving the default `False`, but I think we had quite a lot more discussion elsewhere. I guess the problem is that that never led to a conclusion and hence the quick fix was all that remained!
Well, I am not sure I am worried about the slowdown itself here, one can possibly argue or optimize it away. OTOH, I am not sure how many other inconsistencies might still be around, so not enabling `optimize` as default yet is maybe simply the safe bet.

As such, I like changing the default, just wonder if there needs to be another iteration to smooth out things/optimize these small array cases.

Resolving patch:
diff --git a/numpy/core/einsumfunc.py b/numpy/core/einsumfunc.py
--- a/numpy/core/einsumfunc.py
+++ b/numpy/core/einsumfunc.py
@@ -1319,8 +1319,8 @@ def einsum(*operands, **kwargs):
 
     """
 
-    # Grab non-einsum kwargs; never optimize 2-argument case.
-    optimize_arg = kwargs.pop('optimize', len(operands) > 3)
+    # Grab non-einsum kwargs; do not optimize by default.
+    optimize_arg = kwargs.pop('optimize', False)
 
     # If no optimization, run pure einsum
     if optimize_arg is False:


Test patch: