Gevent
稲田 直哉 (INADA Naoki)
source
http://github.com/methane/pyconjp2012-gevent-slide/
slide
http://methane.github.com/pyconjp2012-gevent-slide/en.html
Easy and efficient IO multiplexing
Working on conccurrnt IO.
blocks entire thread when can't process IO immediate.
Make threads to multiplex blocking IO.
returns error without blocking when can't process IO immediate.
Only works with single client
import socket def echo(sock): try: while True: data = sock.recv(1024) # blocks until recieve data. if not data: break sock.sendall(data) # block until send buffer is not full. finally: sock.close() def serve(addr): sock = socket.socket() sock.bind(addr); sock.listen(50) while True: conn, _ = sock.accept() # block untile client comes. echo(conn) # doesn't return until client disconnect. serve(('0.0.0.0', 4000))
Wrap with thread. Very easy.
import socket, threading def echo(sock): try: while True: data = sock.recv(1024) if not data: break sock.sendall(data) finally: sock.close() def serve(addr): sock = socket.socket() sock.bind(addr); sock.listen(50) while True: conn, _ = sock.accept() threading.Thread(target=echo, args=(conn,)).start() serve(('0.0.0.0', 4000))
Using select manually is very hard
#... def on_readable(self): while True: conn, _ = self.sock.accept() EchoHandler(conn) #... def on_readable(self): try: data = self.sock.recv(4096) if not data: self.close() return self.buf.append(data) finally: self._update() #...
It makes event driven programming easy
from tornado import ioloop, iostream from tornado.netutil import TCPServer class EchoServer(TCPServer): def handle_stream(self, stream, addr): stream.read_until_close( lambda _: stream.close(), # when closed connection. stream.write, # when recieved data. ) def serve(addr): server = EchoServer() server.listen(addr[1], addr[0]) ioloop.IOLoop.instance().start() serve(('', 4000))
echo server with Gevent
Gevent vs Threading
Gevent vs Tornado
from gevent.server import StreamServer def handler(sock, addr): try: while 1: buf = sock.recv(4096) if not buf: return sock.sendall(buf) finally: sock.close() def serve(addr): server = StreamServer(addr, handler, backlog=1024) server.serve_forever() serve(('', 4000))
import gevent.monkey; gevent.monkey.patch_all() import socket, threading def echo(sock): try: while True: data = sock.recv(1024) if not data: break sock.sendall(data) finally: sock.close() def serve(addr): sock = socket.socket() sock.bind(addr); sock.listen(50) while True: conn, _ = sock.accept() threading.Thread(target=echo, args=(conn,)).start() serve(('0.0.0.0', 4000))
1000 clients sends 1000 messages to echo server.
(Total 1M messages)
threading: 34MB
gevent: 26MB
tornado: 12MB
select: 6.1MB
threading: 3.9GB
gevent: 41MB
tornado: 27MB
select: 21MB
Threaded code doesn't run in 32bit environment.
threading:
43sec
gevent: 53sec
tornado: 43sec
select: 25sec
2000 clients send 50 messages. (Total: 0.1M)
Add busy loop before send.
def stress(): # 18.6 ms def rec(n): if n: return rec(n-1) for i in xrange(100): rec(100)
Gevent | Threading | |
---|---|---|
RSS | 46.1MB | 210.5MB |
VSS | 46.5MB | 7.9GB |
time | 3m20sec | 10m55sec |
Threading have significant overhead.
Threading is enough on many circumstance
It's ok to use Gevent for fun :-)
multicore, multithread, heavy load
When threading overhead is problem, Gevent helps us.
Event driven programming splits code too small.
def spamegg(a): b = spam() return egg(a, b)
class SpameHamEgg(object): def bake(self, a, callback): self.a = a self.callback = callback spam(callback=self.on_spam) def on_spam(self, b): egg(self.a, b, callback=self.callback)
limited coroutine implemented by generator
from tornado import gen @gen.engine def spamegg(a): b = yeild spam() return egg(a, b)
Callback is called from outside of try-catch block.
Event driven programming requires another error handling style.
def spamegg(): try: a = spam() return egg(a) except Exception as e: log.error(e) return None
import contextlib @contextlib.contextmanager def log_error(): try: yield except Exception as e: log.error(e) def spamegg(): with StackContext(log_error): spam(callback=egg)
Many libraries works on Gevent with monkey patching.
It's easy to support gevent.
Requires full scratch.
Works on Gevent with monkey patch.
It doesn't return to Tornado IO loops. So Motor (gevent like system) is developed.
Torando, Twisted, node.js are good event driven programming framework.
Gevent allows standard programming style
and using existing libraries.
lightweight thread requires explicit switch. (coroutine)
import greenlet def f1(): print 'f1', 1 g2.switch() print 'f1', 3 g2.switch() print 'f1', 5 def f2(): print 'f2', 2 g1.switch() print 'f2', 4 g1.switch() g1 = greenlet.greenlet(f1) g2 = greenlet.greenlet(f2) g1.switch()
Result
f1 1
f2 2
f1 3
f2 4
f1 5
cooperative (easy to write threadsafe code)
doesn't run blocking system call concurrently
Wrapping libev event loop
import gevent.core import time loop = gevent.core.loop() def callback(): print time.time() # repeated timer event. timer = loop.timer(1.0, 1.0) timer.start(callback) loop.run()
Result:
1347446334.99
1347446335.99
1347446336.99
1347446337.99
...
The Greenlet connects eventloop and greenlet.
import gevent.core, greenlet, time # simplified hub. # Use hub = gevent.get_hub() normally. loop = gevent.core.loop() hub = greenlet.greenlet(loop.run) # simplified gevent.sleep() def sleep(seconds): timer = loop.timer(seconds) # Switch to current greenlet on callback. timer.start(greenlet.getcurrent().switch) # Switch to hub and run event loop. hub.switch() # function looks like blocking code. def sleeper(): for _ in range(4): print time.time() sleep(1) sleeper()
Result:
1347448193.7
1347448194.7
1347448195.71
1347448196.71
http://sdiehl.github.com/gevent-tutorial
http://methane.github.com/gevent-tutorial-ja
https://github.com/SiteSupport/gevent
http://gevent.org/