Saturday, May 9, 2009

Python's Greenlet/Linden Lab's Eventlet

Have you guys used Python's greenlet or eventlet before? Its pretty cool and i thought i write something about it. So here's a little experiment i did using a couple of Python's module i.e. optparse, greenlet, eventlet. I have also did some simple measuring to see what was causing latencies etc.

What i did was basically, collected statistics on running a computation against the standard matrix multiplication which is highly parallel. There are 3 programs i created using the normal iterative-approach i.e. for-loop, greenlet and lastly eventlet implementation.

Overall, i find that greenlet is much more suitable to eventlet (w.r.t time) and the iterative-approach (w.r.t flexibility and elegance). I include my scripts below at the end of this post.

Here's how i executed and profiled my script:

ray:~ ray$ python -m cProfile -o eventletprof_10 ./myEventletDemo.py
Sum of matrix multiplication: 285

ray:~ ray$ python -m cProfile -o eventletprof_100 ./myEventletDemo.py --i 100
Sum of matrix multiplication: 328350

ray:~ ray$ python -m cProfile -o eventletprof_1000 ./myEventletDemo.py --i 1000
Sum of matrix multiplication: 332833500

ray:~ ray$ python -m cProfile -o eventletprof_10000 ./myEventletDemo.py --i 10000
Sum of matrix multiplication: 333283335000

ray:~ ray$ python -m cProfile -o eventletprof_100000 ./myEventletDemo.py --i 100000
Sum of matrix multiplication: 333328333350000
...
...
ray:~ ray$ python -m cProfile -o noneventletprof_10 ./myNoneventletDemo.py
iteration version of matrix add: 285

ray:~ ray$ python -m cProfile -o noneventletprof_100 ./myNoneventletDemo.py --i 100
iteration version of matrix add: 328350

ray:~ ray$ python -m cProfile -o noneventletprof_1000 ./myNoneventletDemo.py --i 1000
iteration version of matrix add: 332833500

ray:~ ray$ python -m cProfile -o noneventletprof_10000 ./myNoneventletDemo.py --i 10000
iteration version of matrix add: 333283335000

ray:~ ray$ python -m cProfile -o noneventletprof_100000 ./myNoneventletDemo.py --i 100000
iteration version of matrix add: 333328333350000
...
...
ray:~ ray$ python -m cProfile -o greenletprof_10 ./myGreenletDemo.py
iteration version of matrix add: 285

ray:~ ray$ python -m cProfile -o greenletprof_100 ./myGreenletDemo.py --i 100
iteration version of matrix add: 328350

ray:~ ray$ python -m cProfile -o greenletprof_1000 ./myGreenletDemo.py --i 1000
iteration version of matrix add: 332833500

ray:~ ray$ python -m cProfile -o greenletprof_10000 ./myGreenletDemo.py --i 10000
iteration version of matrix add: 333283335000

ray:~ ray$ python -m cProfile -o greenletprof_100000 ./myGreenletDemo.py --i 100000
iteration version of matrix add: 333328333350000


What profiling does is to output the results into a file (e.g. greenletprof_100000) and you can view the statistics in the Python interpreter using the following:

Python 2.5.1 (r251:54863, Jan 13 2009, 10:26:13)
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pstats
>>> profiledData = pstats.Stats('greenletprof_100000')
>>> profiledData.print_stats()
And you'll see the statistics printed in much detail. However, this is rather tedious so i would suggest a graphical UI to view the profiled data instead and one such option is RunSnakeRun.

Comparing the statistics in detail between the different runs reveals that the iterative approach is the most efficient followed by Greenlet and last eventlet. Here are the statistics in detail:

>>> import pstats
>>> stats10 = pstats.Stats('nongreenleteventlet100000.profile')
>>> stats10.print_stats()
Sun May 10 22:30:16 2009 nongreenleteventlet100000.profile

6 function calls in 0.069 CPU seconds

Random listing order was used

ncalls tottime percall cumtime percall filename:lineno(function)
1 0.060 0.060 0.069 0.069 /home/tayboonl/Desktop/Greenlet_Eventlet/nongreenleteventletdemo.py:8(execMatrixAdd_Iter)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.000 0.000 0.069 0.069 :1()
3 0.009 0.003 0.009 0.003 {range}

