Brian, in the Cython version, class Neuron, do this: instead of "def
update(...)", please write "cpdef update(...)" and time your code
again. If this make it faster (it should be near the C-struct
version), please consider first removing your original "def update",
and replace your "cdef _update()" by "cpdef update()".


On Thu, May 7, 2009 at 8:10 PM, Brian Blais <[email protected]> wrote:
> 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
> [email protected]
> 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)
>
>
>
> _______________________________________________
> Cython-dev mailing list
> [email protected]
> http://codespeak.net/mailman/listinfo/cython-dev
>
>



-- 
Lisandro Dalcín
---------------
Centro Internacional de Métodos Computacionales en Ingeniería (CIMEC)
Instituto de Desarrollo Tecnológico para la Industria Química (INTEC)
Consejo Nacional de Investigaciones Científicas y Técnicas (CONICET)
PTLC - Güemes 3450, (3000) Santa Fe, Argentina
Tel/Fax: +54-(0)342-451.1594
_______________________________________________
Cython-dev mailing list
[email protected]
http://codespeak.net/mailman/listinfo/cython-dev

Reply via email to