#15820: Implement sequences of bounded integers
-------------------------------------+-------------------------------------
Reporter: SimonKing | Owner:
Type: enhancement | Status: needs_review
Priority: major | Milestone: sage-6.3
Component: algebra | Resolution:
Keywords: sequence bounded | Merged in:
integer | Reviewers:
Authors: Simon King | Work issues: Use mpn_* for speedup
Report Upstream: N/A | of iteration and item access
Branch: | Commit:
u/SimonKing/ticket/15820 | 735939e593c9c302ff42c4973cbfe2e48eb22644
Dependencies: | Stopgaps:
-------------------------------------+-------------------------------------
Changes (by SimonKing):
* status: needs_work => needs_review
Comment:
Using the `mpn_*` function was a very good idea. Some basic operations are
a lot faster now. Here I am repeating (and slightly extending) my earlier
benchmarks for those operations that have changed in the recent commits:
__The test bed__
{{{
sage: from sage.misc.bounded_integer_sequences import
BoundedIntegerSequence
sage: L0 = [randint(0,7) for i in range(5)]
sage: L1 = [randint(0,15) for i in range(15)]
sage: L2 = [randint(0,31) for i in range(50)]
sage: L3 = [randint(0,31) for i in range(5000)]
sage: T0 = tuple(L0); T1 = tuple(L1); T2 = tuple(L2); T3 = tuple(L3)
sage: S0 = BoundedIntegerSequence(8, L0)
sage: S1 = BoundedIntegerSequence(16, L1)
sage: S2 = BoundedIntegerSequence(32, L2)
sage: S3 = BoundedIntegerSequence(32, L3)
}}}
__Conversion list -> tuple/sequence__
{{{
sage: %timeit x = tuple(L0)
1000000 loops, best of 3: 307 ns per loop
sage: %timeit x = tuple(L1)
1000000 loops, best of 3: 369 ns per loop
sage: %timeit x = tuple(L2)
1000000 loops, best of 3: 534 ns per loop
sage: %timeit x = tuple(L3)
10000 loops, best of 3: 19.6 µs per loop
sage: %timeit x = BoundedIntegerSequence(8, L0)
1000000 loops, best of 3: 1.34 µs per loop
sage: %timeit x = BoundedIntegerSequence(16, L1)
1000000 loops, best of 3: 1.57 µs per loop
sage: %timeit x = BoundedIntegerSequence(32, L2)
100000 loops, best of 3: 2.06 µs per loop
sage: %timeit x = BoundedIntegerSequence(32, L3)
10000 loops, best of 3: 76.7 µs per loop
}}}
Conversion to a tuple seems to be roughly 4 times faster. But I don't
think this will be critical (at least not in my own applications...)
__Conversion tuple/sequence -> list__
{{{
sage: %timeit x = list(T0)
1000000 loops, best of 3: 537 ns per loop
sage: %timeit x = list(T1)
1000000 loops, best of 3: 624 ns per loop
sage: %timeit x = list(T2)
1000000 loops, best of 3: 752 ns per loop
sage: %timeit x = list(T3)
100000 loops, best of 3: 22.6 µs per loop
sage: %timeit x = list(S0)
1000000 loops, best of 3: 1.58 µs per loop
sage: %timeit x = list(S1)
100000 loops, best of 3: 2.06 µs per loop
sage: %timeit x = list(S2)
100000 loops, best of 3: 4.19 µs per loop
sage: %timeit x = list(S3)
1000 loops, best of 3: 253 µs per loop
sage: %timeit x = S0.list()
1000000 loops, best of 3: 1.05 µs per loop
sage: %timeit x = S1.list()
1000000 loops, best of 3: 1.87 µs per loop
sage: %timeit x = S2.list()
100000 loops, best of 3: 4.95 µs per loop
sage: %timeit x = S3.list()
1000 loops, best of 3: 306 µs per loop
}}}
The gap between lists and sequences strongly depends on the bound of the
sequence, which is not much of a surprise. Anyway, it is faster than
before.
__Slicing__
For step 1, short tuples are faster than short sequences, but long
sequences are faster than long tuples:
{{{
sage: timeit("x=T0[:-1]", number=100000)
100000 loops, best of 3: 479 ns per loop
sage: timeit("x=T1[:-1]", number=100000)
100000 loops, best of 3: 548 ns per loop
sage: timeit("x=T2[:-1]", number=100000)
100000 loops, best of 3: 773 ns per loop
sage: timeit("x=T3[:-1]", number=100000)
100000 loops, best of 3: 19.6 µs per loop
sage: timeit("x=S0[:-1]", number=100000)
100000 loops, best of 3: 1.75 µs per loop
sage: timeit("x=S1[:-1]", number=100000)
100000 loops, best of 3: 1.8 µs per loop
sage: timeit("x=S2[:-1]", number=100000)
100000 loops, best of 3: 1.77 µs per loop
sage: timeit("x=S3[:-1]", number=100000)
100000 loops, best of 3: 2.4 µs per loop
}}}
As before, a step different from 1 is bad for sequences. However, there is
an improvement compared with the previous timings:
{{{
sage: timeit("x=T2[:-1:2]", number=100000)
100000 loops, best of 3: 944 ns per loop
sage: timeit("x=T3[:-1:2]", number=100000)
100000 loops, best of 3: 11.8 µs per loop
sage: timeit("x=S2[:-1:2]", number=10000)
10000 loops, best of 3: 2.7 µs per loop
sage: timeit("x=S3[:-1:2]", number=10000)
10000 loops, best of 3: 52.2 µs per loop
}}}
__Accessing single items__
Bounded integer sequences can now compete with tuples, both short and
long!
{{{
sage: timeit("x=T0[1]", number=1000000)
1000000 loops, best of 3: 349 ns per loop
sage: timeit("x=T0[4]", number=1000000)
1000000 loops, best of 3: 351 ns per loop
sage: timeit("x=S0[1]", number=1000000)
1000000 loops, best of 3: 354 ns per loop
sage: timeit("x=S0[4]", number=1000000)
1000000 loops, best of 3: 355 ns per loop
sage: timeit("x=T3[1]", number=1000000)
1000000 loops, best of 3: 346 ns per loop
sage: timeit("x=T3[4500]", number=1000000)
1000000 loops, best of 3: 347 ns per loop
sage: timeit("x=S3[1]", number=1000000)
1000000 loops, best of 3: 350 ns per loop
sage: timeit("x=S3[4500]", number=1000000)
1000000 loops, best of 3: 370 ns per loop
}}}
__Containment tests__
Here we talk about operations that do not exist in this form for tuples
(e.g., subsequence tests). They have been improved by the latest commits:
{{{
sage: L0x = [randint(0,7) for i in range(5)]
sage: L1x = [randint(0,15) for i in range(15)]
sage: L2x = [randint(0,31) for i in range(50)]
sage: L3x = [randint(0,31) for i in range(5000)] # verified that they
differ from L0,L1,L2,L3
sage: S0x = BoundedIntegerSequence(8, L0x)
sage: S1x = BoundedIntegerSequence(16, L1x)
sage: S2x = BoundedIntegerSequence(32, L2x)
sage: S3x = BoundedIntegerSequence(32, L3x)
sage: S1y = BoundedIntegerSequence(16, L1)
sage: S2y = BoundedIntegerSequence(32, L2)
sage: S3y = BoundedIntegerSequence(32, L3)
sage: S1y==S1!=S1x, S1y is not S1
(True, True)
sage: S0z1 = S0+S0
sage: S0z2 = S0+S0x
sage: S1z1 = S1+S1
sage: S1z2 = S1+S1x
sage: S2z1 = S2+S2
sage: S2z2 = S2+S2x
sage: S3z1 = S3+S3
sage: S3z2 = S3+S3x
sage: timeit("S0z2.startswith(S0)", number=1000000)
1000000 loops, best of 3: 230 ns per loop
sage: timeit("S0z2.startswith(S0x)", number=1000000)
1000000 loops, best of 3: 236 ns per loop
sage: timeit("S0x in S0z2", number=1000000)
1000000 loops, best of 3: 511 ns per loop
sage: timeit("S1z2.startswith(S1)", number=1000000)
1000000 loops, best of 3: 215 ns per loop
sage: timeit("S1z2.startswith(S1x)", number=1000000)
1000000 loops, best of 3: 219 ns per loop
sage: timeit("S1x in S1z2", number=1000000)
1000000 loops, best of 3: 692 ns per loop
sage: timeit("S2z2.startswith(S2)", number=1000000)
1000000 loops, best of 3: 238 ns per loop
sage: timeit("S2z2.startswith(S2x)", number=1000000)
1000000 loops, best of 3: 228 ns per loop
sage: timeit("S2x in S2z2", number=1000000)
1000000 loops, best of 3: 1.92 µs per loop
sage: timeit("S3z2.startswith(S3)", number=1000000)
1000000 loops, best of 3: 981 ns per loop
sage: timeit("S3z2.startswith(S3x)", number=1000000)
1000000 loops, best of 3: 213 ns per loop
sage: timeit("S3x in S3z2", number=1000)
1000 loops, best of 3: 2.41 ms per loop
}}}
__Conclusion__
- Slicing and iteration are still a weak point of bounded integer
sequences. However, there is an improvement by the latest commits.
- Access to single items is now competitive. Some other operations have
been competitive or superior before.
- Probably iteration and slicing could be improved with more complicated
code: Currently, I take a single item, do a bitshift (which is faster than
before!) and then proceed with the next item. But since several items
usually fit into one "limb" (this is how GMP stores the data), it would be
possible to accumulate a group of items into one limb, then do a
''single'' bitshift for this group of items, and then proceed to the next
group of items. I would consider to implement it if my own applications
show the need for it...
Needs review, I think! Please check if I produced a memory leak (by not
freeing allocated temporary data), a memory corruption (by messing up in
the case of items that are stored exactly on the seam between two limbs),
or functions in which a memory error or keyboard interrupt would not be
propagated. I plan to do such tests, too, but I suppose 4 or 6 eyes see
more than 2.
--
Ticket URL: <http://trac.sagemath.org/ticket/15820#comment:71>
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 unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/sage-trac.
For more options, visit https://groups.google.com/d/optout.