SlideShare a Scribd company logo
1 of 66
Download to read offline
How To Make The
Fastest Router
In Python
Makoto Kuwata
https://github.com/kwatch/
PloneConference 2018 Tokyo
Abstract
‣ TL;DR
‣ You can make router much faster (max: x10)
‣ Requirements
‣ Python3
‣ Experience of Web Application Framework (Django, Flask, Plone, etc)
‣ Sample Code
‣ https://github.com/kwatch/router-sample/
‣ https://github.com/kwatch/keight/tree/python/ (Framework)
Table of Contents
‣ What is Router?
‣ Linear Search
‣ Naive / Prefix String / Fixed Path Dictionary
‣ Regular Expression
‣ Naive / Smart / Optimized
‣ State Machine
‣ Conclusion
What is Router?
What is Router?
‣ Router is a component of web app framework (WAF).
‣ Router determines request handler according to request
method and request path.

Handler A
App
Server
Handler B
Handler C
Client
: HTTP Request
: HTTP Response
WSGI
App
Server Side
Router determines
"which handler?"
Request Handler Example
class BooksAPI(RequestHandler):
with on.path('/api/books/'):
@on('GET')
def do_index(self):
return {"action": "index"}
@on('POST')
def do_create(self):
return {"action": "create"}
....
← handler class
← URL Path
← request method
← handler func
← request method
← handler func
Request Handler Example
....
with on.path('/api/books/{id:int}'):
@on('GET')
def do_show(self, id):
return {"action": "show", "id": id}
@on('PUT')
def do_update(self, id):
return {"action": "update", "id": id}
@on('DELETE')
def do_delete(self, id):
return {"action": "delete", "id": id}
← URL Path
← request method
← handler func
← request method
← handler func
← request method
← handler func
Mapping Example: Request to Handler
mapping_table = [
## method path class func
("GET" , r"/api/books/" , BooksAPI , do_index),
("POST" , r"/api/books/" , BooksAPI , do_create),

("GET" , r"/api/books/(d+)" , BooksAPI , do_show),
("PUT" , r"/api/books/(d+)" , BooksAPI , do_update),

("DELETE", r"/api/books/(d+)" , BooksAPI , do_delete),

("GET" , r"/api/orders/" , OrdersAPI, do_index),

("POST" , r"/api/orders/" , OrdersAPI, do_create),

("GET" , r"/api/orders/(d+)", OrdersAPI, do_show),
("PUT" , r"/api/orders/(d+)", OrdersAPI, do_update),

("DELETE", r"/api/orders/(d+)", OrdersAPI, do_delete),

....
]
Mapping Example: Request to Handler
mapping_list = [
## path class {method: func}
(r"/api/books/" , BooksAPI , {"GET": do_index,
"POST": do_create}),
(r"/api/books/(d+)" , BooksAPI , {"GET": do_show,
"PUT": do_update,
"DELETE": do_delete}),

(r"/api/orders/" , OrdersAPI, {"GET": do_index,
"POST": do_create}),
(r"/api/orders/(d+)", OrdersAPI, {"GET": do_show,
"PUT": do_update,
"DELETE": do_delete}),

....
]
Same information
in different format
Router Example
>>> router = Router(mapping_list)
>>> router.lookup("GET", "/api/books/")
(BooksAPI, do_index, [])
>>> router.lookup("GET", "/api/books/123")
(BooksAPI, do_show, [123])
Router Example
### 404 Not Found
>>> router.lookup("GET", "/api/books/123/comments")
(None, None, None)
### 405 Method Not Allowed
>>> router.lookup("POST", "/api/books/123")
(BooksAPI, None, [123])
Linear Search
(Naive)
Linear Search
mapping = [
(r"/api/books/" , BooksAPI , {"GET": do_index,
"POST": do_create}),
(r"/api/books/(d+)" , BooksAPI , {"GET": do_show,
"PUT": do_update,
"DELETE": do_delete}),

(r"/api/orders/" , OrdersAPI, {"GET": do_index,
"POST": do_create}),
(r"/api/orders/(d+)", OrdersAPI, {"GET": do_show,
"PUT": do_update,
"DELETE": do_delete}),

....
]
Router Class
class LinearNaiveRouter(Router):
def __init__(self, mapping):
self._mapping_list = 
[ (compile_path(path), klass, funcs)
for path, klass, funcs in mapping ]
def lookup(req_meth, req_path):
for rexp, klass, funcs in self._mapping_list:
m = rexp.match(req_path)
if m:
params = [ int(v) for v in m.groups() ]
func = funcs.get(req_meth)
return klass, func, params
return None, None, None
Benchmark (Data)
mapping_list = [
(r'/api/aaa' , DummyAPI, {"GET": ...}),
(r'/api/aaa/{id:int}', DummyAPI, {"GET": ...}),
(r'/api/bbb' , DummyAPI, {"GET": ...}),
(r'/api/bbb/{id:int}', DummyAPI, {"GET": ...}),
....
(r'/api/yyy' , DummyAPI, {"GET": ...}),
(r'/api/yyy/{id:int}', DummyAPI, {"GET": ...}),
(r'/api/zzz' , DummyAPI, {"GET": ...}),
(r'/api/zzz/{id:int}', DummyAPI, {"GET": ...}),
]
### Benchmark environment:
### AWS EC2 t3.nano, Ubuntu 18.04, Python 3.6.6
See sample code
for details
Benchmark
0 10 20 30 40 50
Linear Naive
Seconds
(1M Requests)
/api/aaa
/api/aaa/{id}
/api/zzz
/api/zzz/{id}
sec
SlowerFaster
very fast on top of list
(/api/aaa, /api/aaa/{id})
very slow on bottom of list
(/api/zzz, /api/zzz/{id})
Pros.
Cons.
Pros & Cons
✗ Very slow when many mapping entries exist.
✓ Easy to understand and implement
Linear Search
(Prefix String)
Prefix String
mapping_list = [
("/books" , r"/books" , BooksAPI , {"GET": ...}),

("/books/" , r"/books/(d+)" , BooksAPI , {"GET": ...}),

("/orders" , r"/orders" , OrdersAPI, {"GET": ...}),

("/orders/", r"/orders/(d+)", OrdersAPI, {"GET": ...}),

]
for prefix, rexp, klass, funcs in mapping:
if not "/api/orders/123".startswith(prefix):
continue
m = rexp.match("/api/orders/123")
if m:
...
Much faster than
rexp.match()
(replace expensive operation
with cheap operation)
Prefix strings
Router Class
def prefix_str(s):
return s.split('{', 1)[0]
class PrefixLinearRouter(Router):
def __init__(self, mapping):
for path, klass, funcs in mapping:
prefix = prefix_str(path)
rexp = compile_path(path)
t = (prefix, rexp, klass, funcs)
self._mapping_list.append(t)
...
Router Class
....
def lookup(req_meth, req_path):
for prefix, rexp, klass, funcs in self._mapping:
if not req_path.startswith(prefix):
continue
m = rexp.match(req_path)
if m:
params = [ int(v) for v in m.groups() ]
func = funcs.get(req_meth)
return klass, func, params
return None, None, None
Much faster than
rexp.match()
Benchmark
0 10 20 30 40 50
Linear Naive
Prefix Str
Seconds
(1M Requests)
/api/aaa
/api/aaa/{id}
/api/zzz
/api/zzz/{id}
sec
SlowerFaster
about twice as fast as
naive implementation
Pros.
Cons.
Pros & Cons
✗ Still slow when many mapping entries exist.
✓ Makes linear search faster.
✓ Easy to understand and implement.
Linear Search
(Fixed Path Dictionary)
Fixed Path Dictionary
## variable path (contains one or more path parameters)
mapping_list = [
("/books" , r"/books" , BooksAPI , {"GET": ...}),

("/books/" , r"/books/(d+)" , BooksAPI , {"GET": ...}),

("/orders" , r"/orders" , OrdersAPI, {"GET": ...}),

("/orders/", r"/orders/(d+)", OrdersAPI, {"GET": ...}),

]
## fixed path (contains no path parameters)
mapping_dict = {
r"/books" : (BooksAPI , {"GET": ...}, []),
r"/orders": (OrdersAPI, {"GET": ...}, []),
}
Use fixed path as key of dict
Move fixed path to dict
Router Class
class FixedLinearRouter(object):
def __init__(self, mapping):
self._mapping_dict = {}
self._mapping_list = []
for path, klass, funcs in mapping:
if '{' not in path:
self._mapping_dict[path] = (klass, funcs, [])

