Benchmark against httprouter, reitit is ~3-6x slower

This commit is contained in:
Tommi Reiman 2018-11-03 19:37:26 +02:00
parent 2c58205e85
commit bd76fd6d0e

View file

@ -2,7 +2,8 @@
(:require [criterium.core :as cc] (:require [criterium.core :as cc]
[reitit.perf-utils :refer :all] [reitit.perf-utils :refer :all]
[reitit.ring :as ring] [reitit.ring :as ring]
[clojure.string :as str])) [clojure.string :as str]
[reitit.core :as r]))
;; ;;
;; start repl with `lein perf repl` ;; start repl with `lein perf repl`
@ -31,46 +32,266 @@
(mapv (fn [[p d]] (if (= path p) [p (assoc d method h)] [p d])) routes) (mapv (fn [[p d]] (if (= path p) [p (assoc d method h)] [p d])) routes)
(conj routes [path {method h}])))) (conj routes [path {method h}]))))
(def routes [{"POST", "/1/classes/:className"}, (def routes
{"GET", "/1/classes/:className/:objectId"}, [{"GET" "/authorizations"}
{"PUT", "/1/classes/:className/:objectId"}, {"GET" "/authorizations/:id"}
{"GET", "/1/classes/:className"}, {"POST" "/authorizations"}
{"DELETE", "/1/classes/:className/:objectId"}, ;; {"PUT" "/authorizations/clients/:client_id"}
;; {"PATCH" "/authorizations/:id"}
{"DELETE" "/authorizations/:id"}
{"GET" "/applications/:client_id/tokens/:access_token"}
{"DELETE" "/applications/:client_id/tokens"}
{"DELETE" "/applications/:client_id/tokens/:access_token"}
;; Users ;; Activity
{"POST", "/1/users"}, {"GET" "/events"}
{"GET", "/1/login"}, {"GET" "/repos/:owner/:repo/events"}
{"GET", "/1/users/:objectId"}, {"GET" "/networks/:owner/:repo/events"}
{"PUT", "/1/users/:objectId"}, {"GET" "/orgs/:org/events"}
{"GET", "/1/users"}, {"GET" "/users/:user/received_events"}
{"DELETE", "/1/users/:objectId"}, {"GET" "/users/:user/received_events/public"}
{"POST", "/1/requestPasswordReset"}, {"GET" "/users/:user/events"}
{"GET" "/users/:user/events/public"}
{"GET" "/users/:user/events/orgs/:org"}
{"GET" "/feeds"}
{"GET" "/notifications"}
{"GET" "/repos/:owner/:repo/notifications"}
{"PUT" "/notifications"}
{"PUT" "/repos/:owner/:repo/notifications"}
{"GET" "/notifications/threads/:id"}
;; {"PATCH" "/notifications/threads/:id"}
{"GET" "/notifications/threads/:id/subscription"}
{"PUT" "/notifications/threads/:id/subscription"}
{"DELETE" "/notifications/threads/:id/subscription"}
{"GET" "/repos/:owner/:repo/stargazers"}
{"GET" "/users/:user/starred"}
{"GET" "/user/starred"}
{"GET" "/user/starred/:owner/:repo"}
{"PUT" "/user/starred/:owner/:repo"}
{"DELETE" "/user/starred/:owner/:repo"}
{"GET" "/repos/:owner/:repo/subscribers"}
{"GET" "/users/:user/subscriptions"}
{"GET" "/user/subscriptions"}
{"GET" "/repos/:owner/:repo/subscription"}
{"PUT" "/repos/:owner/:repo/subscription"}
{"DELETE" "/repos/:owner/:repo/subscription"}
{"GET" "/user/subscriptions/:owner/:repo"}
{"PUT" "/user/subscriptions/:owner/:repo"}
{"DELETE" "/user/subscriptions/:owner/:repo"}
;; Roles ;; Gists
{"POST", "/1/roles"}, {"GET" "/users/:user/gists"}
{"GET", "/1/roles/:objectId"}, {"GET" "/gists"}
{"PUT", "/1/roles/:objectId"}, ;; {"GET" "/gists/public"}
{"GET", "/1/roles"}, ;; {"GET" "/gists/starred"}
{"DELETE", "/1/roles/:objectId"}, {"GET" "/gists/:id"}
{"POST" "/gists"}
;; {"PATCH" "/gists/:id"}
{"PUT" "/gists/:id/star"}
{"DELETE" "/gists/:id/star"}
{"GET" "/gists/:id/star"}
{"POST" "/gists/:id/forks"}
{"DELETE" "/gists/:id"}
;; Files ;; Git Data
{"POST", "/1/files/:fileName"}, {"GET" "/repos/:owner/:repo/git/blobs/:sha"}
{"POST" "/repos/:owner/:repo/git/blobs"}
{"GET" "/repos/:owner/:repo/git/commits/:sha"}
{"POST" "/repos/:owner/:repo/git/commits"}
;; {"GET" "/repos/:owner/:repo/git/refs/*ref"}
{"GET" "/repos/:owner/:repo/git/refs"}
{"POST" "/repos/:owner/:repo/git/refs"}
;; {"PATCH" "/repos/:owner/:repo/git/refs/*ref"}
;; {"DELETE" "/repos/:owner/:repo/git/refs/*ref"}
{"GET" "/repos/:owner/:repo/git/tags/:sha"}
{"POST" "/repos/:owner/:repo/git/tags"}
{"GET" "/repos/:owner/:repo/git/trees/:sha"}
{"POST" "/repos/:owner/:repo/git/trees"}
;; Analytics ;; Issues
{"POST", "/1/events/:eventName"}, {"GET" "/issues"}
{"GET" "/user/issues"}
{"GET" "/orgs/:org/issues"}
{"GET" "/repos/:owner/:repo/issues"}
{"GET" "/repos/:owner/:repo/issues/:number"}
{"POST" "/repos/:owner/:repo/issues"}
;; {"PATCH" "/repos/:owner/:repo/issues/:number"}
{"GET" "/repos/:owner/:repo/assignees"}
{"GET" "/repos/:owner/:repo/assignees/:assignee"}
{"GET" "/repos/:owner/:repo/issues/:number/comments"}
;; {"GET" "/repos/:owner/:repo/issues/comments"}
;; {"GET" "/repos/:owner/:repo/issues/comments/:id"}
{"POST" "/repos/:owner/:repo/issues/:number/comments"}
;; {"PATCH" "/repos/:owner/:repo/issues/comments/:id"}
;; {"DELETE" "/repos/:owner/:repo/issues/comments/:id"}
{"GET" "/repos/:owner/:repo/issues/:number/events"}
;; {"GET" "/repos/:owner/:repo/issues/events"}
;; {"GET" "/repos/:owner/:repo/issues/events/:id"}
{"GET" "/repos/:owner/:repo/labels"}
{"GET" "/repos/:owner/:repo/labels/:name"}
{"POST" "/repos/:owner/:repo/labels"}
;; {"PATCH" "/repos/:owner/:repo/labels/:name"}
{"DELETE" "/repos/:owner/:repo/labels/:name"}
{"GET" "/repos/:owner/:repo/issues/:number/labels"}
{"POST" "/repos/:owner/:repo/issues/:number/labels"}
{"DELETE" "/repos/:owner/:repo/issues/:number/labels/:name"}
{"PUT" "/repos/:owner/:repo/issues/:number/labels"}
{"DELETE" "/repos/:owner/:repo/issues/:number/labels"}
{"GET" "/repos/:owner/:repo/milestones/:number/labels"}
{"GET" "/repos/:owner/:repo/milestones"}
{"GET" "/repos/:owner/:repo/milestones/:number"}
{"POST" "/repos/:owner/:repo/milestones"}
;; {"PATCH" "/repos/:owner/:repo/milestones/:number"}
{"DELETE" "/repos/:owner/:repo/milestones/:number"}
;; Push Notifications ;; Miscellaneous
{"POST", "/1/push"}, {"GET" "/emojis"}
{"GET" "/gitignore/templates"}
{"GET" "/gitignore/templates/:name"}
{"POST" "/markdown"}
{"POST" "/markdown/raw"}
{"GET" "/meta"}
{"GET" "/rate_limit"}
;; Installations ;; Organizations
{"POST", "/1/installations"}, {"GET" "/users/:user/orgs"}
{"GET", "/1/installations/:objectId"}, {"GET" "/user/orgs"}
{"PUT", "/1/installations/:objectId"}, {"GET" "/orgs/:org"}
{"GET", "/1/installations"}, ;; {"PATCH" "/orgs/:org"}
{"DELETE", "/1/installations/:objectId"}, {"GET" "/orgs/:org/members"}
{"GET" "/orgs/:org/members/:user"}
{"DELETE" "/orgs/:org/members/:user"}
{"GET" "/orgs/:org/public_members"}
{"GET" "/orgs/:org/public_members/:user"}
{"PUT" "/orgs/:org/public_members/:user"}
{"DELETE" "/orgs/:org/public_members/:user"}
{"GET" "/orgs/:org/teams"}
{"GET" "/teams/:id"}
{"POST" "/orgs/:org/teams"}
;; {"PATCH" "/teams/:id"}
{"DELETE" "/teams/:id"}
{"GET" "/teams/:id/members"}
{"GET" "/teams/:id/members/:user"}
{"PUT" "/teams/:id/members/:user"}
{"DELETE" "/teams/:id/members/:user"}
{"GET" "/teams/:id/repos"}
{"GET" "/teams/:id/repos/:owner/:repo"}
{"PUT" "/teams/:id/repos/:owner/:repo"}
{"DELETE" "/teams/:id/repos/:owner/:repo"}
{"GET" "/user/teams"}
;; Cloud Functions ;; Pull Requests
{"POST", "/1/functions"}]) {"GET" "/repos/:owner/:repo/pulls"}
{"GET" "/repos/:owner/:repo/pulls/:number"}
{"POST" "/repos/:owner/:repo/pulls"}
;; {"PATCH" "/repos/:owner/:repo/pulls/:number"}
{"GET" "/repos/:owner/:repo/pulls/:number/commits"}
{"GET" "/repos/:owner/:repo/pulls/:number/files"}
{"GET" "/repos/:owner/:repo/pulls/:number/merge"}
{"PUT" "/repos/:owner/:repo/pulls/:number/merge"}
{"GET" "/repos/:owner/:repo/pulls/:number/comments"}
;; {"GET" "/repos/:owner/:repo/pulls/comments"}
;; {"GET" "/repos/:owner/:repo/pulls/comments/:number"}
{"PUT" "/repos/:owner/:repo/pulls/:number/comments"}
;; {"PATCH" "/repos/:owner/:repo/pulls/comments/:number"}
;; {"DELETE" "/repos/:owner/:repo/pulls/comments/:number"}
;; Repositories
{"GET" "/user/repos"}
{"GET" "/users/:user/repos"}
{"GET" "/orgs/:org/repos"}
{"GET" "/repositories"}
{"POST" "/user/repos"}
{"POST" "/orgs/:org/repos"}
{"GET" "/repos/:owner/:repo"}
;; {"PATCH" "/repos/:owner/:repo"}
{"GET" "/repos/:owner/:repo/contributors"}
{"GET" "/repos/:owner/:repo/languages"}
{"GET" "/repos/:owner/:repo/teams"}
{"GET" "/repos/:owner/:repo/tags"}
{"GET" "/repos/:owner/:repo/branches"}
{"GET" "/repos/:owner/:repo/branches/:branch"}
{"DELETE" "/repos/:owner/:repo"}
{"GET" "/repos/:owner/:repo/collaborators"}
{"GET" "/repos/:owner/:repo/collaborators/:user"}
{"PUT" "/repos/:owner/:repo/collaborators/:user"}
{"DELETE" "/repos/:owner/:repo/collaborators/:user"}
{"GET" "/repos/:owner/:repo/comments"}
{"GET" "/repos/:owner/:repo/commits/:sha/comments"}
{"POST" "/repos/:owner/:repo/commits/:sha/comments"}
{"GET" "/repos/:owner/:repo/comments/:id"}
;; {"PATCH" "/repos/:owner/:repo/comments/:id"}
{"DELETE" "/repos/:owner/:repo/comments/:id"}
{"GET" "/repos/:owner/:repo/commits"}
{"GET" "/repos/:owner/:repo/commits/:sha"}
{"GET" "/repos/:owner/:repo/readme"}
;; {"GET" "/repos/:owner/:repo/contents/*path"}
;; {"PUT" "/repos/:owner/:repo/contents/*path"}
;; {"DELETE" "/repos/:owner/:repo/contents/*path"}
;; {"GET" "/repos/:owner/:repo/:archive_format/:ref"}
{"GET" "/repos/:owner/:repo/keys"}
{"GET" "/repos/:owner/:repo/keys/:id"}
{"POST" "/repos/:owner/:repo/keys"}
;; {"PATCH" "/repos/:owner/:repo/keys/:id"}
{"DELETE" "/repos/:owner/:repo/keys/:id"}
{"GET" "/repos/:owner/:repo/downloads"}
{"GET" "/repos/:owner/:repo/downloads/:id"}
{"DELETE" "/repos/:owner/:repo/downloads/:id"}
{"GET" "/repos/:owner/:repo/forks"}
{"POST" "/repos/:owner/:repo/forks"}
{"GET" "/repos/:owner/:repo/hooks"}
{"GET" "/repos/:owner/:repo/hooks/:id"}
{"POST" "/repos/:owner/:repo/hooks"}
;; {"PATCH" "/repos/:owner/:repo/hooks/:id"}
{"POST" "/repos/:owner/:repo/hooks/:id/tests"}
{"DELETE" "/repos/:owner/:repo/hooks/:id"}
{"POST" "/repos/:owner/:repo/merges"}
{"GET" "/repos/:owner/:repo/releases"}
{"GET" "/repos/:owner/:repo/releases/:id"}
{"POST" "/repos/:owner/:repo/releases"}
;; {"PATCH" "/repos/:owner/:repo/releases/:id"}
{"DELETE" "/repos/:owner/:repo/releases/:id"}
{"GET" "/repos/:owner/:repo/releases/:id/assets"}
{"GET" "/repos/:owner/:repo/stats/contributors"}
{"GET" "/repos/:owner/:repo/stats/commit_activity"}
{"GET" "/repos/:owner/:repo/stats/code_frequency"}
{"GET" "/repos/:owner/:repo/stats/participation"}
{"GET" "/repos/:owner/:repo/stats/punch_card"}
{"GET" "/repos/:owner/:repo/statuses/:ref"}
{"POST" "/repos/:owner/:repo/statuses/:ref"}
;; Search
{"GET" "/search/repositories"}
{"GET" "/search/code"}
{"GET" "/search/issues"}
{"GET" "/search/users"}
{"GET" "/legacy/issues/search/:owner/:repository/:state/:keyword"}
{"GET" "/legacy/repos/search/:keyword"}
{"GET" "/legacy/user/search/:keyword"}
{"GET" "/legacy/user/email/:email"}
;; Users
{"GET" "/users/:user"}
{"GET" "/user"}
;; {"PATCH" "/user"}
{"GET" "/users"}
{"GET" "/user/emails"}
{"POST" "/user/emails"}
{"DELETE" "/user/emails"}
{"GET" "/users/:user/followers"}
{"GET" "/user/followers"}
{"GET" "/users/:user/following"}
{"GET" "/user/following"}
{"GET" "/user/following/:user"}
{"GET" "/users/:user/following/:target_user"}
{"PUT" "/user/following/:user"}
{"DELETE" "/user/following/:user"}
{"GET" "/users/:user/keys"}
{"GET" "/user/keys"}
{"GET" "/user/keys/:id"}
{"POST" "/user/keys"}
;; {"PATCH" "/user/keys/:id"}
{"DELETE" "/user/keys/:id"}])
(def app (def app
@ -78,33 +299,39 @@
(ring/router (ring/router
(reduce (partial add h) [] routes)))) (reduce (partial add h) [] routes))))
(defrecord Req [uri request-method])
(defn route->req [route]
(map->Req {:request-method (-> route keys first str/lower-case keyword)
:uri (str/replace (-> route vals first) #"\/:\w+" "/1")}))
(defn routing-test [] (defn routing-test []
;; https://github.com/julienschmidt/go-http-routing-benchmark ;; https://github.com/julienschmidt/go-http-routing-benchmark
;; coudn't run the GO tests, so reusing just the numbers (run on better hw?): ;; go test -bench="HttpRouter"
;;
;; Intel Core i5-2500K (4x 3,30GHz + Turbo Boost), CPU-governor: performance
;; 2x 4 GiB DDR3-1333 RAM, dual-channel
;; go version go1.3rc1 linux/amd64
;; Ubuntu 14.04 amd64 (Linux Kernel 3.13.0-29), fresh installation
;; 37ns (2.0x) (suite "httprouter vs reitit-ring")
;; 180ns (4.0x)
;; 200ns (4.8x)
"httpRouter"
;; 77ns -> 120ns (decode path-params) ;; 40ns (httprouter)
;; 700ns -> 795ns (decode path-params) ;; 140ns
;; 890ns -> 1000ns (decode path-params) (let [req (map->Req {:request-method :get, :uri "/user/repos"})]
(title "reitit-ring") (title "static")
(let [r1 (map->Request {:request-method :get, :uri "/1/users"}) (assert (= {:status 200, :body "/user/repos"} (app req)))
r2 (map->Request {:request-method :get, :uri "/1/classes/go"}) (cc/quick-bench (app req)))
r3 (map->Request {:request-method :get, :uri "/1/classes/go/123456789"})]
(assert (= {:status 200, :body "/1/users"} (app r1))) ;; 160ns (httprouter)
(assert (= {:status 200, :body "/1/classes/:className"} (app r2))) ;; 990ns
(assert (= {:status 200, :body "/1/classes/:className/:objectId"} (app r3))) (let [req (map->Req {:request-method :get, :uri "/repos/julienschmidt/httprouter/stargazers"})]
(cc/quick-bench (app r1)) (title "param")
(cc/quick-bench (app r2)) (assert (= {:status 200, :body "/repos/:owner/:repo/stargazers"} (app req)))
(cc/quick-bench (app r3)))) (cc/quick-bench (app req)))
;; 30µs (httprouter)
;; 190µs
(let [requests (mapv route->req routes)]
(title "all")
(cc/quick-bench
(doseq [r requests]
(app r)))))
(comment (comment
(routing-test)) (routing-test))