| Commit message (Collapse) | Author | Age | Files | Lines |
|
|
|
|
| |
try to remove dependencies on `@env` so we can have more flexible
internals
|
|
|
|
| |
May be missed in 5fe141638f1243ac6ae187ae14aa398b4c1875a2 commit
Also fixes the broken build
|
|
|
|
|
| |
This commit allows us to use one request object rather than allocating
multiple request objects to deal with the session.
|
|
|
|
|
| |
We're going to implement storing env values differently in the future,
so let's disconnect these methods from the instance variables
|
| |
|
|
|
|
| |
we're already doing this with a bunch of other header data.
|
|
|
|
| |
we have a method that knows how to get rack.input, so lets use that.
|
|
|
|
|
|
| |
We need to abstract the internals of the request object away from this
instance variable so that the values for `@env` can be calculated in a
different way.
|
| |
|
|
|
|
|
|
|
|
|
| |
Since none of the action pack tests failed without this conditional it
didn't seem necessary. This fixes the build because it correctly returns
a 404 instead of a 500 for the asset routes test.
Test that was failing was in the `assets_test.rb` file and was the test
named `test_assets_routes_are_not_drawn_when_compilation_is_disabled`.
|
|
|
|
|
|
|
|
|
|
| |
This refactoring moves the controller class name that was on the route
set to the request. The purpose of this refactoring is for changes we
need to move controller tests to integration tests, mainly being able to
access the controller on the request instead of having to go through
the router.
[Eileen M. Uchitelle & Aaron Patterson]
|
| |
|
|
|
|
|
| |
we want to go through methods to access `env` because in the future that
ivar may not be available, or may be calculated lazily
|
|
|
|
|
| |
now the parameter filter doesn't need to know about the env hash in
these two methds.
|
|
|
|
| |
Now the Headers internals don't depend on the env hash.
|
|
|
|
|
| |
This prevents external mutations from impacting the internals of the
request or the Header object.
|
|
|
|
|
| |
this reduces the API footprint for the env hash so that we can be more
flexible when changing API in the future
|
|
|
|
| |
This allows us to avoid calling `env_name` twice.
|
|
|
|
| |
duping the request will dup it's underlying env hash.
|
|
|
|
|
|
|
| |
`ActiveSupport::Dependencies.constantize(const_name)` calls
`Reference.new` which is defined as
`ActiveSupport::Dependencies.constantize(const_name)` meaning this call
is already cached and we're doing caching that isn't necessary.
|
| |
|
|
|
|
|
| |
I'm going to change the constructor, so it's easier to do it in one
place.
|
|\
| |
| | |
Remove duplicity in tests
|
| | |
|
| |
| |
| |
| | |
This way we can make the Route object a read-only data structure.
|
| | |
|
| |
| |
| |
| |
| | |
nobody should be touching the routes hash without going through the
NamedRouteCollection object.
|
| |
| |
| |
| |
| | |
We shouldn't be messing with the NamedRouteCollection internals. Just
ask the object if the named route is in there.
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
```ruby
require 'action_pack'
require 'action_dispatch'
require 'benchmark/ips'
route_set = ActionDispatch::Routing::RouteSet.new
routes = ActionDispatch::Routing::Mapper.new route_set
ObjectSpace::AllocationTracer.setup(%i{path line type})
result = ObjectSpace::AllocationTracer.trace do
500.times do
routes.resources :foo
end
end
sorted = ObjectSpace::AllocationTracer.allocated_count_table.sort_by(&:last)
sorted.each do |k,v|
next if v == 0
p k => v
end
__END__
Before:
{:T_SYMBOL=>11}
{:T_REGEXP=>17}
{:T_STRUCT=>6500}
{:T_MATCH=>12004}
{:T_OBJECT=>99009}
{:T_DATA=>100088}
{:T_HASH=>122015}
{:T_STRING=>159637}
{:T_IMEMO=>363134}
{:T_ARRAY=>433056}
After:
{:T_SYMBOL=>11}
{:T_REGEXP=>17}
{:T_STRUCT=>6500}
{:T_MATCH=>12004}
{:T_OBJECT=>91009}
{:T_DATA=>100088}
{:T_HASH=>114013}
{:T_STRING=>159637}
{:T_ARRAY=>321056}
{:T_IMEMO=>351133}
```
|
| | |
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
Eagerly calculate and cache the name of Symbol objects in the path AST.
This drops about 26 string allocations per resource:
```ruby
require 'action_pack'
require 'action_dispatch'
require 'benchmark/ips'
route_set = ActionDispatch::Routing::RouteSet.new
routes = ActionDispatch::Routing::Mapper.new route_set
ObjectSpace::AllocationTracer.setup(%i{path line type})
result = ObjectSpace::AllocationTracer.trace do
500.times do
routes.resources :foo
end
end
sorted = ObjectSpace::AllocationTracer.allocated_count_table.sort_by(&:last)
sorted.each do |k,v|
next if v == 0
p k => v
end
__END__
Before:
{:T_SYMBOL=>11}
{:T_REGEXP=>17}
{:T_STRUCT=>6500}
{:T_MATCH=>12004}
{:T_OBJECT=>99009}
{:T_DATA=>116084}
{:T_HASH=>122015}
{:T_STRING=>172647}
{:T_IMEMO=>371132}
{:T_ARRAY=>433056}
After:
{:T_SYMBOL=>11}
{:T_REGEXP=>17}
{:T_STRUCT=>6500}
{:T_MATCH=>12004}
{:T_OBJECT=>99009}
{:T_DATA=>100088}
{:T_HASH=>122015}
{:T_STRING=>159637}
{:T_IMEMO=>363134}
{:T_ARRAY=>433056}
```
|
| |
| |
| |
| | |
verb_matcher never returns nil.
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
Rather than building a regexp for every route, lets use the strategy
pattern to select among objects that can match HTTP verbs. This commit
introduces strategy objects for each verb that has a predicate method on
the request object like `get?`, `post?`, etc.
When we build the route object, look up the strategy for the verbs the
user specified. If we can't find it, fall back on string matching.
Using a strategy / null object pattern (the `All` VerbMatcher is our
"null" object in this case) we can:
1) Remove conditionals
2) Drop boot time allocations
2) Drop run time allocations
3) Improve runtime performance
Here is our boot time allocation benchmark:
```ruby
require 'action_pack'
require 'action_dispatch'
route_set = ActionDispatch::Routing::RouteSet.new
routes = ActionDispatch::Routing::Mapper.new route_set
result = ObjectSpace::AllocationTracer.trace do
500.times do
routes.resources :foo
end
end
sorted = ObjectSpace::AllocationTracer.allocated_count_table.sort_by(&:last)
sorted.each do |k,v|
next if v == 0
p k => v
end
__END__
Before:
$ be ruby -rallocation_tracer route_test.rb
{:T_SYMBOL=>11}
{:T_REGEXP=>4017}
{:T_STRUCT=>6500}
{:T_MATCH=>12004}
{:T_DATA=>84092}
{:T_OBJECT=>99009}
{:T_HASH=>122015}
{:T_STRING=>216652}
{:T_IMEMO=>355137}
{:T_ARRAY=>441057}
After:
$ be ruby -rallocation_tracer route_test.rb
{:T_SYMBOL=>11}
{:T_REGEXP=>17}
{:T_STRUCT=>6500}
{:T_MATCH=>12004}
{:T_DATA=>84092}
{:T_OBJECT=>99009}
{:T_HASH=>122015}
{:T_STRING=>172647}
{:T_IMEMO=>355136}
{:T_ARRAY=>433056}
```
This benchmark adds 500 resources. Each resource has 8 routes, so it
adds 4000 routes. You can see from the results that this patch
eliminates 4000 Regexp allocations, ~44000 String allocations, and ~8000
Array allocations. With that, we can figure out that the previous code
would allocate 1 regexp, 11 strings, and 2 arrays per route *more* than
this patch in order to handle verb matching.
Next lets look at runtime allocations:
```ruby
require 'action_pack'
require 'action_dispatch'
require 'benchmark/ips'
route_set = ActionDispatch::Routing::RouteSet.new
routes = ActionDispatch::Routing::Mapper.new route_set
routes.resources :foo
route = route_set.routes.first
request = ActionDispatch::Request.new("REQUEST_METHOD" => "GET")
result = ObjectSpace::AllocationTracer.trace do
500.times do
route.matches? request
end
end
sorted = ObjectSpace::AllocationTracer.allocated_count_table.sort_by(&:last)
sorted.each do |k,v|
next if v == 0
p k => v
end
__END__
Before:
$ be ruby -rallocation_tracer route_test.rb
{:T_MATCH=>500}
{:T_STRING=>501}
{:T_IMEMO=>1501}
After:
$ be ruby -rallocation_tracer route_test.rb
{:T_IMEMO=>1001}
```
This benchmark runs 500 calls against the `matches?` method on the route
object. We check this method in the case that there are two methods
that match the same path, but they are differentiated by the verb (or
other conditionals). For example `POST /users` vs `GET /users`, same
path, different action.
Previously, we were using regexps to match against the verb. You can
see that doing the regexp match would allocate 1 match object and 1
string object each time it was called. This patch eliminates those
allocations.
Next lets look at runtime performance.
```ruby
require 'action_pack'
require 'action_dispatch'
require 'benchmark/ips'
route_set = ActionDispatch::Routing::RouteSet.new
routes = ActionDispatch::Routing::Mapper.new route_set
routes.resources :foo
route = route_set.routes.first
match = ActionDispatch::Request.new("REQUEST_METHOD" => "GET")
no_match = ActionDispatch::Request.new("REQUEST_METHOD" => "POST")
Benchmark.ips do |x|
x.report("match") do
route.matches? match
end
x.report("no match") do
route.matches? no_match
end
end
__END__
Before:
$ be ruby -rallocation_tracer runtime.rb
Calculating -------------------------------------
match 17.145k i/100ms
no match 24.244k i/100ms
-------------------------------------------------
match 259.708k (± 4.3%) i/s - 1.303M
no match 453.376k (± 5.9%) i/s - 2.279M
After:
$ be ruby -rallocation_tracer runtime.rb
Calculating -------------------------------------
match 23.958k i/100ms
no match 29.402k i/100ms
-------------------------------------------------
match 465.063k (± 3.8%) i/s - 2.324M
no match 691.956k (± 4.5%) i/s - 3.469M
```
This tests tries to see how many times it can match a request per
second. Switching to method calls and string comparison makes the
successful match case about 79% faster, and the unsuccessful case about
52% faster.
That was fun!
|
| |
| |
| |
| |
| | |
We don't need to add and delete from the conditions hash anymore, just
pass the regexp directly to the constructor.
|
| |
| |
| |
| |
| |
| | |
verb matching is very common (all routes besides rack app endpoints
require one). We will extract verb matching for now, and use a more
efficient method of matching (then regexp) later
|
| | |
|
| | |
|
| |
| |
| |
| |
| | |
I want to change the real constructor to take a particular parameter for
matching the request method
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| | |
This commit introduces a functional Path AST visitor and implements
`each` on the AST in terms of the functional visitor. The functional
visitor doesn't maintain state, so we only need to allocate one of them.
Given this benchmark route file:
```ruby
require 'action_pack'
require 'action_dispatch'
route_set = ActionDispatch::Routing::RouteSet.new
routes = ActionDispatch::Routing::Mapper.new route_set
ObjectSpace::AllocationTracer.setup(%i{path line type})
result = ObjectSpace::AllocationTracer.trace do
500.times{|i|
routes.resource :omglol
}
end
result.find_all { |k,v| k.first =~ /git\/rails/ }.sort_by { |k,v|
v.first
}.each { |k,v|
p k => v
}
```
node.rb line 17 was in our top 3 allocation spot:
```
{["/Users/aaron/git/rails/actionpack/lib/action_dispatch/journey/nodes/node.rb", 17, :T_OBJECT]=>[31526, 0, 28329, 0, 2, 1123160]}
{["/Users/aaron/git/rails/actionpack/lib/action_dispatch/routing/mapper.rb", 2080, :T_IMEMO]=>[34002, 0, 30563, 0, 2, 1211480]}
{["/Users/aaron/git/rails/actionpack/lib/action_dispatch/routing/mapper.rb", 2071, :T_IMEMO]=>[121934, 1, 109608, 0, 7, 4344400]}
```
This commit eliminates allocations at that place.
|
| |
| |
| |
| | |
add another predicate method so we can avoid is_a checks
|
| |
| |
| |
| | |
we don't really need this visitor
|
| |
| |
| |
| |
| | |
Routes are always constructed with a list of required_defaults, so
there's no need to check whether or not it's nil
|
| | |
|
| |
| |
| |
| |
| | |
we may want to change the name of the class at some point, so it's
better to use a predicate
|
| |
| |
| |
| |
| | |
The string we create is almost always the same, so rather than joining
all the time, lets join once, then reuse that string everywhere.
|
|\ \
| | |
| | | |
Improve params parser
|
| | | |
|
| | | |
|
| | |
| | |
| | |
| | |
| | |
| | | |
I would like to change the signature of the Route constructor. Since
the mapping object has all the data required to construct a Route
object, move the allocation to a factory method.
|
| | |
| | |
| | |
| | |
| | |
| | | |
We should build the routes using the user facing API which is `Mapper`.
This frees up the library internals to change as we see fit. IOW we
shouldn't be testing internals.
|