else:
prefix = prefix_str(path)
rexp = compile_path(path)
t = (prefix, rexp, klass, funcs)
self._mapping_list.append(t)
....
Router Class
....
def lookup(req_meth, req_path):
t = self._mapping_dict.get(req_path)
if t: return t
for prefix, rexp, klass, funcs in self._mapping_list:

if not req_path.startswith(prefix)
continue
m = rexp.match(req_path)
if m:
params = [ int(v) for v in m.groups() ]
func = funcs.get(req_meth)
return klass, func, params
return None, None, None
Much faster than
for-loop
Number of entries
are reduced
Benchmark
0 10 20 30 40 50
Linear Naive
Prefix Str
Fixed Path
Seconds
(1M Requests)
/api/aaa
/api/aaa/{id}
/api/zzz
/api/zzz/{id}
sec
SlowerFaster
super fast on fixed path!
three times faster than
naive implementation
Pros.
Cons.
Pros & Cons
✗ Still slow when many mapping entries exist.
✓ Makes fixed path search super faster.
✓ Makes variable path search faster,

because number of entries are reduced.
✓ Easy to understand and implement.
Notice
‣ Don't use r"/api/v{version:int}".
‣ because all API paths are regarded as variable path.
‣ Instead, use r"/api/v1", r"/api/v2", ...
‣ in order to increase number of fixed path.
Regular Expression
(Naive)
Concatenate Regular Expressions
mapping_list = {
(r"/api/books/(d+)" , BooksAPI , {"GET": ...}),

(r"/api/orders/(d+)", OrdersAPI, {"GET": ...}),

(r"/api/users/(d+)" , UsersAPI , {"GET": ...}),

]
arr = [
r"(?P<_0>^/api/books/(d+)$)",
r"(?P<_1>^/api/orders/(d+)$)",
r"(?P<_2>^/api/users/(d+)$)",
]
all_rexp = re.compile("|".join(arr))
Named groups
Matching
m = all_rexp.match("/api/users/123")
d = m.groupdict() #=> {"_0": None,
# "_1": None,
# "_2": "/api/users/123"}
for k, v in d.items():
if v:
i = int(v[1:]) # ex: "_2" -> 2
break
_, klass, funcs, pos, nparams = mapping_list[i]
arr = m.groups() #=> (None, None, None, None,
# "/api/users/123", "123")
params = arr[5:6] #=> {"123"}
Router Class
class NaiveRegexpRouter(Router):
def __init__(self, mapping):
self._mapping_dict = {}
self._mapping_list = []
arr = []; i = 0; pos = 0
for path, klass, funcs in mapping:
if '{' not in path:
self._mapping_dict[path] = (klass, funcs, [])
else:
rexp = compile_path(path); pat = rexp.pattern
arr.append("(?P<_%s>%s)" % (i, pat))
t = (klass, funcs, pos, path.count('{'))
self._mapping_list.append(t)
i += 1; pos += 1 + path.count('{')
self._all_rexp = re.compile("|".join(arr))
Router Class
....
def lookup(req_meth, req_path):
t = self._mapping_dict.get(req_path)
if t: return t
m = self._all_rexp.match(req_path)
if m:
for k, v in m.groupdict().items():
if v:
i = int(v[1:])
break
klass, funcs, pos, nparams = self._mapping_list[i]

