├── .gitignore ├── README.md ├── __init__.py ├── async_main.py ├── benchmarks ├── README.md ├── asyncio_http.py ├── bentchmark.py ├── flask_http.py ├── requirements.txt └── tornado_http.py ├── print_msg.py └── task.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | .static_storage/ 56 | .media/ 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | .idea 106 | .vscode 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # asyncio demo -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaopeng163/asyncio-demo/81b26e4f9808f8a4be114333986d0095dc20945c/__init__.py -------------------------------------------------------------------------------- /async_main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time 3 | from math import sqrt 4 | from datetime import datetime 5 | 6 | async def print_message_periodical(interval_seconds, message='keep alive'): 7 | while True: 8 | print(f'{datetime.now()} - {message}') 9 | start = time.time() 10 | end = start + interval_seconds 11 | while True: 12 | await asyncio.sleep(0) 13 | now = time.time() 14 | if now >= end: 15 | break 16 | 17 | if __name__ == "__main__": 18 | scheduler = asyncio.get_event_loop() 19 | scheduler.create_task( 20 | print_message_periodical(3, 'three') 21 | ) 22 | scheduler.create_task( 23 | print_message_periodical(10, 'ten') 24 | ) 25 | scheduler.run_forever() 26 | -------------------------------------------------------------------------------- /benchmarks/README.md: -------------------------------------------------------------------------------- 1 | # Benchmark for Http Server 2 | 3 | 4 | ## setup 5 | 6 | Install requirements 7 | 8 | ```bash 9 | pip install -r requirements.txt 10 | ``` 11 | 12 | Install wrk [Modern HTTP benchmarking tool](https://github.com/wg/wrk) 13 | 14 | On Mac, just 15 | 16 | ```bash 17 | brew install wrk 18 | ``` 19 | 20 | ## Start all http server 21 | 22 | 23 | ```bash 24 | python asyncio_http.py 25 | ``` 26 | 27 | ```bash 28 | export FLASK_APP=flask_http.py 29 | flask run 30 | ``` 31 | 32 | ```bash 33 | python tornado_http.py 34 | ``` 35 | 36 | ## Results 37 | 38 | ```bash 39 | ➜ ~ wrk -t12 -c400 -d30s http://127.0.0.1:8888/100 40 | Running 30s test @ http://127.0.0.1:8888/100 41 | 12 threads and 400 connections 42 | Thread Stats Avg Stdev Max +/- Stdev 43 | Latency 180.27ms 32.94ms 329.64ms 89.84% 44 | Req/Sec 173.31 49.03 303.00 69.16% 45 | 62217 requests in 30.10s, 17.44MB read 46 | Socket errors: connect 0, read 381, write 0, timeout 0 47 | Requests/sec: 2067.11 48 | Transfer/sec: 593.49KB 49 | ➜ ~ wrk -t12 -c400 -d30s http://127.0.0.1:5000/100 50 | Running 30s test @ http://127.0.0.1:5000/100 51 | 12 threads and 400 connections 52 | Thread Stats Avg Stdev Max +/- Stdev 53 | Latency 162.54ms 77.98ms 422.78ms 72.48% 54 | Req/Sec 61.26 41.97 310.00 77.66% 55 | 15643 requests in 30.09s, 3.80MB read 56 | Socket errors: connect 0, read 1780, write 6, timeout 0 57 | Requests/sec: 519.88 58 | Transfer/sec: 129.46KB 59 | ➜ ~ wrk -t12 -c400 -d30s http://127.0.0.1:25000/100 60 | Running 30s test @ http://127.0.0.1:25000/100 61 | 12 threads and 400 connections 62 | Thread Stats Avg Stdev Max +/- Stdev 63 | Latency 15.94ms 2.95ms 57.08ms 86.00% 64 | Req/Sec 2.01k 259.08 3.32k 88.75% 65 | 720726 requests in 30.07s, 114.10MB read 66 | Socket errors: connect 0, read 302, write 0, timeout 0 67 | Requests/sec: 23965.96 68 | Transfer/sec: 3.79MB 69 | ``` 70 | 71 | 72 | ## Links 73 | 74 | [uvloop: Blazing fast Python networking](https://magic.io/blog/uvloop-blazing-fast-python-networking/) 75 | 76 | -------------------------------------------------------------------------------- /benchmarks/asyncio_http.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import sys 4 | 5 | import httptools 6 | import uvloop 7 | 8 | from socket import * 9 | 10 | 11 | PRINT = 0 12 | 13 | _RESP_CACHE = {} 14 | 15 | class HttpRequest: 16 | __slots__ = ('_protocol', '_url', '_headers', '_version') 17 | 18 | def __init__(self, protocol, url, headers, version): 19 | self._protocol = protocol 20 | self._url = url 21 | self._headers = headers 22 | self._version = version 23 | 24 | 25 | class HttpResponse: 26 | __slots__ = ('_protocol', '_request', '_headers_sent') 27 | 28 | def __init__(self, protocol, request): 29 | self._protocol = protocol 30 | self._request = request 31 | self._headers_sent = False 32 | 33 | def write(self, data): 34 | self._protocol._transport.write(b''.join([ 35 | 'HTTP/{} 200 OK\r\n'.format( 36 | self._request._version).encode('latin-1'), 37 | b'Content-Type: text/plain\r\n', 38 | 'Content-Length: {}\r\n'.format(len(data)).encode('latin-1'), 39 | b'\r\n', 40 | data 41 | ])) 42 | 43 | 44 | class HttpProtocol(asyncio.Protocol): 45 | 46 | __slots__ = ('_loop', 47 | '_transport', '_current_request', '_current_parser', 48 | '_current_url', '_current_headers') 49 | 50 | def __init__(self, *, loop=None): 51 | if loop is None: 52 | loop = asyncio.get_event_loop() 53 | self._loop = loop 54 | self._transport = None 55 | self._current_request = None 56 | self._current_parser = None 57 | self._current_url = None 58 | self._current_headers = None 59 | 60 | def on_url(self, url): 61 | self._current_url = url 62 | 63 | def on_header(self, name, value): 64 | self._current_headers.append((name, value)) 65 | 66 | def on_headers_complete(self): 67 | self._current_request = HttpRequest( 68 | self, self._current_url, self._current_headers, 69 | self._current_parser.get_http_version()) 70 | 71 | self._loop.call_soon( 72 | self.handle, self._current_request, 73 | HttpResponse(self, self._current_request)) 74 | 75 | #### 76 | 77 | def connection_made(self, transport): 78 | self._transport = transport 79 | sock = transport.get_extra_info('socket') 80 | try: 81 | sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) 82 | except (OSError, NameError): 83 | pass 84 | 85 | def connection_lost(self, exc): 86 | self._current_request = self._current_parser = None 87 | 88 | def data_received(self, data): 89 | if self._current_parser is None: 90 | assert self._current_request is None 91 | self._current_headers = [] 92 | self._current_parser = httptools.HttpRequestParser(self) 93 | 94 | self._current_parser.feed_data(data) 95 | 96 | def handle(self, request, response): 97 | parsed_url = httptools.parse_url(self._current_url) 98 | payload_size = parsed_url.path.decode('ascii')[1:] 99 | if not payload_size: 100 | payload_size = 1024 101 | else: 102 | payload_size = int(payload_size) 103 | resp = _RESP_CACHE.get(payload_size) 104 | if resp is None: 105 | resp = b'X' * payload_size 106 | _RESP_CACHE[payload_size] = resp 107 | response.write(resp) 108 | if not self._current_parser.should_keep_alive(): 109 | self._transport.close() 110 | self._current_parser = None 111 | self._current_request = None 112 | 113 | 114 | def abort(msg): 115 | print(msg, file=sys.stderr) 116 | sys.exit(1) 117 | 118 | 119 | def httptools_server(loop, addr): 120 | return loop.create_server(lambda: HttpProtocol(loop=loop), *addr) 121 | 122 | 123 | if __name__ == '__main__': 124 | parser = argparse.ArgumentParser() 125 | parser.add_argument('--addr', default='127.0.0.1:25000', type=str) 126 | args = parser.parse_args() 127 | 128 | loop_type = 'asyncio' 129 | server_type = 'httptools' 130 | if loop_type: 131 | loop = globals()[loop_type].new_event_loop() 132 | else: 133 | loop = None 134 | 135 | if loop: 136 | asyncio.set_event_loop(loop) 137 | loop.set_debug(False) 138 | 139 | unix = False 140 | if args.addr.startswith('file:'): 141 | unix = True 142 | addr = args.addr[5:] 143 | else: 144 | addr = args.addr.split(':') 145 | addr[1] = int(addr[1]) 146 | addr = tuple(addr) 147 | 148 | server_factory = globals()['{}_server'.format(server_type)] 149 | 150 | print('serving on: {}'.format(addr)) 151 | 152 | if loop: 153 | server = loop.run_until_complete(server_factory(loop, addr)) 154 | try: 155 | loop.run_forever() 156 | finally: 157 | server.close() 158 | loop.close() -------------------------------------------------------------------------------- /benchmarks/bentchmark.py: -------------------------------------------------------------------------------- 1 | import time 2 | from datetime import datetime 3 | from multiprocessing.pool import ThreadPool 4 | 5 | from step1 import search 6 | 7 | def timeit(method): 8 | def timed(*args, **kw): 9 | ts = time.time() 10 | result = method(*args, **kw) 11 | te = time.time() 12 | if 'log_time' in kw: 13 | name = kw.get('log_name', method.__name__.upper()) 14 | kw['log_time'][name] = int((te - ts) * 1000) 15 | else: 16 | print('%r %2.5f ms' % \ 17 | (method.__name__, (te - ts) * 1000)) 18 | return result 19 | return timed 20 | 21 | 22 | N = 100 23 | COUNT = 10000 24 | 25 | @timeit 26 | def synchronous(): 27 | for i in range(COUNT): 28 | search(N) 29 | 30 | @timeit 31 | def multi_process(): 32 | pool = ThreadPool(10) 33 | result = pool.map(search, [N]*COUNT) 34 | pool.close() 35 | pool.join() 36 | 37 | 38 | 39 | if __name__ == "__main__": 40 | synchronous() 41 | multi_process() -------------------------------------------------------------------------------- /benchmarks/flask_http.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | app = Flask(__name__) 3 | 4 | import logging 5 | log = logging.getLogger('werkzeug') 6 | log.setLevel(logging.ERROR) 7 | 8 | 9 | @app.route('/') 10 | def hello_world(payload_size): 11 | 12 | return 'X' * int(payload_size) 13 | 14 | -------------------------------------------------------------------------------- /benchmarks/requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.3.2 2 | async-timeout==3.0.0 3 | attrs==18.1.0 4 | chardet==3.0.4 5 | click==6.7 6 | Flask==1.0.2 7 | httptools==0.0.11 8 | idna==2.7 9 | idna-ssl==1.1.0 10 | itsdangerous==0.24 11 | Jinja2==2.10 12 | MarkupSafe==1.0 13 | multidict==4.3.1 14 | mypy==0.620 15 | requests==2.10.0 16 | tornado==5.1 17 | typed-ast==1.1.0 18 | uvloop==0.11.2 19 | Werkzeug==0.14.1 20 | yarl==1.2.6 -------------------------------------------------------------------------------- /benchmarks/tornado_http.py: -------------------------------------------------------------------------------- 1 | import tornado.httpserver 2 | import tornado.ioloop 3 | import tornado.options 4 | import tornado.web 5 | import logging 6 | from tornado.options import define, options 7 | 8 | logging.getLogger('tornado.access').disabled = True 9 | define("port", default=8888, help="run on the given port", type=int) 10 | 11 | 12 | class MainHandler(tornado.web.RequestHandler): 13 | def get(self, payload_size): 14 | self.write("X"*int(payload_size)) 15 | 16 | 17 | def main(): 18 | tornado.options.parse_command_line() 19 | application = tornado.web.Application([ 20 | (r"/(.*)", MainHandler), 21 | ]) 22 | http_server = tornado.httpserver.HTTPServer(application) 23 | http_server.listen(options.port) 24 | tornado.ioloop.IOLoop.current().start() 25 | 26 | 27 | if __name__ == "__main__": 28 | main() -------------------------------------------------------------------------------- /print_msg.py: -------------------------------------------------------------------------------- 1 | import time 2 | from datetime import datetime 3 | 4 | 5 | def print_message_periodical(interval_seconds, message='keep alive'): 6 | while True: 7 | print(f'{datetime.now()} - {message}') 8 | start = time.time() 9 | end = start + interval_seconds 10 | while True: 11 | yield 12 | now = time.time() 13 | if now >= end: 14 | break 15 | 16 | 17 | if __name__ == "__main__": 18 | a = print_message_periodical(3, 'three') 19 | b = print_message_periodical(10, 'ten') 20 | stack = [a, b] 21 | while True: 22 | for task in stack: 23 | next(task) 24 | 25 | -------------------------------------------------------------------------------- /task.py: -------------------------------------------------------------------------------- 1 | from queue import deque 2 | from print_msg import print_message_periodical 3 | 4 | class Task: 5 | 6 | next_id = 0 7 | 8 | def __init__(self, routine): 9 | self.id = Task.next_id 10 | Task.next_id += 1 11 | self.routine = routine 12 | 13 | 14 | class Scheduler: 15 | 16 | def __init__(self): 17 | self.runnable_tasks = deque() 18 | self.completed_task_results = {} 19 | self.failed_task_errors = {} 20 | 21 | def add(self, routine): 22 | task = Task(routine) 23 | self.runnable_tasks.append(task) 24 | return task.id 25 | 26 | def run_to_completion(self): 27 | while len(self.runnable_tasks) !=0: 28 | task = self.runnable_tasks.popleft() 29 | try: 30 | yielded = next(task.routine) 31 | except StopIteration as stopped: 32 | print(f'completed with result: {stopped.value}') 33 | self.completed_task_results[task.id] = stopped.value 34 | except Exception as e: 35 | print(f'failed with exception: {e}') 36 | self.failed_task_errors[task.id] = e 37 | else: 38 | assert yielded is None 39 | self.runnable_tasks.append(task) 40 | 41 | if __name__ == "__main__": 42 | sch = Scheduler() 43 | sch.add(print_message_periodical(3, 'three')) 44 | sch.add(print_message_periodical(10, 'ten')) 45 | sch.run_to_completion() --------------------------------------------------------------------------------