celluloid/reel — pull request #19 by slivu (2012-11-22 00:33:35+00:00)
Title: Perfactoring - Phase_02

### Before

```
$ httperf --hog --port 9292 --num-conns=1 --num-calls=1000
...
Request rate: 4204.4 req/s (0.2 ms/req)
...
Reply status: 1xx=0 2xx=1000 3xx=0 4xx=0 5xx=0
```

### After

```
$ httperf --hog --port 9292 --num-conns=1 --num-calls=1000
...
Request rate: 5474.9 req/s (0.2 ms/req)
...
Reply status: 1xx=0 2xx=1000 3xx=0 4xx=0 5xx=0
```

### Commenting on most notable updates

```
diff --git a/lib/reel/request.rb b/lib/reel/request.rb

# not needed anymore, data will be accessed directly from parser
- attr_accessor :method, :version, :url, :headers
+ include RequestMixin

UPGRADE = 'Upgrade'.freeze
WEBSOCKET = 'websocket'.freeze

# Array#include? seems slow compared to Hash lookup
# so building a hash map at loadtime and querying it at runtime
+ request_methods = Http::METHODS.map { |m| m.to_s.upcase }
+ REQUEST_METHODS = Hash[request_methods.zip(request_methods)].freeze
+

# this decrease performance by about 400-500 rps
# and i guess it is redundant cause in RackWorker
# we prepare headers in Rack way.
# and if we will feed headers to other framework,
# we will prepare headers in there worker.
- headers = {}
- parser.headers.each do |field, value|
- headers[Http.canonicalize_header(field)] = value
- end

# querying request methods map built at loadtime
+ REQUEST_METHODS[parser.http_method] ||
+ raise(ArgumentError, "Unknown Request Method: %s" % parser.http_method)

# passing parser directly into Request and WebSocket classes.
# this way we will can access parser data without redundant accessors.
# also, by accessing request_path and query_string directly from parser
# removes the overhead needed to build and query a separate URI object.
- upgrade = headers[UPGRADE]
+ upgrade = parser.headers[UPGRADE]
if upgrade && upgrade.downcase == WEBSOCKET
- WebSocket.new(connection.socket, parser.http_method, parser.url, headers)
+ WebSocket.new(parser, connection.socket)
else
- Request.new(parser.http_method, parser.url, parser.http_version, headers, connection)
+ Request.new(parser, connection)
end
end

diff --git a/lib/reel/request_parser.rb b/lib/reel/request_parser.rb

# adding methods that will allow to access
# request_path and query_string directly from parser
+ [:request_path, :query_string].each do |m|
+ define_method m do
+ @parser.send m
+ end
+ end
+

# Http::METHODS not used anymore at runtime so no need to coerce string into sym.
# instead we building a map at loadtime that contains all methods as uppercase strings
def http_method
- @parser.http_method.downcase.to_sym
+ @parser.http_method
end

# joining fixnums is really slow.
# fixnum comparing instead if really fast.
# might looks strange, but according to perftools.rb
# this removed a notable overhead
def http_version
- @parser.http_version.join(".")
- # TODO: add extra HTTP_VERSION handler when HTTP/1.2 released
+ @parser.http_version[1] == 1 ? HTTP_VERSION_1_1 : HTTP_VERSION_1_0
end

diff --git a/lib/reel/response.rb b/lib/reel/response.rb

# though this adds notable overhead,
# we have to behave well and normalize headers before sending them to browser.
# however extra regexing are really redundant here
# and adds even more overhead, so just removed it.
@headers = {}
- headers.each do |name, value|
- name = name.to_s
- key = name[Http::CANONICAL_HEADER]
- key ||= canonicalize_header(name)
- @headers[key] = value.to_s
+ headers.each_pair do |header, value|
+ @headers[Http.canonicalize_header(header)] = value.to_s
end

- # Transform to canonical HTTP header capitalization
- def canonicalize_header(header)
- header.to_s.split(/[\-_]/).map(&:capitalize).join('-')
- end
- private :canonicalize_header
end
end

diff --git a/lib/reel/mixins.rb b/lib/reel/mixins.rb

module RequestMixin
...

# accessing path and query string directly from parser
# will avoid overhead needed to create and query a new URI object
def path
@http_parser.request_path
end

def query_string
@http_parser.query_string
end

end
```