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

Reply via email to