params = m.groups()[pos:pos+nparams]
func = funcs.get(req_meth)
return klass, func, params
return None, None, None
find index in list
find param values
Benchmark
0 10 20 30 40 50
Linear
Regexp
Naive
Prefix Str
Fixed Path
Naive
Seconds
(1M Requests)
/api/aaa
/api/aaa/{id}
/api/zzz
/api/zzz/{id}
sec
SlowerFaster
slower than
linear search :(
Pros.
Cons.
Pros & Cons
✗ Slower than linear search
✓ Nothing :(
Notice
$ python3 --version
3.4.5
$ python3
>>> import re
>>> arr = ['^/(d+)$'] * 101
>>> re.compile("|".join(arr))
File "/opt/vs/python/3.4.5/lib/python3.4/sre_compile.py",
line 579, in compile
"sorry, but this version only supports 100 named groups"
AssertionError: sorry, but this version only supports 100
named groups
Python <= 3.4 limits number of
groups in a regular expression,
and no work around :(
Regular Expression
(Smart)
Improved Regular Expression
mapping_list = {
(r"/api/books/(d+)" , BooksAPI , {"GET": ...}),
(r"/api/orders/(d+)" , OrdersAPI , {"GET": ...}),
(r"/api/users/(d+)" , UsersAPI , {"GET": ...}),
]
arr = [ r"^/api/books/(?:d+)($)",
r"^/api/orders/(?:d+)($)",
r"^/api/users/(?:d+)($)", ]
all_rexp = re.compile("|".join(arr))
m = all_rexp.match("/api/users/123")
arr = m.groups() #=> (None, None, "")
i = arr.index("") #=> 2
t = mapping_list[i] #=> (r"/api/users/(d+)",
# UsersAPI, {"GET": ...})
No more
named groups
Tuple is much light-
weight than dict
index() is faster
than for-loop
Router Class
class SmartRegexpRouter(Router):
def __init__(self, mapping):
self._mapping_dict = {}
self._mapping_list = []
arr = []
for path, klass, funcs in mapping:
if '{' not in path:
self._mapping_dict[path] = (klass, funcs, [])

else:
rexp = compile_path(path); pat = rexp.pattern

arr.append(pat.replace("(", "(?:")
.replace("$", "($)"))
t = (rexp, klass, funcs)
self._mapping_list.append(t)

self._all_rexp = re.compile("|".join(arr))
Router Class
...
def lookup(req_meth, req_path):
t = self._mapping_dict.get(req_path)
if t: return t
m = self._all_rexp.match(req_path)
if m:
i = m.groups().index("")
rexp, klass, funcs = self._mapping_list[i]
m2 = rexp.match(req_path)
params = [ int(v) for v in m2.groups() ]
func = funcs.get(req_meth)
return klass, func, params
return None, None, None
Matching to find
index in list
Matching to get
param values
Benchmark
0 10 20 30 40 50
Linear
Regexp
Naive
Prefix Str
Fixed Path
Naive
Smart
Seconds
(1M Requests)
/api/aaa
/api/aaa/{id}
/api/zzz
/api/zzz/{id}
sec
SlowerFaster
Difference between
/api/aaa/{id} and
/api/zzz/{id} is small
Pros.
Cons.
Pros & Cons
✗ Slower when number of entries is small.

(due to overhead of twice matching)
✗ May be difficult to debug large regular
expression.
✓ Much faster than ever,

especially when many mapping entries exist.
Regular Expression
(Optimized)
Optimize Regular Expression
## before
arr = [r"^/api/books/(?:d+)($)",
r"^/api/orders/(?:d+)($)",
r"^/api/users/(?:d+)($)"]
all_rexp = re.compile("|".join(arr))
### after
arr = [r"^/api",
r"(?:",
"|".join([r"/books/(?:d+)($)",
r"/orders/(?:d+)($)",
r"/users/(?:d+)($)"]),
r")?"]
all_rexp = re.compile("|".join(arr))
Router Class
class OptimizedRegexpRouter(Router):
def __init__(self, mapping):
## Code is too complicated to show here.
## Please download sample code from github.
## https://github.com/kwatch/router-sample/
def lookup(req_meth, req_path):
## nothing changed; same as previous section
Benchmark
0 10 20 30 40 50
Linear
Regexp
Naive
Prefix Str
Fixed Path
Naive
Smart
Optimized
Seconds
(1M Requests)
/api/aaa
/api/aaa/{id}
/api/zzz
/api/zzz/{id}
sec
SlowerFaster
A little faster
on /api/zzz/{id}
Pros.
Cons.
Pros & Cons
✗ Performance benefit is very small (on Python).
✗ Rather difficult to implement and debug.
✓ A little faster than smart regular expression

when a lot of variable paths exist.
State Machine
State Machine
"api"
"books"
"orders"
"123"
"456"
/d+/
/d+/
/api/books/{id:int}/api/books
/api/orders /api/orders/{id:int}
: Start
: Not Accepted
: Accepted
State Machine: Definition
path = "/api/books"
transition = {
"api": {
"books": {
None: (BooksAPI, {"GET": do_index, ...}),
},
},
}
>>> transition["api"]["books"][None]
(BooksAPI, {"GET": do_index, ...})
>>> transition["api"]["users"][None]
KeyError: 'users'
Use None as terminator
(mark of accepted status)
State Machine: Definition
path = "/api/books/{id:int}"
transition = {
"api": {
"books": {
None: (BooksAPI, {"GET": do_index, ...}),
1: {
None: (BooksAPI, {"GET": do_show, ...}),
},
},
},
}
>>> transition["api"]["books"][1][None]
(BooksAPI, {"GET": do_index, ...})
1 represents int parameter,
2 represents str parameter.
State Machine: Transition
def find(req_path):
req_path = req_path.lstrip('/') #ex: "/a/b/c" -> "a/b/c"
items = req_path.split('/') #ex: "a/b/c" -> ["a","b","c"]
d = transition; params = []
for s in items:
if s in d: d = d[s]
elif 1 in d: d = d[1]; params.append(int(s))
elif 2 in d: d = d[2]; params.append(str(s))
else: return None
if None not in d: return None
klass, funcs = d[None]
return klass, funcs, params
>>> find("/api/books/123")
(BooksAPI, {"GET": do_index, ...}, [123])
Router Class
class StateMachineRouter(Router):
def __init__(self, mapping):
self._mapping_dict = {}
self._mapping_list = []
self._transition = {}
for path, klass, funcs in mapping:
if '{' not in path:
self._mapping_dict[path] = (klass, funcs, [])

else:
self._register(path, klass, funcs)
Router Class
...
PARAM_TYPES = {"int": 1, "str": 2}
def _register(self, path, klass, funcs):
ptypes = self.PARAM_TYPES
d = self._transition
for s in path[1:].split('/'):
key = s
if s[0] == "{" and s[-1] == "}":
## ex: "{id:int}" -> ("id", "int")
pname, ptype = s[1:-1].split(':', 1)
key = ptypes.get(ptype) or ptypes["str"]
d = d.setdefault(key, {})
d[None] = (klass, funcs)
Router Class
...
def lookup(self, req_meth, req_path):
d = self._transition
params = []
for s in req_path[1:].split('/'):
if s in d: d = d[s]
elif 1 in d: d = d[1]; params.append(int(s))
elif 2 in d: d = d[2]; params.append(str(s))
else: return None, None, None
if None in d:
klass, funcs = d[None]
func = funcs.get(req_meth)
return klass, func, params
return None, None, None
Benchmark
0 10 20 30 40 50
Linear
Regexp
StateMachine
Naive
Prefix Str
Fixed Path
Naive
Smart
Optimized
Seconds
(1M Requests)
/api/aaa
/api/aaa/{id}
/api/zzz
/api/zzz/{id}
sec
SlowerFaster
/api/aaa/{id} and
/api/zzz/{id} are
same performance
Benchmark (PyPy3.5)
0 10 20 30 40 50
Linear
Regexp
StateMachine
Naive
Prefix Str
Fixed Path
Naive
Smart
Optimized
Seconds
(1M Requests)
/api/aaa
/api/aaa/{id}
/api/zzz
/api/zzz/{id}
sec
SlowerFaster
Regular Expression is
very slow in PyPy3.5
String operation is
very fast because
JIT friendly
Benchmark (PyPy3.5)
0 1 2 3 4 5
Linear
Regexp
StateMachine
Naive
Prefix Str
Fixed Path
Naive
Smart
Optimized
Seconds
(1M Requests)
/api/aaa
/api/aaa/{id}
/api/zzz
/api/zzz/{id}
sec
SlowerFaster
The fastest method due to
Regexp-free (= JIT friendly)
A little slower than StateMachine
because containing Regexp
Pros.
Cons.
Pros & Cons
✗ Not support complicated pattern.
✗ Requires some effort to support URL path suffix
(ex: /api/books/123.json).
✓ Performance champion in routing area.
✓ Much faster in PyPy3.5, due to regexp-free.
JIT friendly!
Conclusion
Conclusion
‣ Linear Search is slow.
‣ Prefix string and Fixed path dict make it faster.
‣ Regular expression is very fast.
‣ Do your best to avoid named group (or named caption).
‣ State Machine is the fastest method in Python.
‣ Especially in PyPy3, due to regexp-free (= JIT friendly).
One More Thing
My Products
‣ Benchmarker.py
Awesome benchmarking utility.
https://pythonhosted.org/Benchmarker/
‣ Oktest.py
New generation of testing framework.
https://pythonhosted.org/Oktest/
‣ PyTenjin
Super fast and feature-rich template engine.
https://www.kuwata-lab.com/tenjin/pytenjin-users-guide.html
https://bit.ly/tenjinpy_slide (presentation)
Thank You

More Related Content

What's hot

Kubernetes Failure Stories, or: How to Crash Your Cluster - ContainerDays EU ...
Kubernetes Failure Stories, or: How to Crash Your Cluster - ContainerDays EU ...Kubernetes Failure Stories, or: How to Crash Your Cluster - ContainerDays EU ...
Kubernetes Failure Stories, or: How to Crash Your Cluster - ContainerDays EU ...Henning Jacobs
 
Introduction to Gitlab
Introduction to GitlabIntroduction to Gitlab
Introduction to GitlabJulien Pivotto
 
[Outdated] Secrets of Performance Tuning Java on Kubernetes
[Outdated] Secrets of Performance Tuning Java on Kubernetes[Outdated] Secrets of Performance Tuning Java on Kubernetes
[Outdated] Secrets of Performance Tuning Java on KubernetesBruno Borges
 
Build and Deploy Cloud Native Camel Quarkus routes with Tekton and Knative
Build and Deploy Cloud Native Camel Quarkus routes with Tekton and KnativeBuild and Deploy Cloud Native Camel Quarkus routes with Tekton and Knative
Build and Deploy Cloud Native Camel Quarkus routes with Tekton and KnativeOmar Al-Safi
 
Automatisez progressivement vos releases
Automatisez progressivement vos releasesAutomatisez progressivement vos releases
Automatisez progressivement vos releasesXebiaLabs
 
從實戰經驗看到的 K8S 導入痛點
從實戰經驗看到的 K8S 導入痛點從實戰經驗看到的 K8S 導入痛點
從實戰經驗看到的 K8S 導入痛點Will Huang
 
Unique ID generation in distributed systems
Unique ID generation in distributed systemsUnique ID generation in distributed systems
Unique ID generation in distributed systemsDave Gardner
 
How Kubernetes scheduler works
How Kubernetes scheduler worksHow Kubernetes scheduler works
How Kubernetes scheduler worksHimani Agrawal
 
Building robust and friendly command line applications in go
Building robust and friendly command line applications in goBuilding robust and friendly command line applications in go
Building robust and friendly command line applications in goAndrii Soldatenko
 
Optimizing Kubernetes Resource Requests/Limits for Cost-Efficiency and Latenc...
Optimizing Kubernetes Resource Requests/Limits for Cost-Efficiency and Latenc...Optimizing Kubernetes Resource Requests/Limits for Cost-Efficiency and Latenc...
Optimizing Kubernetes Resource Requests/Limits for Cost-Efficiency and Latenc...Henning Jacobs
 
DRUPAL CI/CD FROM DEV TO PROD WITH GITLAB, KUBERNETES AND HELM
DRUPAL CI/CD FROM DEV TO PROD WITH GITLAB, KUBERNETES AND HELMDRUPAL CI/CD FROM DEV TO PROD WITH GITLAB, KUBERNETES AND HELM
DRUPAL CI/CD FROM DEV TO PROD WITH GITLAB, KUBERNETES AND HELMDrupalCamp Kyiv
 
Azure Durable Functions
Azure Durable FunctionsAzure Durable Functions
Azure Durable FunctionsKarthikeyan VK
 
Clojure testing with Midje
Clojure testing with MidjeClojure testing with Midje
Clojure testing with Midjeinovex GmbH
 
CI/CD for React Native
CI/CD for React NativeCI/CD for React Native
CI/CD for React NativeJoao Marins
 
Introducing GitLab (September 2018)
Introducing GitLab (September 2018)Introducing GitLab (September 2018)
Introducing GitLab (September 2018)Noa Harel
 
Intro to Github Actions @likecoin
Intro to Github Actions @likecoinIntro to Github Actions @likecoin
Intro to Github Actions @likecoinWilliam Chong
 
Introduction to kubernetes
Introduction to kubernetesIntroduction to kubernetes
Introduction to kubernetesGabriel Carro
 
Introduction to docker swarm
Introduction to docker swarmIntroduction to docker swarm
Introduction to docker swarmWalid Ashraf
 

What's hot (20)

Kubernetes Failure Stories, or: How to Crash Your Cluster - ContainerDays EU ...
Kubernetes Failure Stories, or: How to Crash Your Cluster - ContainerDays EU ...Kubernetes Failure Stories, or: How to Crash Your Cluster - ContainerDays EU ...
Kubernetes Failure Stories, or: How to Crash Your Cluster - ContainerDays EU ...
 
Introduction to Gitlab
Introduction to GitlabIntroduction to Gitlab
Introduction to Gitlab
 
[Outdated] Secrets of Performance Tuning Java on Kubernetes
[Outdated] Secrets of Performance Tuning Java on Kubernetes[Outdated] Secrets of Performance Tuning Java on Kubernetes
[Outdated] Secrets of Performance Tuning Java on Kubernetes
 
Offzone | Another waf bypass
Offzone | Another waf bypassOffzone | Another waf bypass
Offzone | Another waf bypass
 
Build and Deploy Cloud Native Camel Quarkus routes with Tekton and Knative
Build and Deploy Cloud Native Camel Quarkus routes with Tekton and KnativeBuild and Deploy Cloud Native Camel Quarkus routes with Tekton and Knative
Build and Deploy Cloud Native Camel Quarkus routes with Tekton and Knative
 
Automatisez progressivement vos releases
Automatisez progressivement vos releasesAutomatisez progressivement vos releases
Automatisez progressivement vos releases
 
從實戰經驗看到的 K8S 導入痛點
從實戰經驗看到的 K8S 導入痛點從實戰經驗看到的 K8S 導入痛點
從實戰經驗看到的 K8S 導入痛點
 
Unique ID generation in distributed systems
Unique ID generation in distributed systemsUnique ID generation in distributed systems
Unique ID generation in distributed systems
 
How Kubernetes scheduler works
How Kubernetes scheduler worksHow Kubernetes scheduler works
How Kubernetes scheduler works
 
Building robust and friendly command line applications in go
Building robust and friendly command line applications in goBuilding robust and friendly command line applications in go
Building robust and friendly command line applications in go
 
Optimizing Kubernetes Resource Requests/Limits for Cost-Efficiency and Latenc...
Optimizing Kubernetes Resource Requests/Limits for Cost-Efficiency and Latenc...Optimizing Kubernetes Resource Requests/Limits for Cost-Efficiency and Latenc...
Optimizing Kubernetes Resource Requests/Limits for Cost-Efficiency and Latenc...
 
Docker by Example - Basics
Docker by Example - Basics Docker by Example - Basics
Docker by Example - Basics
 
DRUPAL CI/CD FROM DEV TO PROD WITH GITLAB, KUBERNETES AND HELM
DRUPAL CI/CD FROM DEV TO PROD WITH GITLAB, KUBERNETES AND HELMDRUPAL CI/CD FROM DEV TO PROD WITH GITLAB, KUBERNETES AND HELM
DRUPAL CI/CD FROM DEV TO PROD WITH GITLAB, KUBERNETES AND HELM
 
Azure Durable Functions
Azure Durable FunctionsAzure Durable Functions
Azure Durable Functions
 
Clojure testing with Midje
Clojure testing with MidjeClojure testing with Midje
Clojure testing with Midje
 
CI/CD for React Native
CI/CD for React NativeCI/CD for React Native
CI/CD for React Native
 
Introducing GitLab (September 2018)
Introducing GitLab (September 2018)Introducing GitLab (September 2018)
Introducing GitLab (September 2018)
 
Intro to Github Actions @likecoin
Intro to Github Actions @likecoinIntro to Github Actions @likecoin
Intro to Github Actions @likecoin
 
Introduction to kubernetes
Introduction to kubernetesIntroduction to kubernetes
Introduction to kubernetes
 
Introduction to docker swarm
Introduction to docker swarmIntroduction to docker swarm
Introduction to docker swarm
 

Similar to How to make the fastest Router in Python

AWS Hadoop and PIG and overview
AWS Hadoop and PIG and overviewAWS Hadoop and PIG and overview
AWS Hadoop and PIG and overviewDan Morrill
 
Reitit - Clojure/North 2019
Reitit - Clojure/North 2019Reitit - Clojure/North 2019
Reitit - Clojure/North 2019Metosin Oy
 
Web program-peformance-optimization
Web program-peformance-optimizationWeb program-peformance-optimization
Web program-peformance-optimizationxiaojueqq12345
 
Thumbtack Expertise Days # 5 - Javaz
Thumbtack Expertise Days # 5 - JavazThumbtack Expertise Days # 5 - Javaz
Thumbtack Expertise Days # 5 - JavazAlexey Remnev
 
Scala45 spray test
Scala45 spray testScala45 spray test
Scala45 spray testkopiczko
 
[245] presto 내부구조 파헤치기
[245] presto 내부구조 파헤치기[245] presto 내부구조 파헤치기
[245] presto 내부구조 파헤치기NAVER D2
 
High-level Programming Languages: Apache Pig and Pig Latin
High-level Programming Languages: Apache Pig and Pig LatinHigh-level Programming Languages: Apache Pig and Pig Latin
High-level Programming Languages: Apache Pig and Pig LatinPietro Michiardi
 
Cakefest 2010: API Development
Cakefest 2010: API DevelopmentCakefest 2010: API Development
Cakefest 2010: API DevelopmentAndrew Curioso
 
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQueryRemedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQueryTatsuhiko Miyagawa
 
Presto anatomy
Presto anatomyPresto anatomy
Presto anatomyDongmin Yu
 
Flink Forward San Francisco 2019: Build a Table-centric Apache Flink Ecosyste...
Flink Forward San Francisco 2019: Build a Table-centric Apache Flink Ecosyste...Flink Forward San Francisco 2019: Build a Table-centric Apache Flink Ecosyste...
Flink Forward San Francisco 2019: Build a Table-centric Apache Flink Ecosyste...Flink Forward
 
Flink Forward San Francisco 2019: Build a Table-centric Apache Flink Ecosyste...
Flink Forward San Francisco 2019: Build a Table-centric Apache Flink Ecosyste...Flink Forward San Francisco 2019: Build a Table-centric Apache Flink Ecosyste...
Flink Forward San Francisco 2019: Build a Table-centric Apache Flink Ecosyste...Flink Forward
 
Using and scaling Rack and Rack-based middleware
Using and scaling Rack and Rack-based middlewareUsing and scaling Rack and Rack-based middleware
Using and scaling Rack and Rack-based middlewareAlona Mekhovova
 
Переход на Scala: босиком по граблям
Переход на Scala: босиком по граблямПереход на Scala: босиком по граблям
Переход на Scala: босиком по граблямSveta Bozhko
 
Logstash-Elasticsearch-Kibana
Logstash-Elasticsearch-KibanaLogstash-Elasticsearch-Kibana
Logstash-Elasticsearch-Kibanadknx01
 
Introduction To Groovy 2005
Introduction To Groovy 2005Introduction To Groovy 2005
Introduction To Groovy 2005Tugdual Grall
 

Similar to How to make the fastest Router in Python (20)

AWS Hadoop and PIG and overview
AWS Hadoop and PIG and overviewAWS Hadoop and PIG and overview
AWS Hadoop and PIG and overview
 
Reitit - Clojure/North 2019
Reitit - Clojure/North 2019Reitit - Clojure/North 2019
Reitit - Clojure/North 2019
 
Laravel
LaravelLaravel
Laravel
 
Web program-peformance-optimization
Web program-peformance-optimizationWeb program-peformance-optimization
Web program-peformance-optimization
 
Thumbtack Expertise Days # 5 - Javaz
Thumbtack Expertise Days # 5 - JavazThumbtack Expertise Days # 5 - Javaz
Thumbtack Expertise Days # 5 - Javaz
 
Scala45 spray test
Scala45 spray testScala45 spray test
Scala45 spray test
 
router-simple.cr
router-simple.crrouter-simple.cr
router-simple.cr
 
[245] presto 내부구조 파헤치기
[245] presto 내부구조 파헤치기[245] presto 내부구조 파헤치기
[245] presto 내부구조 파헤치기
 
High-level Programming Languages: Apache Pig and Pig Latin
High-level Programming Languages: Apache Pig and Pig LatinHigh-level Programming Languages: Apache Pig and Pig Latin
High-level Programming Languages: Apache Pig and Pig Latin
 
Cakefest 2010: API Development
Cakefest 2010: API DevelopmentCakefest 2010: API Development
Cakefest 2010: API Development
 
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQueryRemedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
Remedie: Building a desktop app with HTTP::Engine, SQLite and jQuery
 
Presto anatomy
Presto anatomyPresto anatomy
Presto anatomy
 
Flink Forward San Francisco 2019: Build a Table-centric Apache Flink Ecosyste...
Flink Forward San Francisco 2019: Build a Table-centric Apache Flink Ecosyste...Flink Forward San Francisco 2019: Build a Table-centric Apache Flink Ecosyste...
Flink Forward San Francisco 2019: Build a Table-centric Apache Flink Ecosyste...
 
Flink Forward San Francisco 2019: Build a Table-centric Apache Flink Ecosyste...
Flink Forward San Francisco 2019: Build a Table-centric Apache Flink Ecosyste...Flink Forward San Francisco 2019: Build a Table-centric Apache Flink Ecosyste...
Flink Forward San Francisco 2019: Build a Table-centric Apache Flink Ecosyste...
 
Intro to PSGI and Plack
Intro to PSGI and PlackIntro to PSGI and Plack
Intro to PSGI and Plack
 
Using and scaling Rack and Rack-based middleware
Using and scaling Rack and Rack-based middlewareUsing and scaling Rack and Rack-based middleware
Using and scaling Rack and Rack-based middleware
 
Переход на Scala: босиком по граблям
Переход на Scala: босиком по граблямПереход на Scala: босиком по граблям
Переход на Scala: босиком по граблям
 
Logstash-Elasticsearch-Kibana
Logstash-Elasticsearch-KibanaLogstash-Elasticsearch-Kibana
Logstash-Elasticsearch-Kibana
 
Rack
RackRack
Rack
 
Introduction To Groovy 2005
Introduction To Groovy 2005Introduction To Groovy 2005
Introduction To Groovy 2005
 

More from kwatch

Migr8.rb チュートリアル
Migr8.rb チュートリアルMigr8.rb チュートリアル
Migr8.rb チュートリアルkwatch
 
なんでもID
なんでもIDなんでもID
なんでもIDkwatch
 
Nippondanji氏に怒られても仕方ない、配列型とJSON型の使い方
Nippondanji氏に怒られても仕方ない、配列型とJSON型の使い方Nippondanji氏に怒られても仕方ない、配列型とJSON型の使い方
Nippondanji氏に怒られても仕方ない、配列型とJSON型の使い方kwatch
 
【SQLインジェクション対策】徳丸先生に怒られない、動的SQLの安全な組み立て方
【SQLインジェクション対策】徳丸先生に怒られない、動的SQLの安全な組み立て方【SQLインジェクション対策】徳丸先生に怒られない、動的SQLの安全な組み立て方
【SQLインジェクション対策】徳丸先生に怒られない、動的SQLの安全な組み立て方kwatch
 
O/Rマッパーによるトラブルを未然に防ぐ
O/Rマッパーによるトラブルを未然に防ぐO/Rマッパーによるトラブルを未然に防ぐ
O/Rマッパーによるトラブルを未然に防ぐkwatch
 
正規表現リテラルは本当に必要なのか?
正規表現リテラルは本当に必要なのか?正規表現リテラルは本当に必要なのか?
正規表現リテラルは本当に必要なのか?kwatch
 
【公開終了】Python4PHPer - PHPユーザのためのPython入門 (Python2.5)
【公開終了】Python4PHPer - PHPユーザのためのPython入門 (Python2.5)【公開終了】Python4PHPer - PHPユーザのためのPython入門 (Python2.5)
【公開終了】Python4PHPer - PHPユーザのためのPython入門 (Python2.5)kwatch
 
DBスキーマもバージョン管理したい!
DBスキーマもバージョン管理したい!DBスキーマもバージョン管理したい!
DBスキーマもバージョン管理したい!kwatch
 
PHPとJavaScriptにおけるオブジェクト指向を比較する
PHPとJavaScriptにおけるオブジェクト指向を比較するPHPとJavaScriptにおけるオブジェクト指向を比較する
PHPとJavaScriptにおけるオブジェクト指向を比較するkwatch
 
SQL上級者こそ知って欲しい、なぜO/Rマッパーが重要か?
SQL上級者こそ知って欲しい、なぜO/Rマッパーが重要か?SQL上級者こそ知って欲しい、なぜO/Rマッパーが重要か?
SQL上級者こそ知って欲しい、なぜO/Rマッパーが重要か?kwatch
 
Fantastic DSL in Python
Fantastic DSL in PythonFantastic DSL in Python
Fantastic DSL in Pythonkwatch
 
What is wrong on Test::More? / Test::Moreが抱える問題点とその解決策
What is wrong on Test::More? / Test::Moreが抱える問題点とその解決策What is wrong on Test::More? / Test::Moreが抱える問題点とその解決策
What is wrong on Test::More? / Test::Moreが抱える問題点とその解決策kwatch
 
PHP5.5新機能「ジェネレータ」初心者入門
PHP5.5新機能「ジェネレータ」初心者入門PHP5.5新機能「ジェネレータ」初心者入門
PHP5.5新機能「ジェネレータ」初心者入門kwatch
 
Pretty Good Branch Strategy for Git/Mercurial
Pretty Good Branch Strategy for Git/MercurialPretty Good Branch Strategy for Git/Mercurial
Pretty Good Branch Strategy for Git/Mercurialkwatch
 
Oktest - a new style testing library for Python -
Oktest - a new style testing library for Python -Oktest - a new style testing library for Python -
Oktest - a new style testing library for Python -kwatch
 
文字列結合のベンチマークをいろんな処理系でやってみた
文字列結合のベンチマークをいろんな処理系でやってみた文字列結合のベンチマークをいろんな処理系でやってみた
文字列結合のベンチマークをいろんな処理系でやってみたkwatch
 
I have something to say about the buzz word "From Java to Ruby"
I have something to say about the buzz word "From Java to Ruby"I have something to say about the buzz word "From Java to Ruby"
I have something to say about the buzz word "From Java to Ruby"kwatch
 
Cより速いRubyプログラム
Cより速いRubyプログラムCより速いRubyプログラム
Cより速いRubyプログラムkwatch
 
Javaより速いLL用テンプレートエンジン
Javaより速いLL用テンプレートエンジンJavaより速いLL用テンプレートエンジン
Javaより速いLL用テンプレートエンジンkwatch
 
Underlaying Technology of Modern O/R Mapper
Underlaying Technology of Modern O/R MapperUnderlaying Technology of Modern O/R Mapper
Underlaying Technology of Modern O/R Mapperkwatch
 

More from kwatch (20)

Migr8.rb チュートリアル
Migr8.rb チュートリアルMigr8.rb チュートリアル
Migr8.rb チュートリアル
 
なんでもID
なんでもIDなんでもID
なんでもID
 
Nippondanji氏に怒られても仕方ない、配列型とJSON型の使い方
Nippondanji氏に怒られても仕方ない、配列型とJSON型の使い方Nippondanji氏に怒られても仕方ない、配列型とJSON型の使い方
Nippondanji氏に怒られても仕方ない、配列型とJSON型の使い方
 
【SQLインジェクション対策】徳丸先生に怒られない、動的SQLの安全な組み立て方
【SQLインジェクション対策】徳丸先生に怒られない、動的SQLの安全な組み立て方【SQLインジェクション対策】徳丸先生に怒られない、動的SQLの安全な組み立て方
【SQLインジェクション対策】徳丸先生に怒られない、動的SQLの安全な組み立て方
 
O/Rマッパーによるトラブルを未然に防ぐ
O/Rマッパーによるトラブルを未然に防ぐO/Rマッパーによるトラブルを未然に防ぐ
O/Rマッパーによるトラブルを未然に防ぐ
 
正規表現リテラルは本当に必要なのか?
正規表現リテラルは本当に必要なのか?正規表現リテラルは本当に必要なのか?
正規表現リテラルは本当に必要なのか?
 
【公開終了】Python4PHPer - PHPユーザのためのPython入門 (Python2.5)
【公開終了】Python4PHPer - PHPユーザのためのPython入門 (Python2.5)【公開終了】Python4PHPer - PHPユーザのためのPython入門 (Python2.5)
【公開終了】Python4PHPer - PHPユーザのためのPython入門 (Python2.5)
 
DBスキーマもバージョン管理したい!
DBスキーマもバージョン管理したい!DBスキーマもバージョン管理したい!
DBスキーマもバージョン管理したい!
 
PHPとJavaScriptにおけるオブジェクト指向を比較する
PHPとJavaScriptにおけるオブジェクト指向を比較するPHPとJavaScriptにおけるオブジェクト指向を比較する
PHPとJavaScriptにおけるオブジェクト指向を比較する
 
SQL上級者こそ知って欲しい、なぜO/Rマッパーが重要か?
SQL上級者こそ知って欲しい、なぜO/Rマッパーが重要か?SQL上級者こそ知って欲しい、なぜO/Rマッパーが重要か?
SQL上級者こそ知って欲しい、なぜO/Rマッパーが重要か?
 
Fantastic DSL in Python
Fantastic DSL in PythonFantastic DSL in Python
Fantastic DSL in Python
 
What is wrong on Test::More? / Test::Moreが抱える問題点とその解決策
What is wrong on Test::More? / Test::Moreが抱える問題点とその解決策What is wrong on Test::More? / Test::Moreが抱える問題点とその解決策
What is wrong on Test::More? / Test::Moreが抱える問題点とその解決策
 
PHP5.5新機能「ジェネレータ」初心者入門
PHP5.5新機能「ジェネレータ」初心者入門PHP5.5新機能「ジェネレータ」初心者入門
PHP5.5新機能「ジェネレータ」初心者入門
 
Pretty Good Branch Strategy for Git/Mercurial
Pretty Good Branch Strategy for Git/MercurialPretty Good Branch Strategy for Git/Mercurial
Pretty Good Branch Strategy for Git/Mercurial
 
Oktest - a new style testing library for Python -
Oktest - a new style testing library for Python -Oktest - a new style testing library for Python -
Oktest - a new style testing library for Python -
 
文字列結合のベンチマークをいろんな処理系でやってみた
文字列結合のベンチマークをいろんな処理系でやってみた文字列結合のベンチマークをいろんな処理系でやってみた
文字列結合のベンチマークをいろんな処理系でやってみた
 
I have something to say about the buzz word "From Java to Ruby"
I have something to say about the buzz word "From Java to Ruby"I have something to say about the buzz word "From Java to Ruby"
I have something to say about the buzz word "From Java to Ruby"
 
Cより速いRubyプログラム
Cより速いRubyプログラムCより速いRubyプログラム
Cより速いRubyプログラム
 
Javaより速いLL用テンプレートエンジン
Javaより速いLL用テンプレートエンジンJavaより速いLL用テンプレートエンジン
Javaより速いLL用テンプレートエンジン
 
Underlaying Technology of Modern O/R Mapper
Underlaying Technology of Modern O/R MapperUnderlaying Technology of Modern O/R Mapper
Underlaying Technology of Modern O/R Mapper
 

Recently uploaded

Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Drew Madelung
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Miguel Araújo
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...apidays
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessPixlogix Infotech
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherRemote DBA Services
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilV3cube
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAndrey Devyatkin
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobeapidays
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?Antenna Manufacturer Coco
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfsudhanshuwaghmare1
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024The Digital Insurer
 

Recently uploaded (20)

+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your Business
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of Brazil
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?What Are The Drone Anti-jamming Systems Technology?
What Are The Drone Anti-jamming Systems Technology?
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024
 

How to make the fastest Router in Python

  • 1. How To Make The Fastest Router In Python Makoto Kuwata https://github.com/kwatch/ PloneConference 2018 Tokyo
  • 2. Abstract ‣ TL;DR ‣ You can make router much faster (max: x10) ‣ Requirements ‣ Python3 ‣ Experience of Web Application Framework (Django, Flask, Plone, etc) ‣ Sample Code ‣ https://github.com/kwatch/router-sample/ ‣ https://github.com/kwatch/keight/tree/python/ (Framework)
  • 3. Table of Contents ‣ What is Router? ‣ Linear Search ‣ Naive / Prefix String / Fixed Path Dictionary ‣ Regular Expression ‣ Naive / Smart / Optimized ‣ State Machine ‣ Conclusion
  • 5. What is Router? ‣ Router is a component of web app framework (WAF). ‣ Router determines request handler according to request method and request path.
 Handler A App Server Handler B Handler C Client : HTTP Request : HTTP Response WSGI App Server Side Router determines "which handler?"
  • 6. Request Handler Example class BooksAPI(RequestHandler): with on.path('/api/books/'): @on('GET') def do_index(self): return {"action": "index"} @on('POST') def do_create(self): return {"action": "create"} .... ← handler class ← URL Path ← request method ← handler func ← request method ← handler func
  • 7. Request Handler Example .... with on.path('/api/books/{id:int}'): @on('GET') def do_show(self, id): return {"action": "show", "id": id} @on('PUT') def do_update(self, id): return {"action": "update", "id": id} @on('DELETE') def do_delete(self, id): return {"action": "delete", "id": id} ← URL Path ← request method ← handler func ← request method ← handler func ← request method ← handler func
  • 8. Mapping Example: Request to Handler mapping_table = [ ## method path class func ("GET" , r"/api/books/" , BooksAPI , do_index), ("POST" , r"/api/books/" , BooksAPI , do_create),
 ("GET" , r"/api/books/(d+)" , BooksAPI , do_show), ("PUT" , r"/api/books/(d+)" , BooksAPI , do_update),
 ("DELETE", r"/api/books/(d+)" , BooksAPI , do_delete),
 ("GET" , r"/api/orders/" , OrdersAPI, do_index),
 ("POST" , r"/api/orders/" , OrdersAPI, do_create),
 ("GET" , r"/api/orders/(d+)", OrdersAPI, do_show), ("PUT" , r"/api/orders/(d+)", OrdersAPI, do_update),
 ("DELETE", r"/api/orders/(d+)", OrdersAPI, do_delete),
 .... ]
  • 9. Mapping Example: Request to Handler mapping_list = [ ## path class {method: func} (r"/api/books/" , BooksAPI , {"GET": do_index, "POST": do_create}), (r"/api/books/(d+)" , BooksAPI , {"GET": do_show, "PUT": do_update, "DELETE": do_delete}),
 (r"/api/orders/" , OrdersAPI, {"GET": do_index, "POST": do_create}), (r"/api/orders/(d+)", OrdersAPI, {"GET": do_show, "PUT": do_update, "DELETE": do_delete}),
 .... ] Same information in different format
  • 10. Router Example >>> router = Router(mapping_list) >>> router.lookup("GET", "/api/books/") (BooksAPI, do_index, []) >>> router.lookup("GET", "/api/books/123") (BooksAPI, do_show, [123])
  • 11. Router Example ### 404 Not Found >>> router.lookup("GET", "/api/books/123/comments") (None, None, None) ### 405 Method Not Allowed >>> router.lookup("POST", "/api/books/123") (BooksAPI, None, [123])
  • 13. Linear Search mapping = [ (r"/api/books/" , BooksAPI , {"GET": do_index, "POST": do_create}), (r"/api/books/(d+)" , BooksAPI , {"GET": do_show, "PUT": do_update, "DELETE": do_delete}),
 (r"/api/orders/" , OrdersAPI, {"GET": do_index, "POST": do_create}), (r"/api/orders/(d+)", OrdersAPI, {"GET": do_show, "PUT": do_update, "DELETE": do_delete}),
 .... ]
  • 14. Router Class class LinearNaiveRouter(Router): def __init__(self, mapping): self._mapping_list = [ (compile_path(path), klass, funcs) for path, klass, funcs in mapping ] def lookup(req_meth, req_path): for rexp, klass, funcs in self._mapping_list: m = rexp.match(req_path) if m: params = [ int(v) for v in m.groups() ] func = funcs.get(req_meth) return klass, func, params return None, None, None
  • 15. Benchmark (Data) mapping_list = [ (r'/api/aaa' , DummyAPI, {"GET": ...}), (r'/api/aaa/{id:int}', DummyAPI, {"GET": ...}), (r'/api/bbb' , DummyAPI, {"GET": ...}), (r'/api/bbb/{id:int}', DummyAPI, {"GET": ...}), .... (r'/api/yyy' , DummyAPI, {"GET": ...}), (r'/api/yyy/{id:int}', DummyAPI, {"GET": ...}), (r'/api/zzz' , DummyAPI, {"GET": ...}), (r'/api/zzz/{id:int}', DummyAPI, {"GET": ...}), ] ### Benchmark environment: ### AWS EC2 t3.nano, Ubuntu 18.04, Python 3.6.6 See sample code for details
  • 16. Benchmark 0 10 20 30 40 50 Linear Naive Seconds (1M Requests) /api/aaa /api/aaa/{id} /api/zzz /api/zzz/{id} sec SlowerFaster very fast on top of list (/api/aaa, /api/aaa/{id}) very slow on bottom of list (/api/zzz, /api/zzz/{id})
  • 17. Pros. Cons. Pros & Cons ✗ Very slow when many mapping entries exist. ✓ Easy to understand and implement
  • 19. Prefix String mapping_list = [ ("/books" , r"/books" , BooksAPI , {"GET": ...}),
 ("/books/" , r"/books/(d+)" , BooksAPI , {"GET": ...}),
 ("/orders" , r"/orders" , OrdersAPI, {"GET": ...}),
 ("/orders/", r"/orders/(d+)", OrdersAPI, {"GET": ...}),
 ] for prefix, rexp, klass, funcs in mapping: if not "/api/orders/123".startswith(prefix): continue m = rexp.match("/api/orders/123") if m: ... Much faster than rexp.match() (replace expensive operation with cheap operation) Prefix strings
  • 20. Router Class def prefix_str(s): return s.split('{', 1)[0] class PrefixLinearRouter(Router): def __init__(self, mapping): for path, klass, funcs in mapping: prefix = prefix_str(path) rexp = compile_path(path) t = (prefix, rexp, klass, funcs) self._mapping_list.append(t) ...
  • 21. Router Class .... def lookup(req_meth, req_path): for prefix, rexp, klass, funcs in self._mapping: if not req_path.startswith(prefix): continue m = rexp.match(req_path) if m: params = [ int(v) for v in m.groups() ] func = funcs.get(req_meth) return klass, func, params return None, None, None Much faster than rexp.match()
  • 22. Benchmark 0 10 20 30 40 50 Linear Naive Prefix Str Seconds (1M Requests) /api/aaa /api/aaa/{id} /api/zzz /api/zzz/{id} sec SlowerFaster about twice as fast as naive implementation
  • 23. Pros. Cons. Pros & Cons ✗ Still slow when many mapping entries exist. ✓ Makes linear search faster. ✓ Easy to understand and implement.
  • 25. Fixed Path Dictionary ## variable path (contains one or more path parameters) mapping_list = [ ("/books" , r"/books" , BooksAPI , {"GET": ...}),
 ("/books/" , r"/books/(d+)" , BooksAPI , {"GET": ...}),
 ("/orders" , r"/orders" , OrdersAPI, {"GET": ...}),
 ("/orders/", r"/orders/(d+)", OrdersAPI, {"GET": ...}),
 ] ## fixed path (contains no path parameters) mapping_dict = { r"/books" : (BooksAPI , {"GET": ...}, []), r"/orders": (OrdersAPI, {"GET": ...}, []), } Use fixed path as key of dict Move fixed path to dict
  • 26. Router Class class FixedLinearRouter(object): def __init__(self, mapping): self._mapping_dict = {} self._mapping_list = [] for path, klass, funcs in mapping: if '{' not in path: self._mapping_dict[path] = (klass, funcs, [])
 else: prefix = prefix_str(path) rexp = compile_path(path) t = (prefix, rexp, klass, funcs) self._mapping_list.append(t) ....
  • 27. Router Class .... def lookup(req_meth, req_path): t = self._mapping_dict.get(req_path) if t: return t for prefix, rexp, klass, funcs in self._mapping_list:
 if not req_path.startswith(prefix) continue m = rexp.match(req_path) if m: params = [ int(v) for v in m.groups() ] func = funcs.get(req_meth) return klass, func, params return None, None, None Much faster than for-loop Number of entries are reduced
  • 28. Benchmark 0 10 20 30 40 50 Linear Naive Prefix Str Fixed Path Seconds (1M Requests) /api/aaa /api/aaa/{id} /api/zzz /api/zzz/{id} sec SlowerFaster super fast on fixed path! three times faster than naive implementation
  • 29. Pros. Cons. Pros & Cons ✗ Still slow when many mapping entries exist. ✓ Makes fixed path search super faster. ✓ Makes variable path search faster,
 because number of entries are reduced. ✓ Easy to understand and implement.
  • 30. Notice ‣ Don't use r"/api/v{version:int}". ‣ because all API paths are regarded as variable path. ‣ Instead, use r"/api/v1", r"/api/v2", ... ‣ in order to increase number of fixed path.
  • 32. Concatenate Regular Expressions mapping_list = { (r"/api/books/(d+)" , BooksAPI , {"GET": ...}),
 (r"/api/orders/(d+)", OrdersAPI, {"GET": ...}),
 (r"/api/users/(d+)" , UsersAPI , {"GET": ...}),
 ] arr = [ r"(?P<_0>^/api/books/(d+)$)", r"(?P<_1>^/api/orders/(d+)$)", r"(?P<_2>^/api/users/(d+)$)", ] all_rexp = re.compile("|".join(arr)) Named groups
  • 33. Matching m = all_rexp.match("/api/users/123") d = m.groupdict() #=> {"_0": None, # "_1": None, # "_2": "/api/users/123"} for k, v in d.items(): if v: i = int(v[1:]) # ex: "_2" -> 2 break _, klass, funcs, pos, nparams = mapping_list[i] arr = m.groups() #=> (None, None, None, None, # "/api/users/123", "123") params = arr[5:6] #=> {"123"}
  • 34. Router Class class NaiveRegexpRouter(Router): def __init__(self, mapping): self._mapping_dict = {} self._mapping_list = [] arr = []; i = 0; pos = 0 for path, klass, funcs in mapping: if '{' not in path: self._mapping_dict[path] = (klass, funcs, []) else: rexp = compile_path(path); pat = rexp.pattern arr.append("(?P<_%s>%s)" % (i, pat)) t = (klass, funcs, pos, path.count('{')) self._mapping_list.append(t) i += 1; pos += 1 + path.count('{') self._all_rexp = re.compile("|".join(arr))
  • 35. Router Class .... def lookup(req_meth, req_path): t = self._mapping_dict.get(req_path) if t: return t m = self._all_rexp.match(req_path) if m: for k, v in m.groupdict().items(): if v: i = int(v[1:]) break klass, funcs, pos, nparams = self._mapping_list[i]
 params = m.groups()[pos:pos+nparams] func = funcs.get(req_meth) return klass, func, params return None, None, None find index in list find param values
  • 36. Benchmark 0 10 20 30 40 50 Linear Regexp Naive Prefix Str Fixed Path Naive Seconds (1M Requests) /api/aaa /api/aaa/{id} /api/zzz /api/zzz/{id} sec SlowerFaster slower than linear search :(
  • 37. Pros. Cons. Pros & Cons ✗ Slower than linear search ✓ Nothing :(
  • 38. Notice $ python3 --version 3.4.5 $ python3 >>> import re >>> arr = ['^/(d+)$'] * 101 >>> re.compile("|".join(arr)) File "/opt/vs/python/3.4.5/lib/python3.4/sre_compile.py", line 579, in compile "sorry, but this version only supports 100 named groups" AssertionError: sorry, but this version only supports 100 named groups Python <= 3.4 limits number of groups in a regular expression, and no work around :(
  • 40. Improved Regular Expression mapping_list = { (r"/api/books/(d+)" , BooksAPI , {"GET": ...}), (r"/api/orders/(d+)" , OrdersAPI , {"GET": ...}), (r"/api/users/(d+)" , UsersAPI , {"GET": ...}), ] arr = [ r"^/api/books/(?:d+)($)", r"^/api/orders/(?:d+)($)", r"^/api/users/(?:d+)($)", ] all_rexp = re.compile("|".join(arr)) m = all_rexp.match("/api/users/123") arr = m.groups() #=> (None, None, "") i = arr.index("") #=> 2 t = mapping_list[i] #=> (r"/api/users/(d+)", # UsersAPI, {"GET": ...}) No more named groups Tuple is much light- weight than dict index() is faster than for-loop
  • 41. Router Class class SmartRegexpRouter(Router): def __init__(self, mapping): self._mapping_dict = {} self._mapping_list = [] arr = [] for path, klass, funcs in mapping: if '{' not in path: self._mapping_dict[path] = (klass, funcs, [])
 else: rexp = compile_path(path); pat = rexp.pattern
 arr.append(pat.replace("(", "(?:") .replace("$", "($)")) t = (rexp, klass, funcs) self._mapping_list.append(t)
 self._all_rexp = re.compile("|".join(arr))
  • 42. Router Class ... def lookup(req_meth, req_path): t = self._mapping_dict.get(req_path) if t: return t m = self._all_rexp.match(req_path) if m: i = m.groups().index("") rexp, klass, funcs = self._mapping_list[i] m2 = rexp.match(req_path) params = [ int(v) for v in m2.groups() ] func = funcs.get(req_meth) return klass, func, params return None, None, None Matching to find index in list Matching to get param values
  • 43. Benchmark 0 10 20 30 40 50 Linear Regexp Naive Prefix Str Fixed Path Naive Smart Seconds (1M Requests) /api/aaa /api/aaa/{id} /api/zzz /api/zzz/{id} sec SlowerFaster Difference between /api/aaa/{id} and /api/zzz/{id} is small
  • 44. Pros. Cons. Pros & Cons ✗ Slower when number of entries is small.
 (due to overhead of twice matching) ✗ May be difficult to debug large regular expression. ✓ Much faster than ever,
 especially when many mapping entries exist.
  • 46. Optimize Regular Expression ## before arr = [r"^/api/books/(?:d+)($)", r"^/api/orders/(?:d+)($)", r"^/api/users/(?:d+)($)"] all_rexp = re.compile("|".join(arr)) ### after arr = [r"^/api", r"(?:", "|".join([r"/books/(?:d+)($)", r"/orders/(?:d+)($)", r"/users/(?:d+)($)"]), r")?"] all_rexp = re.compile("|".join(arr))
  • 47. Router Class class OptimizedRegexpRouter(Router): def __init__(self, mapping): ## Code is too complicated to show here. ## Please download sample code from github. ## https://github.com/kwatch/router-sample/ def lookup(req_meth, req_path): ## nothing changed; same as previous section
  • 48. Benchmark 0 10 20 30 40 50 Linear Regexp Naive Prefix Str Fixed Path Naive Smart Optimized Seconds (1M Requests) /api/aaa /api/aaa/{id} /api/zzz /api/zzz/{id} sec SlowerFaster A little faster on /api/zzz/{id}
  • 49. Pros. Cons. Pros & Cons ✗ Performance benefit is very small (on Python). ✗ Rather difficult to implement and debug. ✓ A little faster than smart regular expression
 when a lot of variable paths exist.
  • 52. State Machine: Definition path = "/api/books" transition = { "api": { "books": { None: (BooksAPI, {"GET": do_index, ...}), }, }, } >>> transition["api"]["books"][None] (BooksAPI, {"GET": do_index, ...}) >>> transition["api"]["users"][None] KeyError: 'users' Use None as terminator (mark of accepted status)
  • 53. State Machine: Definition path = "/api/books/{id:int}" transition = { "api": { "books": { None: (BooksAPI, {"GET": do_index, ...}), 1: { None: (BooksAPI, {"GET": do_show, ...}), }, }, }, } >>> transition["api"]["books"][1][None] (BooksAPI, {"GET": do_index, ...}) 1 represents int parameter, 2 represents str parameter.
  • 54. State Machine: Transition def find(req_path): req_path = req_path.lstrip('/') #ex: "/a/b/c" -> "a/b/c" items = req_path.split('/') #ex: "a/b/c" -> ["a","b","c"] d = transition; params = [] for s in items: if s in d: d = d[s] elif 1 in d: d = d[1]; params.append(int(s)) elif 2 in d: d = d[2]; params.append(str(s)) else: return None if None not in d: return None klass, funcs = d[None] return klass, funcs, params >>> find("/api/books/123") (BooksAPI, {"GET": do_index, ...}, [123])
  • 55. Router Class class StateMachineRouter(Router): def __init__(self, mapping): self._mapping_dict = {} self._mapping_list = [] self._transition = {} for path, klass, funcs in mapping: if '{' not in path: self._mapping_dict[path] = (klass, funcs, [])
 else: self._register(path, klass, funcs)
  • 56. Router Class ... PARAM_TYPES = {"int": 1, "str": 2} def _register(self, path, klass, funcs): ptypes = self.PARAM_TYPES d = self._transition for s in path[1:].split('/'): key = s if s[0] == "{" and s[-1] == "}": ## ex: "{id:int}" -> ("id", "int") pname, ptype = s[1:-1].split(':', 1) key = ptypes.get(ptype) or ptypes["str"] d = d.setdefault(key, {}) d[None] = (klass, funcs)
  • 57. Router Class ... def lookup(self, req_meth, req_path): d = self._transition params = [] for s in req_path[1:].split('/'): if s in d: d = d[s] elif 1 in d: d = d[1]; params.append(int(s)) elif 2 in d: d = d[2]; params.append(str(s)) else: return None, None, None if None in d: klass, funcs = d[None] func = funcs.get(req_meth) return klass, func, params return None, None, None
  • 58. Benchmark 0 10 20 30 40 50 Linear Regexp StateMachine Naive Prefix Str Fixed Path Naive Smart Optimized Seconds (1M Requests) /api/aaa /api/aaa/{id} /api/zzz /api/zzz/{id} sec SlowerFaster /api/aaa/{id} and /api/zzz/{id} are same performance
  • 59. Benchmark (PyPy3.5) 0 10 20 30 40 50 Linear Regexp StateMachine Naive Prefix Str Fixed Path Naive Smart Optimized Seconds (1M Requests) /api/aaa /api/aaa/{id} /api/zzz /api/zzz/{id} sec SlowerFaster Regular Expression is very slow in PyPy3.5 String operation is very fast because JIT friendly
  • 60. Benchmark (PyPy3.5) 0 1 2 3 4 5 Linear Regexp StateMachine Naive Prefix Str Fixed Path Naive Smart Optimized Seconds (1M Requests) /api/aaa /api/aaa/{id} /api/zzz /api/zzz/{id} sec SlowerFaster The fastest method due to Regexp-free (= JIT friendly) A little slower than StateMachine because containing Regexp
  • 61. Pros. Cons. Pros & Cons ✗ Not support complicated pattern. ✗ Requires some effort to support URL path suffix (ex: /api/books/123.json). ✓ Performance champion in routing area. ✓ Much faster in PyPy3.5, due to regexp-free. JIT friendly!
  • 63. Conclusion ‣ Linear Search is slow. ‣ Prefix string and Fixed path dict make it faster. ‣ Regular expression is very fast. ‣ Do your best to avoid named group (or named caption). ‣ State Machine is the fastest method in Python. ‣ Especially in PyPy3, due to regexp-free (= JIT friendly).
  • 65. My Products ‣ Benchmarker.py Awesome benchmarking utility. https://pythonhosted.org/Benchmarker/ ‣ Oktest.py New generation of testing framework. https://pythonhosted.org/Oktest/ ‣ PyTenjin Super fast and feature-rich template engine. https://www.kuwata-lab.com/tenjin/pytenjin-users-guide.html https://bit.ly/tenjinpy_slide (presentation)