[Cython] class versus struct...optimization

Brian Blais bblais at gmail.com
Fri May 8 01:10:23 CEST 2009


Hello,

I am trying to write a neuron simulator, and I have the need for  
speed.  :)  Unfortunately, I seem to be taking a serious performance  
hit by writing things as classes, as opposed to c-structs.  I am  
including the code below (I hope it's not too much) of the smallest  
meaningful example I could write (although it still doesn't do  
anything real. :)  ).  The first is an all-python version, which is  
easy to follow but slow.

In [1]:%timeit py1.runit()
10 loops, best of 3: 606 ms per loop


The second is a cython version, which makes extension classes to  
replace the python ones, and I get a nice speed up of about 30x:

In [2]:%timeit cpy2.runit()
10 loops, best of 3: 20.9 ms per loop


The final one is ugly code, making an array of structures instead of  
lists of class instances.  Where I would have more classes in the  
second version, I'd pile those variables all into this one big struct  
so I can make an array of them easily.  It's ugly, but much faster:

In [3]:%timeit cpy3.runit()
1000 loops, best of 3: 1.29 ms per loop


My question is, can I have the best of both worlds (i.e. am I doing  
something obviously stupid), or is this the price for cleaner object- 
oriented code?  Are the the method look-ups killing me in the object- 
oriented way?  In the larger code I am working on, the difference  
between the class versus struct versions is about a factor of 5, not  
the factor of 20 above, but there is more going on.  Still, a factor  
of 5 makes a big difference if the simulation may take a day to run!

thanks,

		Brian Blais


--
Brian Blais
bblais at gmail.com
http://web.bryant.edu/~bblais


#================================================
#==================PYTHON VERSION================
#================================================
from numpy import ones,zeros,prod

class Neuron(object):
     """docstring for Neuron"""
     def __init__(self, N):
         super(Neuron, self).__init__()
         self.N = N
         self.type=1
         self.shape=(N,)
         self.spikes=zeros(N,int)
         self.old_spikes=zeros(N,int)
         self.V=zeros(N)
         self.connections_to=[]

     def update(self,t):

         for c in self.connections_to:
             V=self.__getattribute__('V')
             w=c.weights
             V+=w[:,c.incell.spikes.nonzero()[0]].sum(axis=1)

         V=self.V
         self.V-=self.V/100.0

         self.spikes[:]=1.0


class Connection(object):
     """docstring for Connection"""
     def __init__(self, incell,outcell):
         super(Connection, self).__init__()
         self.incell=incell
         self.outcell=outcell
         self.shape=(outcell.N,incell.N)
         self.outcell.connections_to.append(self)
         self.weights=ones((outcell.N,incell.N),float)


     def update(self,t):
         pass


def run_sim( duration,neurons,connections):

     t=0.0
     while t<=duration:

         for n in neurons:
             n.update(t)

         # for c in connections:
         #     c.update(t)

         t+=1.0

def runit():
     n1=Neuron(5)
     n2=Neuron(3)

     c=Connection(n1,n2)

     run_sim(10000,[n1,n2],[c])




#================================================
#==================CYTHON VERSION WITH CLASSES================
#================================================

import numpy as np
cimport numpy as np


DTYPE=np.float64
ctypedef np.float64_t DTYPE_t

DTYPE_I=np.int
ctypedef np.int_t DTYPE_I_t

from numpy import ones,zeros,prod

cdef class Neuron:
     """docstring for Neuron"""

     cdef readonly int N
     cdef public np.ndarray spikes
     cdef public np.ndarray V
     cdef public object connections_to

     def __getitem__(self,key):
         return self.__getattribute__(key)


     def __init__(self, N):
         self.N = N
         self.spikes=zeros(N,int)
         self.V=zeros(N)
         self.connections_to=[]

     cdef void _update(self,double t):
         cdef int __i,__j

         cdef np.ndarray[DTYPE_I_t,ndim=1] spikes
         cdef np.ndarray[DTYPE_t,ndim=1] V
         cdef np.ndarray[DTYPE_t,ndim=2] _W
         cdef int k


         for c in self.connections_to:
             V=self.__getattribute__('V')
             _W=c.weights
             spikes=c.incell.spikes
             k=c.weights.shape[1]

             for __i in range(self.N):
                 for __j in range(k):
                     if spikes[__j]:
                         V[__i]+=_W[__i,__j]


         V=self.V
         spikes=self.spikes
         for __i in range(self.N):
             V[__i]-=V[__i]/100.0
             spikes[__i]=1


     def update(self,double t):
         self._update(t)


