Crystal router benchmarks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Based on https://github.com/oprypin/retour/blob/master/examples/benchmark/benchmark.cr | |
require "benchmark" | |
require "colorize" | |
require "radix" | |
require "amber_router" | |
require "athena-routing" | |
require "../../src/http" | |
annotation Route; end | |
module RouteApp | |
extend self | |
@[Route("/get")] | |
def root(p) | |
"/get" | |
end | |
@[Route("/get/users/:id")] | |
def users(p) | |
"/get/users/<#{p["id"]}>" | |
end | |
@[Route("/get/users/:id/books")] | |
def users_books(p) | |
"/get/users/<#{p["id"]}>/books" | |
end | |
@[Route("/get/books/:id")] | |
def books(p) | |
"/get/books/<#{p["id"]}>" | |
end | |
@[Route("/get/books/:id/chapters")] | |
def book_chapters(p) | |
"/get/books/<#{p["id"]}>/chapters" | |
end | |
@[Route("/get/books/:id/authors")] | |
def book_authors(p) | |
"/get/books/<#{p["id"]}>/authors" | |
end | |
@[Route("/get/books/:id/pictures")] | |
def book_pictures(p) | |
"/get/books/<#{p["id"]}>/pictures" | |
end | |
@[Route("/get/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z")] | |
def alphabet(p) | |
"/get/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z" | |
end | |
@[Route("/get/var/:b/:c/:d/:e/:f/:g/:h/:i/:j/:k/:l/:m/:n/:o/:p/:q/:r/:s/:t/:u/:v/:w/:x/:y/:z")] | |
def variable_alphabet(p) | |
"/get/var/<#{p["b"]}>/<#{p["c"]}>/<#{p["d"]}>/<#{p["e"]}>/<#{p["f"]}>/<#{p["g"]}>/<#{p["h"]}>/<#{p["i"]}>/<#{p["j"]}>/<#{p["k"]}>/<#{p["l"]}>/<#{p["m"]}>/<#{p["n"]}>/<#{p["o"]}>/<#{p["p"]}>/<#{p["q"]}>/<#{p["r"]}>/<#{p["s"]}>/<#{p["t"]}>/<#{p["u"]}>/<#{p["v"]}>/<#{p["w"]}>/<#{p["x"]}>/<#{p["y"]}>/<#{p["z"]}>" | |
end | |
@[Route("/get/foobarbizfoobarbizfoobarbizfoobarbizfoobarbizbat/:id")] | |
def foobar_bat(p) | |
"/get/foobarbizfoobarbizfoobarbizfoobarbizfoobarbizbat/<#{p["id"]}>" | |
end | |
@[Route("/get/foobarbizfoobarbizfoobarbizfoobarbizfoobarbizbom/:id")] | |
def foobar_bom(p) | |
"/get/foobarbizfoobarbizfoobarbizfoobarbizfoobarbizbom/<#{p["id"]}>" | |
end | |
@[Route("/post/*rest")] | |
def catchall(p) | |
"/post/<#{p["rest"]}>" | |
end | |
@[Route("/put/products/*slug/dp/:id")] | |
def amazon_style_url(p) | |
"/put/products/<#{p["slug"]?}>/dp/<#{p["id"]?}>" | |
end | |
@[Route("/get/test/:id", constraints: {:id => /foo_[0-9]+/})] | |
def requirement_path(p) | |
"/get/test/<#{p["id"]}>" | |
end | |
def get(payload, params) | |
{% begin %} | |
case payload | |
{% for method in RouteApp.methods %} | |
{% for ann in method.annotations(Route) %} | |
when {{method.name.symbolize}} | |
{{method.name.id}}(params) | |
{% end %} | |
{% end %} | |
end | |
{% end %} | |
end | |
end | |
amber_router = Amber::Router::RouteSet(Symbol).new | |
radix_router = Radix::Tree(Symbol).new | |
{% for method in RouteApp.methods %} | |
{% for ann in method.annotations(Route) %} | |
amber_router.add({{*ann.args}}, {{method.name.symbolize}}, {{**ann.named_args}}) | |
radix_router.add({{*ann.args}}, {{method.name.symbolize}}) | |
{% end %} | |
{% end %} | |
module RetourApp | |
extend self | |
include Retour::HTTPRouter | |
@[Retour::Get("/get")] | |
def root | |
"/get" | |
end | |
@[Retour::Get("/get/users/{id}")] | |
def users(id) | |
"/get/users/<#{id}>" | |
end | |
@[Retour::Get("/get/users/{id}/books")] | |
def users_books(id) | |
"/get/users/<#{id}>/books" | |
end | |
@[Retour::Get("/get/books/{id}")] | |
def books(id) | |
"/get/books/<#{id}>" | |
end | |
@[Retour::Get("/get/books/{id}/chapters")] | |
def book_chapters(id) | |
"/get/books/<#{id}>/chapters" | |
end | |
@[Retour::Get("/get/books/{id}/authors")] | |
def book_authors(id) | |
"/get/books/<#{id}>/authors" | |
end | |
@[Retour::Get("/get/books/{id}/pictures")] | |
def book_pictures(id) | |
"/get/books/<#{id}>/pictures" | |
end | |
@[Retour::Get("/get/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z")] | |
def alphabet | |
"/get/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z" | |
end | |
@[Retour::Get("/get/var/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}/{r}/{s}/{t}/{u}/{v}/{w}/{x}/{y}/{z}")] | |
def variable_alphabet(b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z) | |
"/get/var/<#{b}>/<#{c}>/<#{d}>/<#{e}>/<#{f}>/<#{g}>/<#{h}>/<#{i}>/<#{j}>/<#{k}>/<#{l}>/<#{m}>/<#{n}>/<#{o}>/<#{p}>/<#{q}>/<#{r}>/<#{s}>/<#{t}>/<#{u}>/<#{v}>/<#{w}>/<#{x}>/<#{y}>/<#{z}>" | |
end | |
@[Retour::Get("/get/foobarbizfoobarbizfoobarbizfoobarbizfoobarbizbat/{id}")] | |
def foobar_bat(id) | |
"/get/foobarbizfoobarbizfoobarbizfoobarbizfoobarbizbat/<#{id}>" | |
end | |
@[Retour::Get("/get/foobarbizfoobarbizfoobarbizfoobarbizfoobarbizbom/{id}")] | |
def foobar_bom(id) | |
"/get/foobarbizfoobarbizfoobarbizfoobarbizfoobarbizbom/<#{id}>" | |
end | |
@[Retour::Get("/post/{rest:.*}")] | |
def catchall(rest) | |
"/post/<#{rest}>" | |
end | |
@[Retour::Get("/put/products/{slug:.*}/dp/{id}")] | |
def amazon_style_url(slug, id) | |
"/put/products/<#{slug}>/dp/<#{id}>" | |
end | |
@[Retour::Get("/get/test/{id:foo_[0-9]+}")] | |
def requirement_path(id) | |
"/get/test/<#{id}>" | |
end | |
end | |
module AthenaApp | |
extend self | |
@[ARTA::Route("/get")] | |
def root(p) | |
"/get" | |
end | |
@[ARTA::Route("/get/users/{id}")] | |
def users(p) | |
"/get/users/<#{p["id"]}>" | |
end | |
@[ARTA::Route("/get/users/{id}/books")] | |
def users_books(p) | |
"/get/users/<#{p["id"]}>/books" | |
end | |
@[ARTA::Route("/get/books/{id}")] | |
def books(p) | |
"/get/books/<#{p["id"]}>" | |
end | |
@[ARTA::Route("/get/books/{id}/chapters")] | |
def book_chapters(p) | |
"/get/books/<#{p["id"]}>/chapters" | |
end | |
@[ARTA::Route("/get/books/{id}/authors")] | |
def book_authors(p) | |
"/get/books/<#{p["id"]}>/authors" | |
end | |
@[ARTA::Route("/get/books/{id}/pictures")] | |
def book_pictures(p) | |
"/get/books/<#{p["id"]}>/pictures" | |
end | |
@[ARTA::Route("/get/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z")] | |
def alphabet(p) | |
"/get/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z" | |
end | |
@[ARTA::Route("/get/var/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}/{r}/{s}/{t}/{u}/{v}/{w}/{x}/{y}/{z}")] | |
def variable_alphabet(p) | |
"/get/var/<#{p["b"]}>/<#{p["c"]}>/<#{p["d"]}>/<#{p["e"]}>/<#{p["f"]}>/<#{p["g"]}>/<#{p["h"]}>/<#{p["i"]}>/<#{p["j"]}>/<#{p["k"]}>/<#{p["l"]}>/<#{p["m"]}>/<#{p["n"]}>/<#{p["o"]}>/<#{p["p"]}>/<#{p["q"]}>/<#{p["r"]}>/<#{p["s"]}>/<#{p["t"]}>/<#{p["u"]}>/<#{p["v"]}>/<#{p["w"]}>/<#{p["x"]}>/<#{p["y"]}>/<#{p["z"]}>" | |
end | |
@[ARTA::Route("/get/foobarbizfoobarbizfoobarbizfoobarbizfoobarbizbat/{id}")] | |
def foobar_bat(p) | |
"/get/foobarbizfoobarbizfoobarbizfoobarbizfoobarbizbat/<#{p["id"]}>" | |
end | |
@[ARTA::Route("/get/foobarbizfoobarbizfoobarbizfoobarbizfoobarbizbom/{id}")] | |
def foobar_bom(p) | |
"/get/foobarbizfoobarbizfoobarbizfoobarbizfoobarbizbom/<#{p["id"]}>" | |
end | |
@[ARTA::Route("/post/{rest<.*>}")] | |
def catchall(p) | |
"/post/<#{p["rest"]}>" | |
end | |
@[ARTA::Route("/put/products/{slug<.*>}/dp/{id}")] | |
def amazon_style_url(p) | |
"/put/products/<#{p["slug"]?}>/dp/<#{p["id"]?}>" | |
end | |
@[ARTA::Route("/get/test/{id<foo_[0-9]+>}")] | |
def requirement_path(p) | |
"/get/test/<#{p["id"]}>" | |
end | |
def get(params) | |
{% begin %} | |
case params["_route"] | |
{% for method in AthenaApp.methods %} | |
{% for ann in method.annotations(ARTA::Route) %} | |
when {{method.name.stringify}} | |
{{method.name.id}}(params) | |
{% end %} | |
{% end %} | |
end | |
{% end %} | |
end | |
end | |
athena_routes = ART::RouteCollection.new | |
{% for method in AthenaApp.methods %} | |
{% for ann in method.annotations(ARTA::Route) %} | |
athena_routes.add {{method.name.stringify}}, ART::Route.new {{ann[0]}} | |
{% end %} | |
{% end %} | |
ART.compile athena_routes | |
ATHENA_MATCHER = ART::Matcher::URLMatcher.new ART::RequestContext.new | |
{ | |
{"root", "/get", "/get"}, | |
{"deep", "/get/books/23/chapters", "/get/books/<23>/chapters"}, | |
{"wrong", "/get/books/23/pages", nil}, | |
{"many segments", "/get/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z", "/get/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z"}, | |
{"many variables", "/get/var/2/3/4/5/6/7/8/9/0/1/2/3/4/5/6/7/8/9/0/1/2/3/4/5/6", "/get/var/<2>/<3>/<4>/<5>/<6>/<7>/<8>/<9>/<0>/<1>/<2>/<3>/<4>/<5>/<6>/<7>/<8>/<9>/<0>/<1>/<2>/<3>/<4>/<5>/<6>"}, | |
{"long segments", "/get/foobarbizfoobarbizfoobarbizfoobarbizfoobarbizbat/3", "/get/foobarbizfoobarbizfoobarbizfoobarbizfoobarbizbat/<3>"}, | |
{"catchall route", "/post/products/23/reviews", "/post/<products/23/reviews>"}, | |
{"globs with suffix match", "/put/products/Winter-Windproof-Trapper-Hat/dp/B01J7DAMCQ", "/put/products/<Winter-Windproof-Trapper-Hat>/dp/<B01J7DAMCQ>"}, | |
{"route with a valid constraint", "/get/test/foo_99", "/get/test/<foo_99>"}, | |
{"route with an invalid constraint", "/get/test/foo_bar", nil}, | |
}.each_with_index do |(name, path, expected), ti| | |
puts path.colorize(:white).bold | |
Benchmark.ips(*{% if flag?(:release) %}{3, 1}{% else %}{0.01, 0.001}{% end %}) do |x| | |
x.report("retour") do | |
actual = RetourApp._get(path).as?(String) | |
raise "#{actual.inspect} != #{expected.inspect}" if actual != expected | |
end | |
x.report("amber") do | |
actual = amber_router.find(path) | |
actual = actual.found? ? RouteApp.get(actual.payload, actual.params) : nil | |
raise "#{actual.inspect} != #{expected.inspect}" if actual != expected | |
end | |
x.report("Athena::Routing") do | |
actual = ATHENA_MATCHER.match? path | |
actual = actual ? AthenaApp.get actual : nil | |
raise "#{actual.inspect} != #{expected.inspect}" if actual != expected | |
end | |
x.report("radix") do | |
actual = radix_router.find(path) | |
actual = actual.found? ? RouteApp.get(actual.payload, actual.params) : nil | |
raise "#{actual.inspect} != #{expected.inspect}" if actual != expected | |
end if ti < 7 | |
end | |
puts | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/get | |
retour 2.88M (346.72ns) (± 1.98%) 672B/op 4.54× slower | |
amber 3.22M (310.50ns) (± 1.39%) 387B/op 4.07× slower | |
Athena::Routing 13.10M ( 76.32ns) (± 1.01%) 64.0B/op fastest | |
radix 6.75M (148.25ns) (± 1.20%) 144B/op 1.94× slower | |
/get/books/23/chapters | |
retour 1.15M (871.38ns) (± 1.21%) 1.02kB/op 2.41× slower | |
amber 1.05M (953.19ns) (± 1.62%) 1.24kB/op 2.64× slower | |
Athena::Routing 2.77M (361.15ns) (± 1.32%) 321B/op fastest | |
radix 1.61M (621.89ns) (± 0.84%) 480B/op 1.72× slower | |
/get/books/23/pages | |
retour 1.02M (983.11ns) (± 1.16%) 672B/op 8.89× slower | |
amber 1.90M (526.56ns) (± 3.80%) 596B/op 4.76× slower | |
Athena::Routing 9.04M (110.58ns) (± 1.10%) 64.0B/op fastest | |
radix 1.72M (582.47ns) (± 1.45%) 416B/op 5.27× slower | |
/get/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z | |
retour 1.38M (726.59ns) (± 1.46%) 672B/op 9.08× slower | |
amber 335.60k ( 2.98µs) (± 1.16%) 4.05kB/op 37.24× slower | |
Athena::Routing 12.50M ( 80.02ns) (± 1.74%) 64.0B/op fastest | |
radix 1.95M (512.78ns) (± 2.87%) 353B/op 6.41× slower | |
/get/var/2/3/4/5/6/7/8/9/0/1/2/3/4/5/6/7/8/9/0/1/2/3/4/5/6 | |
retour 190.67k ( 5.24µs) (± 3.11%) 8.6kB/op 2.33× slower | |
amber 106.45k ( 9.39µs) (±16.34%) 13.6kB/op 4.18× slower | |
Athena::Routing 445.19k ( 2.25µs) (± 7.96%) 2.29kB/op fastest | |
radix 338.69k ( 2.95µs) (± 1.61%) 2.86kB/op 1.31× slower | |
/get/foobarbizfoobarbizfoobarbizfoobarbizfoobarbizbat/3 | |
retour 991.67k ( 1.01µs) (± 1.25%) 1.04kB/op 2.70× slower | |
amber 1.24M (808.95ns) (± 1.45%) 1.14kB/op 2.16× slower | |
Athena::Routing 2.67M (374.14ns) (± 2.26%) 368B/op fastest | |
radix 1.15M (868.14ns) (± 1.52%) 592B/op 2.32× slower | |
/post/products/23/reviews | |
retour 1.01M (988.53ns) (± 1.45%) 1.09kB/op 2.71× slower | |
amber 1.03M (968.04ns) (± 1.55%) 1.41kB/op 2.65× slower | |
Athena::Routing 2.74M (365.42ns) (± 1.05%) 368B/op fastest | |
radix 2.57M (389.27ns) (± 5.41%) 449B/op 1.07× slower | |
/put/products/Winter-Windproof-Trapper-Hat/dp/B01J7DAMCQ | |
retour 616.84k ( 1.62µs) (± 2.12%) 1.43kB/op 3.37× slower | |
amber 798.24k ( 1.25µs) (± 1.61%) 1.62kB/op 2.60× slower | |
Athena::Routing 2.08M (481.41ns) (± 3.43%) 434B/op fastest | |
/get/test/foo_99 | |
retour 930.88k ( 1.07µs) (± 1.51%) 1.04kB/op 2.87× slower | |
amber 1.14M (875.70ns) (± 3.72%) 1.11kB/op 2.34× slower | |
Athena::Routing 2.67M (374.35ns) (± 3.48%) 337B/op fastest | |
/get/test/foo_bar | |
retour 1.24M (809.37ns) (± 1.00%) 672B/op 7.80× slower | |
amber 2.04M (491.07ns) (± 3.13%) 498B/op 4.73× slower | |
Athena::Routing 9.63M (103.81ns) (± 1.34%) 64.0B/op fastest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/get | |
retour 2.87M (348.53ns) (± 3.65%) 672B/op 2.35× slower | |
amber 3.38M (296.18ns) (± 5.70%) 386B/op 2.00× slower | |
Athena::Routing 4.08M (245.25ns) (± 3.23%) 385B/op 1.65× slower | |
radix 6.74M (148.45ns) (± 1.32%) 144B/op fastest | |
/get/books/23/chapters | |
retour 1.16M (865.14ns) (± 1.22%) 1.02kB/op 1.48× slower | |
amber 1.09M (914.88ns) (± 3.07%) 1.24kB/op 1.57× slower | |
Athena::Routing 1.71M (583.19ns) (± 1.53%) 672B/op fastest | |
radix 1.60M (626.00ns) (± 1.94%) 480B/op 1.07× slower | |
/get/books/23/pages | |
retour 997.14k ( 1.00µs) (± 1.42%) 672B/op 3.02× slower | |
amber 2.01M (496.63ns) (± 1.99%) 594B/op 1.50× slower | |
Athena::Routing 3.01M (331.92ns) (±10.41%) 416B/op fastest | |
radix 1.63M (613.92ns) (± 3.57%) 417B/op 1.85× slower | |
/get/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z | |
retour 1.37M (727.88ns) (± 2.68%) 672B/op 1.69× slower | |
amber 342.52k ( 2.92µs) (± 2.43%) 4.04kB/op 6.79× slower | |
Athena::Routing 2.33M (429.72ns) (± 1.28%) 400B/op fastest | |
radix 1.95M (511.53ns) (± 1.23%) 352B/op 1.19× slower | |
/get/var/2/3/4/5/6/7/8/9/0/1/2/3/4/5/6/7/8/9/0/1/2/3/4/5/6 | |
retour 189.87k ( 5.27µs) (± 1.94%) 8.61kB/op 2.12× slower | |
amber 119.17k ( 8.39µs) (± 2.15%) 13.7kB/op 3.38× slower | |
Athena::Routing 402.21k ( 2.49µs) (± 1.16%) 2.61kB/op fastest | |
radix 326.44k ( 3.06µs) (± 4.79%) 2.86kB/op 1.23× slower | |
/get/foobarbizfoobarbizfoobarbizfoobarbizfoobarbizbat/3 | |
retour 964.05k ( 1.04µs) (± 2.62%) 1.04kB/op 1.44× slower | |
amber 1.23M (814.65ns) (± 1.93%) 1.14kB/op 1.13× slower | |
Athena::Routing 1.39M (719.15ns) (± 1.13%) 704B/op fastest | |
radix 1.17M (853.92ns) (± 1.43%) 592B/op 1.19× slower | |
/post/products/23/reviews | |
retour 1.02M (976.10ns) (± 2.66%) 1.09kB/op 2.19× slower | |
amber 1.04M (962.11ns) (± 2.02%) 1.41kB/op 2.16× slower | |
Athena::Routing 1.55M (643.31ns) (± 3.20%) 720B/op 1.44× slower | |
radix 2.24M (446.25ns) (±10.79%) 450B/op fastest | |
/put/products/Winter-Windproof-Trapper-Hat/dp/B01J7DAMCQ | |
retour 629.89k ( 1.59µs) (± 2.50%) 1.43kB/op 1.94× slower | |
amber 796.07k ( 1.26µs) (± 4.65%) 1.62kB/op 1.54× slower | |
Athena::Routing 1.22M (816.34ns) (± 1.59%) 768B/op fastest | |
/get/test/foo_99 | |
retour 921.98k ( 1.08µs) (± 5.42%) 1.04kB/op 1.85× slower | |
amber 1.14M (877.25ns) (± 1.10%) 1.11kB/op 1.50× slower | |
Athena::Routing 1.71M (586.06ns) (± 1.54%) 673B/op fastest | |
/get/test/foo_bar | |
retour 1.25M (801.39ns) (± 0.82%) 672B/op 2.45× slower | |
amber 2.06M (484.73ns) (± 1.40%) 498B/op 1.48× slower | |
Athena::Routing 3.06M (326.77ns) (± 0.92%) 402B/op fastest |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment