format and reduced memory by 90% Expanded features for comparing profiles Rewritten core in C (from Rust) I also realized that profilers don't make programs fast Just like how debuggers don't find bugs
Sinatra The toolset for doing BenchDD Building a 100x fast Sinatra ⽇本語字幕です。英語より情報量が多いことはないです 䢀 JA mini-translation. You won't miss a thing by skipping this
apps, 100x faster "Cinatra" ? C = 100 in Roman numerals was hard to distinguish in verbal comms, so "Xinatra" class MyApp < Xinatra::Base before do ... end get '/' do return "Hello, world!" end end 100倍速いSinatraを作ってみようと思ったんですよね
of "fast". The routing/handling logic is 100x fast = "Hello world" apps are 100x fast In real-world-ish benchmarks, it's about 1.02x fast ルーティングとハンドリングが100倍速いことをもって100倍といってます
end Warming up -------------------------------------- sumup 39.181k i/100ms Calculating ------------------------------------- sumup 392.296k (± 0.2%) i/s (2.55 μs/i) 1.998M in 5.093700s Run code and measure its time
will impact performance as a whole However, those are hard to find since those are slight Even though they may be easy to fix "Not slow" != "Fast" チリツモでプログラムは遅くなる
ns/req (lower is better) 215 ns/req 51 ns/req Honoのほうが空のRackアプリより速かった……。 I initially wanted to overtake Hono, a JavaScript web framework Full-featured Hono was faster than an empty Rack app...
@router.define("GET", "/foo", -> () { 'hello' }) end dataset "small" do { ["/hello", ...] } dataset "large" do { ... } scenario "trie" do data.each do |d| @router.match("GET", d) end end end
@router.define("GET", "/foo", -> () { 'hello' }) end dataset "small" do { ["/hello", ...] } dataset "large" do { ... } scenario "trie" do data.each do |d| @router.match("GET", d) end end end
compact For Xinatra, I prepared multiple tiers of workloads Small: A generated set of requests (10k reqs) Large: Log collected from real Sinatra apps (100k reqs) ベンチマークのデータセットも複数作っていい
where a simple O(N) loses to a complex O(logN) For 10-20 routes, linear routing was faster found by benchmarking! 270 ns/req (144x Sinatra) 数が少ないうちは O(N) のほうが O(logN) より速いことも
→ 266 ns/req (84 ns headroom to go) Many features to implement params access, before/after actions, Cookies, ... Not bottlenecks, though challenging to fit in 84 ns 他の全機能を 84 ns/req に収めなきゃ
=> "ruby" } end get '/search' do @params #=> { "q" => "ruby" } end Problem: Method calls are expensive % @ivar access is much faster! method 20-50 ns / call ivar 10 ns / access * incl. benchmarking overhead
mutable, and can do less work Xinatra supports both Users can gradually switch to @params and gain perf Lesson learned: Performance can influence API design. params() can't be faster than @params paramsを使って移⾏しつつ @params に切り替えることで速くできる
do # `before` implicitly called ... end Block is saved on app startup The block gets "called" on every request Can be used for authentication and other checks 認証とかに使える before actions
end def call(env) # eliminated #send __before end def __before; end # no-opstub end ⚠ Fast, but multiple befores cannot be defined in this version (breaking!) 270 ns/req (144x Sinatra)