Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #10835: AddedOptimizationAfterTimeTest.patch

File AddedOptimizationAfterTimeTest.patch, 7.8 kB (added by oleganza, 9 months ago)

some tiny refactoring done

  • a/actionpack/lib/action_controller/routing.rb

    old new  
    77require 'action_controller/routing/segments' 
    88require 'action_controller/routing/builder' 
    99require 'action_controller/routing/route_set' 
     10require 'action_controller/routing/recognition_optimisation' 
    1011 
    1112module ActionController 
    1213  # == Routing 
  • /dev/null

    old new  
     1module ActionController 
     2  module Routing 
     3    # BEFORE:   0.191446860631307 ms/url 
     4    # AFTER:    0.029847304022858 ms/url 
     5    # Speed up: 6.4 times 
     6    # 
     7    # Route recognition is slow due to one-by-one iterating over 
     8    # a whole routeset (each map.resources generates at least 14 routes) 
     9    # and matching weird regexps on each step. 
     10    # 
     11    # We optimize this by skipping all URI segments that 100% sure can't  
     12    # be matched, moving deeper in a tree of routes (where node == segment) 
     13    # until first possible match is accured. In such case, we start walking 
     14    # a flat list of routes, matching them with accurate matcher. 
     15    # So, first step: search a segment tree for the first relevant index. 
     16    # Second step: iterate routes starting with that index. 
     17    #  
     18    # How tree is walked? We can do a recursive tests, but it's smarter: 
     19    # We just create a tree of if-s and elsif-s matching segments. 
     20    # 
     21    # We have segments of 3 flavors: 
     22    # 1) nil (no segment, route finished) 
     23    # 2) const-dot-dynamic (like "/posts.:xml", "/preview.:size.jpg") 
     24    # 3) const (like "/posts", "/comments") 
     25    # 4) dynamic ("/:id", "file.:size.:extension") 
     26    # 
     27    # We split incoming string into segments and iterate over them. 
     28    # When segment is nil, we drop immediately, on a current node index. 
     29    # When segment is equal to some const, we step into branch. 
     30    # If none constants matched, we step into 'dynamic' branch (it's a last). 
     31    # If we can't match anything, we drop to last index on a level. 
     32    # 
     33    # Note: we maintain the original routes order, so we finish building  
     34    #       steps on a first dynamic segment.  
     35    # 
     36    #  
     37    # Example. Given the routes: 
     38    #   0 /posts/ 
     39    #   1 /posts/:id 
     40    #   2 /posts/:id/comments 
     41    #   3 /posts/blah 
     42    #   4 /users/ 
     43    #   5 /users/:id 
     44    #   6 /users/:id/profile 
     45    #  
     46    # request_uri = /users/123 
     47    # 
     48    # There will be only 4 iterations:  
     49    #  1) segm test for /posts prefix, skip all /posts/* routes 
     50    #  2) segm test for /users/ 
     51    #  3) segm test for /users/:id 
     52    #     (jump to list index = 5) 
     53    #  4) full test for /users/:id => here we are! 
     54         
     55    class RouteSet 
     56      def recognize_path(path, environment={}) 
     57        result = recognize_optimized(path, environment) and return result 
     58         
     59        # Route was not recognized. Try to find out why (maybe wrong verb). 
     60        allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } } 
     61                   
     62        if environment[:method] && !HTTP_METHODS.include?(environment[:method]) 
     63          raise NotImplemented.new(*allows) 
     64        elsif !allows.empty? 
     65          raise MethodNotAllowed.new(*allows) 
     66        else 
     67          raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}" 
     68        end 
     69      end 
     70       
     71      def recognize_optimized(path, env) 
     72        write_recognize_optimized 
     73        recognize_optimized(path, env) 
     74      end 
     75       
     76      def write_recognize_optimized 
     77        tree = segment_tree(routes) 
     78        body = generate_recognition_code(tree) 
     79        instance_eval %{ 
     80          def recognize_optimized(path, env) 
     81            segments = to_plain_segments(path) 
     82            index = #{body} 
     83            return nil unless index 
     84            while index < routes.size 
     85              result = routes[index].recognize(path, env) and return result  
     86              index += 1 
     87            end 
     88            nil 
     89          end 
     90        }, __FILE__, __LINE__ 
     91      end 
     92       
     93      def segment_tree(routes) 
     94        tree = [0] 
     95         
     96        i = -1 
     97        routes.each do |route| 
     98          i += 1 
     99          # not fast, but runs only once 
     100          segments = to_plain_segments(route.segments.inject("") { |str,s| str << s.to_s }) 
     101           
     102          node  = tree 
     103          segments.each do |seg| 
     104            seg = :dynamic if seg && seg[0] == ?: 
     105            node << [seg, [i]] if node.empty? || node[node.size - 1][0] != seg 
     106            node = node[node.size - 1][1] 
     107          end 
     108        end 
     109        tree 
     110      end 
     111       
     112      def generate_recognition_code(list, padding='  ', level = 0) 
     113        # a digit 
     114        return padding + "#{list[0]}\n" if list.size == 1 && !(Array === list[0]) 
     115         
     116        body = padding + "(seg = segments[#{level}]; \n" 
     117          
     118        i = 0  
     119        was_nil = false 
     120        list.each do |item| 
     121          if Array === item 
     122            i += 1 
     123            cond = (i == 1 ? 'if' : 'elsif') 
     124            tag, sub = item 
     125            if tag == :dynamic 
     126              body += padding + "#{cond} true\n" 
     127              body += generate_recognition_code(sub, padding + "  ", level + 1) 
     128              break 
     129            elsif tag == nil && !was_nil 
     130              was_nil = true 
     131              body += padding + "#{cond} seg.nil?\n" 
     132              body += generate_recognition_code(sub, padding + "  ", level + 1) 
     133            else 
     134              body += padding + "#{cond} seg == '#{tag}'\n" 
     135              body += generate_recognition_code(sub, padding + "  ", level + 1) 
     136            end 
     137          end 
     138        end 
     139        body += padding + "else\n" 
     140        body += padding + "  #{list[0]}\n" 
     141        body += padding + "end)\n" 
     142        body 
     143      end 
     144       
     145      # this must be really fast 
     146      def to_plain_segments(str) 
     147        str = str.dup 
     148        str.sub!(/^\/+/,'') 
     149        str.sub!(/\/+$/,'') 
     150        segments = str.split(/\.[^\/]+\/+|\/+|\.[^\/]+\Z/) # cut off ".format" also 
     151        segments << nil 
     152        segments 
     153      end 
     154       
     155    end 
     156  end 
     157end 
  • a/actionpack/lib/action_controller/routing/route_set.rb

    old new  
    208208        named_routes.clear 
    209209        @combined_regexp = nil 
    210210        @routes_by_controller = nil 
     211        # This will force routing/recognition_optimization.rb  
     212        # to refresh optimisations. 
     213        @compiled_recognize_optimized = nil 
    211214      end 
    212215 
    213216      def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false) 
     
    379382      end 
    380383 
    381384      def recognize_path(path, environment={}) 
    382         routes.each do |route| 
    383           result = route.recognize(path, environment) and return result 
    384         end 
    385  
    386         allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } } 
    387  
    388         if environment[:method] && !HTTP_METHODS.include?(environment[:method]) 
    389           raise NotImplemented.new(*allows) 
    390         elsif !allows.empty? 
    391           raise MethodNotAllowed.new(*allows) 
    392         else 
    393           raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}" 
    394         end 
     385        raise "Not optimized! Check that routing/recognition_optimisation overrides RouteSet#recognize_path." 
    395386      end 
    396387 
    397388      def routes_by_controller