soveran / cuba

Rum based microframework for web development.
http://cuba.is
MIT License
1.44k stars 249 forks source link

Improve performance by caching regexes? #90

Closed asterite closed 6 years ago

asterite commented 6 years ago

Hi @soveran! 😄

I noticed that to consume segments a regex is created each and every time the segment tries to be matched:

https://github.com/soveran/cuba/blob/f66109654ff1c16ee46060330738a902c7a1a7fc/lib/cuba.rb#L214

So I made this benchmark:

require_relative "./lib/cuba"

Cuba.define do
  on "users" do
    on ":id" do |id|
      on root do
        res.write "User #{id}"
      end

      on "projects" do
        on ":project_id" do |project_id|
          res.write "User #{id}, Project #{project_id}"
        end
      end
    end
  end
end

env1 = { "PATH_INFO" => "/users/1", "SCRIPT_NAME" => "/" }
env2 = { "PATH_INFO" => "/users/1/projects/2", "SCRIPT_NAME" => "/" }

time = Time.now
30_000.times do
  Cuba.call(env1)
  Cuba.call(env2)
end
puts Time.now - time

On my machine it takes about 4 seconds to complete.

Now I cache the regexes with this diff:

diff --git a/lib/cuba.rb b/lib/cuba.rb
index b12e9f3..b4090fe 100644
--- a/lib/cuba.rb
+++ b/lib/cuba.rb
@@ -5,6 +5,7 @@ class Cuba
   EMPTY   = "".freeze
   SEGMENT = "([^\\/]+)".freeze
   DEFAULT = "text/html; charset=utf-8".freeze
+  REGEXES = Hash.new { |h, pattern| h[pattern] = /\A\/(#{pattern})(\/|\z)/ }

   class Response
     LOCATION = "Location".freeze
@@ -211,7 +212,7 @@ class Cuba
   private :try

   def consume(pattern)
-    matchdata = env[Rack::PATH_INFO].match(/\A\/(#{pattern})(\/|\z)/)
+    matchdata = env[Rack::PATH_INFO].match(REGEXES[pattern])

     return false unless matchdata

I run the snippet above and it now takes 2 seconds.

Twice as fast!

Do you think this is a good change? I think it's harmless: the routes of an app are fixed (not dynamic) so that REGEXES hash will reach a reasonable maximum size. Plus it can help with routes like ":id" where they might be used in several places.

What do you think?

soveran commented 6 years ago

I kind of replied with a 👍 and a ❤️ I think this is perfect, let's go for it. Do you want to submit the change as a PR?

asterite commented 6 years ago

Sure! I'll send a PR soon :-)

asterite commented 6 years ago

Closed by #91