#13170: Speedup the default nonzero test for matrices
---------------------------+------------------------------------------------
Reporter: SimonKing | Owner: tbd
Type: enhancement | Status: new
Priority: major | Milestone: sage-5.2
Component: performance | Keywords:
Work issues: | Report Upstream: N/A
Reviewers: | Authors: Simon King
Merged in: | Dependencies:
Stopgaps: |
---------------------------+------------------------------------------------
The default `__nonzero__` method for matrices as defined in
sage/matrix/matrix0.pyx is
{{{
#!python
def __nonzero__(self):
z = self._base_ring(0)
cdef Py_ssize_t i, j
for i from 0 <= i < self._nrows:
for j from 0 <= j < self._ncols:
if self.get_unsafe(i,j) != z:
return True
return False
}}}
This default implementation is actually used, over some fields. But it
turns out that the default could be considerably improved. Let's define
three alternative methods, and compare with the default:
{{{
#!python
from sage.matrix.matrix0 cimport Matrix
from sage.structure.element cimport Element
def my_first_bool(Matrix M):
cdef Py_ssize_t i, j
for i from 0 <= i < M._nrows:
for j from 0 <= j < M._ncols:
if M.get_unsafe(i,j):
return True
return False
def my_second_bool(Matrix M):
cdef Py_ssize_t i, j
# avoid some overhead: zero_element may be faster than ...(0)
cdef Element z = M._base_ring.zero_element()
for i from 0 <= i < M._nrows:
for j from 0 <= j < M._ncols:
if M.get_unsafe(i,j)!=z:
return True
return False
def my_third_bool(Matrix M):
cdef Py_ssize_t i, j
cdef Element z = M._base_ring.zero_element()
for i from 0 <= i < M._nrows:
for j from 0 <= j < M._ncols:
# Try to skip coercion
if z._cmp_(M.get_unsafe(i,j)) is not 0:
return True
return False
def default_bool(Matrix M):
cdef Py_ssize_t i, j
z = M._base_ring(0)
for i from 0 <= i < M._nrows:
for j from 0 <= j < M._ncols:
if M.get_unsafe(i,j)!=z:
return True
return False
}}}
__'''GF(2)'''__
A 5- to 10-fold speed-up is possible:
{{{
sage: K = GF(2)
sage: M = random_matrix(K,1000,1000)
sage: my_first_bool(M) == my_second_bool(M) == my_third_bool(M) ==
default_bool(M) == bool(M)
True
sage: %timeit my_first_bool(M)
625 loops, best of 3: 373 ns per loop
sage: %timeit my_second_bool(M)
625 loops, best of 3: 653 ns per loop
sage: %timeit my_third_bool(M)
625 loops, best of 3: 2.74 µs per loop
sage: %timeit default_bool(M)
625 loops, best of 3: 4.33 µs per loop
sage: %timeit bool(M)
625 loops, best of 3: 4.94 µs per loop
sage: M*=0
sage: my_first_bool(M) == my_second_bool(M) == my_third_bool(M) ==
default_bool(M) == bool(M)
True
sage: %timeit my_first_bool(M)
25 loops, best of 3: 16.7 ms per loop
sage: %timeit my_second_bool(M)
5 loops, best of 3: 73.2 ms per loop
sage: %timeit my_third_bool(M)
5 loops, best of 3: 365 ms per loop
sage: %timeit default_bool(M)
5 loops, best of 3: 75.9 ms per loop
sage: %timeit bool(M)
5 loops, best of 3: 73.2 ms per loop
}}}
__'''GF(4)'''__
A 10- to 20-fold speed-up is possible:
{{{
sage: K = GF(4,'z')
sage: M = random_matrix(K,1000,1000)
sage: my_first_bool(M) == my_second_bool(M) == my_third_bool(M) ==
default_bool(M) == bool(M)
True
sage: %timeit my_first_bool(M)
625 loops, best of 3: 499 ns per loop
sage: %timeit my_second_bool(M)
625 loops, best of 3: 1.51 µs per loop
sage: %timeit my_third_bool(M)
625 loops, best of 3: 2.36 µs per loop
sage: %timeit default_bool(M)
625 loops, best of 3: 9.59 µs per loop
sage: %timeit bool(M)
625 loops, best of 3: 10.3 µs per loop
sage: M*=0
sage: my_first_bool(M) == my_second_bool(M) == my_third_bool(M) ==
default_bool(M) == bool(M)
True
sage: %timeit my_first_bool(M)
25 loops, best of 3: 30.1 ms per loop
sage: %timeit my_second_bool(M)
5 loops, best of 3: 466 ms per loop
sage: %timeit my_third_bool(M)
5 loops, best of 3: 845 ms per loop
sage: %timeit default_bool(M)
5 loops, best of 3: 482 ms per loop
sage: %timeit bool(M)
5 loops, best of 3: 473 ms per loop
}}}
__'''GF(3)'''__
Sage already uses a specialised implementation of `__nonzero__`:
{{{
sage: K = GF(3)
sage: M = random_matrix(K,1000,1000)
sage: my_first_bool(M) == my_second_bool(M) == my_third_bool(M) ==
default_bool(M) == bool(M)
True
sage: %timeit default_bool(M)
625 loops, best of 3: 33.5 µs per loop
sage: %timeit bool(M)
625 loops, best of 3: 566 ns per loop
sage: M*=0
sage: %timeit default_bool(M)
# Interrupted after a few MINUTES!!
sage: %timeit bool(M)
125 loops, best of 3: 4.23 ms per loop
}}}
__'''GF(25)'''__
A 20- to 40-fold speed-up is possible:
{{{
sage: K = GF(25,'z')
sage: M = random_matrix(K,1000,1000)
sage: my_first_bool(M) == my_second_bool(M) == my_third_bool(M) ==
default_bool(M) == bool(M)
True
sage: %timeit my_first_bool(M)
625 loops, best of 3: 408 ns per loop
sage: %timeit my_second_bool(M)
625 loops, best of 3: 2.44 µs per loop
sage: %timeit my_third_bool(M)
625 loops, best of 3: 3.47 µs per loop
sage: %timeit default_bool(M)
625 loops, best of 3: 20.6 µs per loop
sage: %timeit bool(M)
625 loops, best of 3: 22.1 µs per loop
sage: M*=0
sage: my_first_bool(M) == my_second_bool(M) == my_third_bool(M) ==
default_bool(M) == bool(M)
True
sage: %timeit my_first_bool(M)
25 loops, best of 3: 16.8 ms per loop
sage: %timeit my_second_bool(M)
5 loops, best of 3: 456 ms per loop
sage: %timeit my_third_bool(M)
5 loops, best of 3: 819 ms per loop
sage: %timeit default_bool(M)
5 loops, best of 3: 446 ms per loop
sage: %timeit bool(M)
5 loops, best of 3: 454 ms per loop
}}}
__'''Polynomial rings'''__
Over more exotic base rings, a 6- to 40-fold speed-up is possible
{{{
sage: K.<x,y> = QQ[]
sage: M = random_matrix(K,100,100)
sage: my_first_bool(M) == my_second_bool(M) == my_third_bool(M) ==
default_bool(M) == bool(M)
True
sage: %timeit my_first_bool(M)
625 loops, best of 3: 374 ns per loop
sage: %timeit my_second_bool(M)
625 loops, best of 3: 1.03 µs per loop
sage: %timeit my_third_bool(M)
625 loops, best of 3: 758 ns per loop
sage: %timeit default_bool(M)
625 loops, best of 3: 14.8 µs per loop
sage: %timeit bool(M)
625 loops, best of 3: 15.4 µs per loop
sage: M*=0
sage: my_first_bool(M) == my_second_bool(M) == my_third_bool(M) ==
default_bool(M) == bool(M)
True
sage: %timeit my_first_bool(M)
625 loops, best of 3: 176 µs per loop
sage: %timeit my_second_bool(M)
625 loops, best of 3: 764 µs per loop
sage: %timeit my_third_bool(M)
125 loops, best of 3: 3.56 ms per loop
sage: %timeit default_bool(M)
625 loops, best of 3: 825 µs per loop
sage: %timeit bool(M)
625 loops, best of 3: 848 µs per loop
}}}
__'''Conclusion'''__
Of course, a custom `__nonzero__` method using special C-functions of the
matrix backend (as in the case of GF(3)) would be best. However, the above
timings suggest to change the default implementation of `__nonzero__` as I
do in my patch.
--
Ticket URL: <http://trac.sagemath.org/sage_trac/ticket/13170>
Sage <http://www.sagemath.org>
Sage: Creating a Viable Open Source Alternative to Magma, Maple, Mathematica,
and MATLAB
--
You received this message because you are subscribed to the Google Groups
"sage-trac" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/sage-trac?hl=en.