1

I am working on learning Cython (See How to Cythonize a Python Class with an attribute that is a function?)

I have the following function that I want to speed up, but Cython says both lines are going through python:

cdef selectindex(float fitnesspref):
    r = np.random.rand()
    return int(log(r) / log(fitnesspref))

So I need to get a random number (I originally just used random() built in Python call, but switched to Numpy hoping it was faster or would Cythonize better. Then I need to take the log of two floats, divide them, and then return the result back as an integer. Nothing too difficult, and should be a great question that people can then use later. I'm struggling to find simple solutions to all the info I need via the Cython docs or Google. I would have thought this was like in every tutorial, but I'm not having a lot of luck.

Looking around though, I can't find an nice easy solution. For example: Canonical way to generate random numbers in Cython

Is there a best, but simpler, way to Cythonize a function like this?

As a side note, I had a similar problem with abs() but then after I changed everything to be cdefs it seemed to automatically switch to using the C version.

Here is the results of the HTML generated by Cython: enter image description here

Another common one I keep bumping into is:

start = time.time()

i.e. is there a simple way to get start and end times using C code to speed that up? I have that line there in an inner loop, so it's slowing things down. But it's really important to what I'm trying to do.

Update 1: I'm trying to follow the suggestions in the comments and they don't seem to work out like I'd expect (which is why this all confuses me in the first place.) For example, here is a c version of random that I wrote and compiled. Why is it still yellow? enter image description here

Update 2: Okay, I researched it further and here is why it won't compile all the way to C:

enter image description here

It's doing a check to make sure I'm not dividing by zero, even though it's a constant and can't ever be zero. How do you get rid of that?

Bruce Nielson
  • 753
  • 8
  • 23
  • Thanks, Sachin, I didn't know you could do that. I'll use that from now on. – Bruce Nielson Jul 14 '19 at 20:48
  • 3
    For the most part you just want to find the equivalent function in the C standard library and use that. The question you linked to about random numbers was slightly more complicated because they were looking to generate numbers safely in multiple threads, but if you don't care about that there's a simple C standard library function for that too – DavidW Jul 15 '19 at 08:59
  • 1
    As a side note, you should probably also set a return type on `selectindex` (i.e. use `cdef int selectindex(float fitnesspref):` instead. – CodeSurgeon Jul 15 '19 at 19:35
  • And don't forget about adding a type to r once you find that C function to generate the random number! – CodeSurgeon Jul 15 '19 at 19:37
  • Thanks for the ideas. See my updated section at the bottom. I can't even get a basic randomize c function to turn white. I honestly don't see what I'm doing wrong. – Bruce Nielson Jul 17 '19 at 01:42
  • 1
    @BruceNielson With respect to zero division: 1) it's probably a non-issue - the C compiler will see it's a constant and skip the branch that can never happen - so don't worry too much about the yellow; 2) The [`cdivision` compiler argument](https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html#compiler-directives) will turn it off. As ever you should apply the compiler arguments as locally as possible (i.e. function decorator or `with` statement), but it's worth looking through them to see what you can disable – DavidW Jul 17 '19 at 15:05

1 Answers1

2

For anyone that follows me, here were the final answers:

Many common functions, including len() are already built in. If you switch it to use carrays it automatically compiles to C. See this link.

For the rest, the following imports were required:

from libc.stdlib cimport rand, RAND_MAX
from libc.math cimport log

To replace calls to random.random():

@cython.cdivision(True)
cdef float crandom() except -1: 
    return  <float>rand() / <float>RAND_MAX

To replace calls to random.randint():

@cython.cdivision(True)
cdef int crandint(int lower, int upper) except -1:
    return (rand() % (upper - lower + 1)) + lower

My selectindex function got rewritten as:

cdef int selectindex(float fitnesspref, int popsize) except -1:
    cdef float r
    cdef int val
    r = crandom() + 0.00000000001
    return <int>(log(r) / log(fitnesspref))

That 0.00000000001 was necessary because C and Python behave slightly differently here. The Python version never returns a straight up zero apparently out of the random calls but the crandom does every so often. And a log of zero is undefined. This might be because my c version is only working with a limited number of ints as it's starting point.

I never did come up with a way to replace time.time().

I hope this helps some newbie following after me.

Bruce Nielson
  • 753
  • 8
  • 23