...
>>> stats10 = pstats.Stats('greenlet100000.profile')
>>> stats10.print_stats()
Sun May 10 22:16:45 2009 greenlet100000.profile

500011 function calls in 1.511 CPU seconds

....
>>> stats10 = pstats.Stats('eventlet100000.profile')
>>> stats10.print_stats()
Sun May 10 22:20:17 2009 eventlet100000.profile

6500027 function calls (5800031 primitive calls) in 34.901 CPU seconds

You might have noticed the remarkable number of functions calls made in the scenario using Eventlet. It suffice at this point for me to say is that Eventlet was not designed for high performance computing w.r.t Greenlet since it was designed to be a asynchronous networking library and to me, it makes alot of sense to always profile the application or framework before using it on a wide basis.

In case you are interested, here are the scripts i used
All together, there are 3 in total first being that implemented using Greenlet (Cool stuff!)

#!/usr/bin/python

import greenlet
import optparse

tasks = []
accum = list()
sum = 0
vecA = vecB = None

def mulNAccum(idx, val1, val2) :
global accum
accum.insert(idx, val1*val2)
#print idx,val1*val2,accum

def createTasks(HowMany):
global vecA, vecB
vecA = range(0, HowMany)
vecB = range(0, HowMany)
for i in range(0,HowMany):
tasks.append(greenlet.greenlet(run=mulNAccum,parent=greenlet.getcurrent()))

def executeTasks(HowMany):
global tasks
global accum
global sum

for i in range(0,HowMany):
tasks[i].switch(i, vecA[i],vecB[i])
#print "tasks finished executing ...\n"
# print accum
for i in range(0, len(accum)):
sum = sum + accum[i]

print "Sum of matrix multiplication: %d\n" % sum

if __name__ == "__main__":
parser = optparse.OptionParser()
parser.add_option("--items", "-i", default=10, action="store", type="int", dest="HowMany", help="number of elements in matrix array to process")
(options, args) = parser.parse_args()

createTasks(options.HowMany)
executeTasks(options.HowMany)

Similar program using the for-loop a.k.a iterative approach

#!/usr/bin/python

import optparse

sum = 0
vecA = vecB = None

def execMatrixAdd_Iter(HowMany):
global vecA, vecB, sum
vecA = range(0, HowMany)
vecB = range(0, HowMany)
for i in range(0, HowMany):
temp = vecA[i] * vecB[i]
sum = temp + sum
print "iteration version of matrix add: %d\n" % sum


if __name__ == "__main__":
parser = optparse.OptionParser()
parser.add_option("--items", "-i", default=10, action="store", type="int", dest="HowMany", help="number of elements in matrix array to process")
(options, args) = parser.parse_args()
execMatrixAdd_Iter(options.HowMany)

Last script using Linden Lab's eventlet

#!/usr/bin/python

import eventlet
import eventlet.api
import optparse

tasks = []
accum = list()
sum = 0
vecA = vecB = None

def mulNAccum(idx, val1, val2) :
global accum
accum.insert(idx, val1*val2)
#print idx,val1*val2,accum

def createTasks(HowMany):
global vecA, vecB
vecA = range(0, HowMany)
vecB = range(0, HowMany)
for i in range(0,HowMany):
tasks.append(eventlet.api.spawn(mulNAccum, i, vecA[i-1], vecB[i-1]))

def executeTasks(HowMany):
global tasks
global accum
global sum

for i in range(0,HowMany):
tasks[i].switch()
#print "tasks finished executing ...\n"
# print accum
for i in range(0, len(accum)):
sum = sum + accum[i]

print "Sum of matrix multiplication: %d\n" % sum

if __name__ == "__main__":
parser = optparse.OptionParser()
parser.add_option("--items", "-i", default=10, action="store", type="int", dest="HowMany", help="number of elements in matrix array to process")
(options, args) = parser.parse_args()

createTasks(options.HowMany)
executeTasks(options.HowMany)





0 comments: