7
\$\begingroup\$

Today I built a linspace function in Python's C API:

static PyObject *
linspace(PyObject * self, PyObject * args)
{
    int n, i;
    double start, end;

    if (!PyArg_ParseTuple(args, "ddi:linspace", &start, &end, &n))
        return NULL;

    if (n <= 1)
        return Py_BuildValue("[d]", end);

    double h;
    PyObject *pylist = PyList_New(n);

    h = (end - start) / (n - 1);

    for (i = 0; i < n; i++)
        PyList_SetItem(pylist, i, Py_BuildValue("d", (start + h * i)));

    return Py_BuildValue("O", pylist);
}

It behaves how I would like it to behave, however, when I benchmarked it against NumPy's linspace it was slower by about a factor of 80.

I have a few questions that I think may be affecting performance, but I can't seem to find help online:

  • Is there a memory leak? Or am I not incrementing or decrementing any references that I should be?
  • Can I do this with a C double array and then return that as a Python Object? Would this even be faster (I think it may)?
  • Am I missing something? I am new to the C API and I am not confident in it yet.
\$\endgroup\$

2 Answers 2

3
\$\begingroup\$
  1. I can't see any obvious memory leaks. If you're worried, then you might start out by seeing what sys.getrefcount tells you.

  2. You will need to package up your array-of-doubles as a new type of Python object. See section 2 of the Extending/Embedding manual.

  3. Since you know that you are creating float objects, you could speed things up slightly by using PyFloat_FromDouble instead of the generic Py_BuildValue (which has to parse its first argument and then dispatch). But this is not going to beat NumPy, because packaging up an array-of-numbers as a new type of Python object is exactly what NumPy does, and that's why it runs so fast: it doesn't have to allocate a new Python object for every position in the list.

\$\endgroup\$
2
\$\begingroup\$

There's one memory leak:

return Py_BuildValue("O", pylist);

pylist has a reference count of 1 before this line and Py_BuildValue increment it to 2. Subsequent code will only ever reduce it to 1, so it never gets freed. Instead just do

return pylist;

An efficient implementation using on Python built-in types would probably use the array module instead of a list and a memoryview to access it. I'd expect this to be comparable in speed with numpy.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.