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