Question: It was brought to my attention that the matmul function in numpy is performing significantly worse than the dot function when multiplying array views. In this case my array view is the real part of a complex array. Here is some code which reproduces the issue: import numpy as np from timeit import timeit N = 1300 xx = np.random.randn(N, N) + 1j yy = np.random.randn(N, N) + 1J x = np.real(xx) y = np.real(yy) assert np.shares_memory(x, xx) assert np.shares_memory(y, yy) dot = timeit('np.dot(x,y)', number = 10, globals = globals()) matmul = timeit('np.matmul(x,y)', number = 10, globals = globals()) print('time for np.matmul: ', matmul) print('time for np.dot: ', dot)  On my machine the output is as follows: time for np.matmul: 23.023062199994456 time for np.dot: 0.2706864000065252  This clearly has something to do with the shared memory as replacing np.real(xx) with np.real(xx).copy() makes the performance discrepancy go away. Trolling the numpy docs was not particularly helpful as the listed differences did not discuss implementation details when dealing with memory views.

Answer: These timings indicate the dot is doing a copy with real: In [22]: timeit np.dot(xx.real,xx.real) 232 ms ± 3.34 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) In [23]: timeit np.dot(xx.real.copy(),xx.real.copy()) 232 ms ± 4.18 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)  Applying that to matmul produces nearly the same times: In [24]: timeit np.matmul(xx.real.copy(),xx.real.copy()) 231 ms ± 3.54 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)  Again, matmul with real is taking some slow route. matmul/dot both do poorer when given int arrays, though not as slow as the matmul real case. matmul/dot can also handle object dtypes, but that's even slower. So there's a lot going on under the covers that we, as python level users, don't see (and isn't documented). edit I was tempted to change the title to focus on complex-real, but decided to check another view - a slice of a float array In [42]: y=xx.real.copy()[::2,::2];y.shape,y.dtype Out[42]: ((650, 650), dtype('float64')) In [43]: timeit np.dot(y,y) 36.4 ms ± 63.4 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) In [44]: timeit np.dot(y.copy(),y.copy()) 35.6 ms ± 191 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)  Again it is evident that dot is using copies of the view. matmul does not: In [45]: timeit np.matmul(y,y) 1.89 s ± 3.01 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)  but with copies the times are the same as dot: In [46]: timeit np.matmul(y.copy(),y.copy()) 35.3 ms ± 102 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)  So my guess is that dot routinely makes a copy if it can't send the arrays directly to the BLAS routines. matmul apparently takes a slower route instead. edit While their handling of 2d arrays is similar, dot and matmul are quite different in how they handle 3+d arrays. In fact the main reason to add @ was to provide a convenient 'batch' notion to matrix multiplication. Sticking with the large complex arrays, lets make one 3x larger: In [49]: yy=np.array([xx,xx,xx]);yy.shape Out[49]: (3, 1300, 1300) In [50]: timeit np.dot(xx,xx) 794 ms ± 12.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) In [51]: timeit np.dot(xx,yy) # (yy,xx) same timings 55.5 s ± 151 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) In [52]: timeit np.matmul(xx,yy) # (yy,yy) same 2.58 s ± 362 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)  matmul has just increased the time by 3; dot by 70. I could explore things more, but not with timings in the minute range.