cdef class Connection:
     """docstring for Connection"""
     cdef public object incell,outcell
     cdef public object shape
     cdef public np.ndarray weights

     def __init__(self, incell,outcell):
         self.incell=incell
         self.outcell=outcell
         self.shape=(outcell.N,incell.N)
         self.outcell.connections_to.append(self)
         self.weights=ones((outcell.N,incell.N),float)


     def update(self,t):
         pass


def run_sim(duration,neurons,connections):

     t=0.0
     while t<=duration:

         for n in neurons:
             n.update(t)

         # for c in connections:
         #     c.update(t)

         t+=1.0


#================================================
#==================CYTHON VERSION WITH STRUCTS================
#================================================

import numpy as np
cimport numpy as np


DTYPE=np.float64
ctypedef np.float64_t DTYPE_t


cdef double* DoubleData(np.ndarray M):
     return <double *>M.data

cdef char* CharData(np.ndarray M):
     return <char *>M.data

cdef int* IntData(np.ndarray M):
     return <int *>M.data

cdef struct Neuron_Group_struct:
     int N
     int *spikes
     double *V
     int type
     int number_of_connections_to

     # hard-coded maximum numbers...yuck!
     int *c_spikes[31]
     int c_num_incell[31],c_num_outcell[31]
     double *c_weights[31]


# function to initialize the struct
cdef Neuron_Group_struct init_Neuron_Group(object self):

     cdef Neuron_Group_struct s
     s.type=self.type
     s.N=self.N
     s.spikes=IntData(self.spikes)
     s.V=DoubleData(self.V)
     s.number_of_connections_to=len(self.connections_to)

     cdef int cg

     for cg from 0<=cg<s.number_of_connections_to:
         s.c_spikes[cg]=IntData(self.connections_to[cg].incell.spikes)
         s.c_num_incell[cg]=self.connections_to[cg].incell.N
         s.c_num_outcell[cg]=self.connections_to[cg].outcell.N

         s.c_weights[cg]=DoubleData(self.connections_to[cg].weights)


cdef void Test_update(Neuron_Group_struct *s,object self,double t):
     cdef int __i,__j
     cdef int number_of_connections_to
     number_of_connections_to=s.number_of_connections_to
     cdef int *spikes
     cdef int num_incell,num_outcell
     cdef int cg,ni,no
     cdef double *weights
     cdef double *V

     V=s.V

     for cg from 0<=cg<number_of_connections_to:
         spikes=s.c_spikes[cg]
         num_incell=s.c_num_incell[cg]
         num_outcell=s.c_num_outcell[cg]

         weights=s.c_weights[cg]

         for ni from 0<=ni<num_incell:
             if spikes[ni]:
                 for no from 0<=no<num_outcell:
                    V[ni]+=weights[no+ni*num_outcell]

     V=s.V
     spikes=s.spikes
     for no from 0<=no<num_outcell:
         V[no]-=V[no]/100.0
         spikes[no]=1


cdef nupdate(Neuron_Group_struct *s,object self,double t):

     if s.type==-1:
         self.update(t)
         return

     if s.type==0: # Silent Neuron
         pass
     elif s.type==1: # Constant Fixed
         Test_update(s,self,t)


def run_sim(duration,neurons,connections):

     cdef long long t
     cdef int i
     cdef int ln,lc

     ln=len(neurons)
     lc=len(connections)

     cdef long long num_iter
     num_iter=duration

     cdef Neuron_Group_struct Sn[31]
     for i from 0<=i<ln:
         Sn[i]=init_Neuron_Group(neurons[i])

     for t from 0<=t<num_iter:

         for i from 0<=i<ln:
             nupdate(&Sn[i],neurons[i],t)



-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://codespeak.net/pipermail/cython-dev/attachments/20090507/5de3781b/attachment.htm 


More information about the Cython-dev mailing list