Skip to content

Instantly share code, notes, and snippets.

@Blacksmoke16
Last active January 14, 2022 07:19
Embed
What would you like to do?
Crystal router benchmarks
# 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
/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
/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