I'm trying to speed up a piece of code that generates all possible splits of a string.
splits('foo') -> [('f', 'oo'), ('fo', 'o'), ('foo', '')]
The code for this in python is very simple:
def splits(text):
return [(text[:i + 1], text[i + 1:])
for i in range(len(text))]
Is there a way to speed this up via cython or some other means? For context, the greater purpose of this code is to find the split of a string with the highest probability.
The split() method splits a string into a list. You can specify the separator, default separator is any whitespace. Note: When maxsplit is specified, the list will contain the specified number of elements plus one.
To split the elements of a list in Python: Use a list comprehension to iterate over the list. On each iteration, call the split() method to split each string. Return the part of each string you want to keep.
Definition and Usage The split() method splits a string into an array of substrings. The split() method returns the new array. The split() method does not change the original string. If (" ") is used as separator, the string is split between words.
This isn't the sort of problem that Cython tends to help with much. It's using slicing, which ends up largely the same speed as pure Python (i.e. actually pretty good).
Using a 100 character long byte string (b'0'*100
) and 10000 iterations in timeit
I get:
cdef int i
and compiled in Cython - 0.20s (this is reproducably a small improvement. It's more significant with longer strings)cdef int i
and the parameter typed to bytes text
- 0.28s (i.e. worse).Best speed is got by using the Python C API directly (see code below) - 0.11s. I've chosen to do this mostly in Cython (but calling the API functions myself) for convenience, but you could write very similar code in C directly with a little more manual error checking. I've written this for the Python 3 API assuming you're using bytes objects (i.e. PyBytes
instead of PyString
) so if you're using Python 2, or Unicode and Python 3 you'll have to change it a little.
from cpython cimport *
cdef extern from "Python.h":
# This isn't included in the cpython definitions
# using PyObject* rather than object lets us control refcounting
PyObject* Py_BuildValue(const char*,...) except NULL
def split(text):
cdef Py_ssize_t l,i
cdef char* s
# Cython automatically checks the return value and raises an error if
# these fail. This provides a type-check on text
PyBytes_AsStringAndSize(text,&s,&l)
output = PyList_New(l)
for i in range(l):
# PyList_SET_ITEM steals a reference
# the casting is necessary to ensure that Cython doesn't
# decref the result of Py_BuildValue
PyList_SET_ITEM(output,i,
<object>Py_BuildValue('y#y#',s,i+1,s+i+1,l-(i+1)))
return output
If you don't want to go all the way with using the C API then a version that preallocates the list output = [None]*len(text)
and does a for-loop rather than a list comprehension is marginally more efficient than your original version - 0.18s
In summary, just compiling it in Cython gives you a decent speed up (a bit less than 2x) and setting the type of i
helps a little. This is all you can really achieve with Cython conventionally. To get full speed you basically need to resort to using the Python C API directly. That gets you a little under a 4x speed up which I think is pretty decent.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With