I am profiling a numpy dot product call.
numpy.dot(pseudo,pseudo)
pseudo
is a numpy array of custom objects. Defined as:
pseudo = numpy.array(
[[PseudoBinary(1), PseudoBinary(0), PseudoBinary(1)],
[PseudoBinary(1), PseudoBinary(0), PseudoBinary(0)],
[PseudoBinary(1), PseudoBinary(0), PseudoBinary(1)]])
PseudoBinary
is a class that has a custom multiply function. It ORs instead of multiplying. See below for the complete code of PseudoBinary definition.
Type:
(Pdb) pseudo.dtype
dtype('O')
According to my profiling, the pseudo dot product is about 500 times slower than a dot product using matrixes with integer values. Pointer to the profiling code is given below.
I am interested in the reasons of the slowness and if there are ways to mitigate them.
Some of the reasons of the slowness may be:
The memory layout of pseudo would not use contiguous memory. According to this, numpy uses pointers with object types. During matrix multiplication, bunch of pointer dereferences may occur instead of directly reading from contiguous memory.
Numpy multiplication may not use the optimized internal compiled implementations. (BLAS, ATLAS etc.) According to this, various conditions should hold for falling back to the optimized implementation. Using custom objects may break those.
Are there other factors in play? Any recommendations for improvement?
The starting point of all this was this question. There, the OP is looking for a “custom dot product”. An operation that visits the elements of two matrices similar to the dot product operation, but does something else than multiplying the corresponding elements of columns and rows. In an answer, I recommended a custom object that overwrites the __mul__
function. But the numpy.dot performance is very slow with that approach. The code that does the performance measurement can be seen in that answer too.
Code showing the PseudoBinary class and dot product execution.
#!/usr/bin/env python
from __future__ import absolute_import
from __future__ import print_function
import numpy
class PseudoBinary(object):
def __init__(self,i):
self.i = i
def __mul__(self,rhs):
return PseudoBinary(self.i or rhs.i)
__rmul__ = __mul__
__imul__ = __mul__
def __add__(self,rhs):
return PseudoBinary(self.i + rhs.i)
__radd__ = __add__
__iadd__ = __add__
def __str__(self):
return "P"+str(self.i)
__repr__ = __str__
base = numpy.array(
[[1, 0, 1],
[1, 0, 0],
[1, 0, 1]])
pseudo = numpy.array(
[[PseudoBinary(1), PseudoBinary(0), PseudoBinary(1)],
[PseudoBinary(1), PseudoBinary(0), PseudoBinary(0)],
[PseudoBinary(1), PseudoBinary(0), PseudoBinary(1)]])
baseRes = numpy.dot(base,base)
pseudoRes = numpy.dot(pseudo,pseudo)
print("baseRes\n",baseRes)
print("pseudoRes\n",pseudoRes)
Prints :
baseRes
[[2 0 2]
[1 0 1]
[2 0 2]]
pseudoRes
[[P3 P2 P2]
[P3 P1 P2]
[P3 P2 P2]]