mirror of
https://github.com/metosin/reitit.git
synced 2025-12-18 00:41:12 +00:00
calf
This commit is contained in:
parent
2a1fea2ccb
commit
ae2337621f
2 changed files with 314 additions and 0 deletions
96
modules/reitit-core/java-src/reitit/Util.java
Normal file
96
modules/reitit-core/java-src/reitit/Util.java
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package reitit;
|
||||
|
||||
import clojure.lang.Keyword;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class Util {
|
||||
|
||||
public static Map<?, String> matchURI(String uri, List<?> patternTokens) {
|
||||
final MatchResult result = matchURI(uri, 0, patternTokens, false);
|
||||
return result == null ? null : result.getParams();
|
||||
}
|
||||
|
||||
public static MatchResult matchURI(String uri, int beginIndex, List<?> patternTokens) {
|
||||
return matchURI(uri, beginIndex, patternTokens, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Match a URI against URI-pattern tokens and return match result on successful match, {@code null} otherwise.
|
||||
* When argument {@code attemptPartialMatch} is {@code true}, both full and partial match are attempted without any
|
||||
* performance penalty. When argument {@code attemptPartialMatch} is {@code false}, only a full match is attempted.
|
||||
*
|
||||
* @param uri the URI string to match
|
||||
* @param beginIndex beginning index in the URI string to match
|
||||
* @param patternTokens URI pattern tokens to match the URI against
|
||||
* @param attemptPartialMatch whether attempt partial match when full match is not possible
|
||||
* @return a match result on successful match, {@literal null} otherwise
|
||||
*/
|
||||
public static MatchResult matchURI(String uri, int beginIndex, List<?> patternTokens, boolean attemptPartialMatch) {
|
||||
if (beginIndex == MatchResult.FULL_MATCH_INDEX) { // if already a full-match then no need to match further
|
||||
return MatchResult.NO_MATCH;
|
||||
}
|
||||
final int tokenCount = patternTokens.size();
|
||||
// if length==1, then token must be string (static URI path)
|
||||
if (tokenCount == 1) {
|
||||
final String staticPath = (String) patternTokens.get(0);
|
||||
if (uri.startsWith(staticPath, beginIndex)) { // URI begins with the path, so at least partial match exists
|
||||
if ((uri.length() - beginIndex) == staticPath.length()) { // if full match exists, then return as such
|
||||
return MatchResult.FULL_MATCH_NO_PARAMS;
|
||||
}
|
||||
return attemptPartialMatch ? MatchResult.partialMatch(staticPath.length()) : MatchResult.NO_MATCH;
|
||||
} else {
|
||||
return MatchResult.NO_MATCH;
|
||||
}
|
||||
}
|
||||
final int uriLength = uri.length();
|
||||
final Map<Object, String> pathParams = new HashMap<Object, String>(tokenCount);
|
||||
int uriIndex = beginIndex;
|
||||
OUTER:
|
||||
for (final Object token : patternTokens) {
|
||||
if (uriIndex >= uriLength) {
|
||||
return attemptPartialMatch ? MatchResult.partialMatch(pathParams, uriIndex) : MatchResult.NO_MATCH;
|
||||
}
|
||||
if (token instanceof String) {
|
||||
final String tokenStr = (String) token;
|
||||
if (uri.startsWith(tokenStr, uriIndex)) {
|
||||
uriIndex += tokenStr.length(); // now i==n if last string token
|
||||
} else { // 'string token mismatch' implies no match
|
||||
return MatchResult.NO_MATCH;
|
||||
}
|
||||
} else {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (int j = uriIndex; j < uriLength; j++) { // capture param chars in one pass
|
||||
final char ch = uri.charAt(j);
|
||||
if (ch == '/') { // separator implies we got param value, now continue
|
||||
pathParams.put(token, sb.toString());
|
||||
uriIndex = j;
|
||||
continue OUTER;
|
||||
} else {
|
||||
sb.append(ch);
|
||||
}
|
||||
}
|
||||
// 'separator not found' implies URI has ended
|
||||
pathParams.put(token, sb.toString());
|
||||
uriIndex = uriLength;
|
||||
}
|
||||
}
|
||||
if (uriIndex < uriLength) { // 'tokens finished but URI still in progress' implies partial or no match
|
||||
return attemptPartialMatch ? MatchResult.partialMatch(pathParams, uriIndex) : MatchResult.NO_MATCH;
|
||||
}
|
||||
return MatchResult.fullMatch(pathParams);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
List<Object> list = new ArrayList<>();
|
||||
list.add("/user/");
|
||||
list.add(Keyword.intern("userId"));
|
||||
list.add("/profile/");
|
||||
list.add(Keyword.intern("type"));
|
||||
list.add("/");
|
||||
System.out.println(Util.matchURI("/user/1234/profile/compact/", list));
|
||||
}
|
||||
}
|
||||
218
perf-test/clj/reitit/calf_perf_test.clj
Normal file
218
perf-test/clj/reitit/calf_perf_test.clj
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
(ns reitit.calf-perf-test
|
||||
(:require [criterium.core :as cc]
|
||||
[reitit.perf-utils :refer :all]
|
||||
[ring.util.codec]
|
||||
[reitit.impl]
|
||||
[reitit.segment :as segment]
|
||||
[reitit.impl :as impl]
|
||||
[reitit.ring :as ring]
|
||||
[reitit.core :as r])
|
||||
(:import (reitit Trie)))
|
||||
|
||||
;;
|
||||
;; start repl with `lein perf repl`
|
||||
;; perf measured with the following setup:
|
||||
;;
|
||||
;; Model Name: MacBook Pro
|
||||
;; Model Identifier: MacBookPro11,3
|
||||
;; Processor Name: Intel Core i7
|
||||
;; Processor Speed: 2,5 GHz
|
||||
;; Number of Processors: 1
|
||||
;; Total Number of Cores: 4
|
||||
;; L2 Cache (per Core): 256 KB
|
||||
;; L3 Cache: 6 MB
|
||||
;; Memory: 16 GB
|
||||
;;
|
||||
|
||||
|
||||
(defn test! [f input]
|
||||
(do
|
||||
(println "\u001B[33m")
|
||||
(println (pr-str input) "=>" (pr-str (f input)))
|
||||
(println "\u001B[0m")
|
||||
(cc/quick-bench (f input))))
|
||||
|
||||
(defn h11 [id type] {:status 200
|
||||
:headers {"Content-Type" "text/plain"}
|
||||
:body (str id ".11." type)})
|
||||
(defn h12 [id type] {:status 200
|
||||
:headers {"Content-Type" "text/plain"}
|
||||
:body (str id ".12." type)})
|
||||
(defn h1x [] {:status 405
|
||||
:headers {"Allow" "GET, PUT"
|
||||
"Content-Type" "text/plain"}
|
||||
:body "405 Method not supported. Supported methods are: GET, PUT"})
|
||||
|
||||
(defn h21 [id] {:status 200
|
||||
:headers {"Content-Type" "text/plain"}
|
||||
:body (str id ".21")})
|
||||
(defn h22 [id] {:status 200
|
||||
:headers {"Content-Type" "text/plain"}
|
||||
:body (str id ".22")})
|
||||
(defn h2x [] {:status 405
|
||||
:headers {"Allow" "GET, PUT"
|
||||
"Content-Type" "text/plain"}
|
||||
:body "405 Method not supported. Supported methods are: GET, PUT"})
|
||||
(defn h30 [cid did] {:status 200
|
||||
:headers {"Content-Type" "text/plain"}
|
||||
:body (str cid ".3." did)})
|
||||
(defn h3x [] {:status 405
|
||||
:headers {"Allow" "PUT"
|
||||
"Content-Type" "text/plain"}
|
||||
:body "405 Method not supported. Only PUT is supported."})
|
||||
(defn h40 [] {:status 200
|
||||
:headers {"Content-Type" "text/plain"}
|
||||
:body "4"})
|
||||
(defn h4x [] {:status 405
|
||||
:headers {"Allow" "PUT"
|
||||
"Content-Type" "text/plain"}
|
||||
:body "405 Method not supported. Only PUT is supported."})
|
||||
(defn hxx [] {:status 400
|
||||
:headers {"Content-Type" "text/plain"}
|
||||
:body "400 Bad request. URI does not match any available uri-template."})
|
||||
|
||||
(def handler-reitit
|
||||
(ring/ring-handler
|
||||
(ring/router
|
||||
[["/user/:id/profile/:type/" {:get (fn [{{:keys [id type]} :path-params}] (h11 id type))
|
||||
:put (fn [{{:keys [id type]} :path-params}] (h12 id type))
|
||||
:handler (fn [_] (h1x))}]
|
||||
#_["/user/:id/permissions/" {:get (fn [{{:keys [id]} :path-params}] (h21 id))
|
||||
:put (fn [{{:keys [id]} :path-params}] (h22 id))
|
||||
:handler (fn [_] (h2x))}]
|
||||
#_["/company/:cid/dept/:did/" {:put (fn [{{:keys [cid did]} :path-params}] (h30 cid did))
|
||||
:handler (fn [_] (h3x))}]
|
||||
#_["/this/is/a/static/route" {:put (fn [_] (h40))
|
||||
:handler (fn [_] (h4x))}]])
|
||||
(fn [_] (hxx))))
|
||||
|
||||
#_(let [request {:request-method :put
|
||||
:uri "/this/is/a/static/route"}]
|
||||
(handler-reitit request)
|
||||
(cc/quick-bench
|
||||
(handler-reitit request)))
|
||||
|
||||
(let [request {:request-method :get
|
||||
:uri "/user/1234/profile/compact/"}]
|
||||
(handler-reitit request)
|
||||
;; OLD: 1338ns
|
||||
;; NEW: 981ns
|
||||
#_(cc/quick-bench
|
||||
(handler-reitit request)))
|
||||
|
||||
(comment
|
||||
|
||||
;; 849ns (clojure, original)
|
||||
;; 599ns (java, initial)
|
||||
;; 810ns (linear)
|
||||
(let [router (r/router ["/user/:id/profile/:type"])]
|
||||
(cc/quick-bench
|
||||
(r/match-by-path router "/user/1234/profile/compact")))
|
||||
|
||||
;; 131ns
|
||||
(let [route ["/user/" :id "/profile/" :type "/"]]
|
||||
(cc/quick-bench
|
||||
(Util/matchURI "/user/1234/profile/compact/" route)))
|
||||
|
||||
;; 728ns
|
||||
(cc/quick-bench
|
||||
(r/match-by-path ring/ROUTER (:uri ring/REQUEST))))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
(comment
|
||||
(let [request {:request-method :get
|
||||
:uri "/user/1234/profile/compact/"}]
|
||||
(time
|
||||
(dotimes [_ 1000]
|
||||
(handler-reitit request)))))
|
||||
|
||||
(import '[reitit Util])
|
||||
|
||||
#_(cc/quick-bench
|
||||
(Trie/split "/this/is/a/static/route"))
|
||||
|
||||
(Util/matchURI "/user/1234/profile/compact/" ["/user/" :id "/profile/" :type "/"])
|
||||
|
||||
(import '[reitit Segment2])
|
||||
|
||||
(def paths ["kikka" "kukka" "kakka" "abba" "jabba" "1" "2" "3" "4"])
|
||||
(def a (Segment2/createArray paths))
|
||||
(def h (Segment2/createHash paths))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
(comment
|
||||
(let [segment (segment/create
|
||||
[["/user/:id/profile/:type/" 1]
|
||||
["/user/:id/permissions/" 2]
|
||||
["/company/:cid/dept/:did/" 3]
|
||||
["/this/is/a/static/route" 4]])]
|
||||
(segment/lookup segment "/user/1/profile/compat/")
|
||||
|
||||
;; OLD: 602ns
|
||||
;; NEW: 472ns
|
||||
(cc/quick-bench
|
||||
(segment/lookup segment "/user/1/profile/compat/"))
|
||||
|
||||
;; OLD: 454ns
|
||||
;; NEW: 372ns
|
||||
(cc/quick-bench
|
||||
(segment/lookup segment "/user/1/permissions/"))))
|
||||
|
||||
#_(cc/quick-bench
|
||||
(Trie/split "/user/1/profile/compat"))
|
||||
|
||||
#_(Trie/split "/user/1/profile/compat")
|
||||
|
||||
#_(cc/quick-bench
|
||||
(Segment2/hashLookup h "abba"))
|
||||
|
||||
|
||||
|
||||
(comment
|
||||
(cc/quick-bench
|
||||
(dotimes [_ 1000]
|
||||
;; 7ns
|
||||
(Segment2/arrayLookup a "abba")))
|
||||
|
||||
(cc/quick-bench
|
||||
(dotimes [_ 1000]
|
||||
;; 3ns
|
||||
(Segment2/hashLookup h "abba"))))
|
||||
|
||||
(comment
|
||||
(time
|
||||
(dotimes [_ 1000]
|
||||
(Util/matchURI "/user/1234/profile/compact/" ["/user/" :id "/profile/" :type "/"])))
|
||||
|
||||
|
||||
(time
|
||||
(let [s (s/create [["/user/:id/profile/:type/" 1]])]
|
||||
(dotimes [_ 1000]
|
||||
(s/lookup s "/user/1234/profile/compact/"))))
|
||||
|
||||
(let [m {"/abba" 1}]
|
||||
(time
|
||||
(dotimes [_ 1000]
|
||||
(get m "/abba"))))
|
||||
|
||||
(time
|
||||
(dotimes [_ 1000]
|
||||
(Util/matchURI "/user/1234/profile/compact/" 0 ["/user/" :id "/profile/" :type "/"] false)))
|
||||
|
||||
;; 124ns
|
||||
(cc/quick-bench
|
||||
(Util/matchURI "/user/1234/profile/compact/" 0 ["/user/" :id "/profile/" :type "/"] false))
|
||||
|
||||
;; 166ns
|
||||
(cc/quick-bench
|
||||
(impl/segments "/user/1234/profile/compact/"))
|
||||
|
||||
;; 597ns
|
||||
(let [s (s/create [["/user/:id/profile/:type/" 1]])]
|
||||
(cc/quick-bench
|
||||
(s/lookup s "/user/1234/profile/compact/")))
|
||||
|
||||
(let [s (s/create [["/user/:id/profile/:type/" 1]])]
|
||||
(s/lookup s "/user/1234/profile/compact/")))
|
||||
Loading…
Reference in a new issue