mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
5bb1d4d288
I wrote a utility that helps find areas where you could optimize your program using a frozen string instead of a string literal, it's called [let_it_go](https://github.com/schneems/let_it_go). After going through the output and adding `.freeze` I was able to eliminate the creation of 1,114 string objects on EVERY request to [codetriage](codetriage.com). How does this impact execution? To look at memory: ```ruby require 'get_process_mem' mem = GetProcessMem.new GC.start GC.disable 1_114.times { " " } before = mem.mb after = mem.mb GC.enable puts "Diff: #{after - before} mb" ``` Creating 1,114 string objects results in `Diff: 0.03125 mb` of RAM allocated on every request. Or 1mb every 32 requests. To look at raw speed: ```ruby require 'benchmark/ips' number_of_objects_reduced = 1_114 Benchmark.ips do |x| x.report("freeze") { number_of_objects_reduced.times { " ".freeze } } x.report("no-freeze") { number_of_objects_reduced.times { " " } } end ``` We get the results ``` Calculating ------------------------------------- freeze 1.428k i/100ms no-freeze 609.000 i/100ms ------------------------------------------------- freeze 14.363k (± 8.5%) i/s - 71.400k no-freeze 6.084k (± 8.1%) i/s - 30.450k ``` Now we can do some maths: ```ruby ips = 6_226k # iterations / 1 second call_time_before = 1.0 / ips # seconds per iteration ips = 15_254 # iterations / 1 second call_time_after = 1.0 / ips # seconds per iteration diff = call_time_before - call_time_after number_of_objects_reduced * diff * 100 # => 0.4530373333993266 miliseconds saved per request ``` So we're shaving off 1 second of execution time for every 220 requests. Is this going to be an insane speed boost to any Rails app: nope. Should we merge it: yep. p.s. If you know of a method call that doesn't modify a string input such as [String#gsub](b0e2da69f0/lib/let_it_go/core_ext/string.rb (L37)
) please [give me a pull request to the appropriate file](b0e2da69f0/lib/let_it_go/core_ext/string.rb (L37)
), or open an issue in LetItGo so we can track and freeze more strings. Keep those strings Frozen ![](https://www.dropbox.com/s/z4dj9fdsv213r4v/let-it-go.gif?dl=1)
93 lines
2.8 KiB
Ruby
93 lines
2.8 KiB
Ruby
module ActionDispatch
|
|
module Journey # :nodoc:
|
|
class Router # :nodoc:
|
|
class Utils # :nodoc:
|
|
# Normalizes URI path.
|
|
#
|
|
# Strips off trailing slash and ensures there is a leading slash.
|
|
# Also converts downcase url encoded string to uppercase.
|
|
#
|
|
# normalize_path("/foo") # => "/foo"
|
|
# normalize_path("/foo/") # => "/foo"
|
|
# normalize_path("foo") # => "/foo"
|
|
# normalize_path("") # => "/"
|
|
# normalize_path("/%ab") # => "/%AB"
|
|
def self.normalize_path(path)
|
|
path = "/#{path}"
|
|
path.squeeze!('/'.freeze)
|
|
path.sub!(%r{/+\Z}, ''.freeze)
|
|
path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
|
|
path = '/' if path == ''.freeze
|
|
path
|
|
end
|
|
|
|
# URI path and fragment escaping
|
|
# http://tools.ietf.org/html/rfc3986
|
|
class UriEncoder # :nodoc:
|
|
ENCODE = "%%%02X".freeze
|
|
US_ASCII = Encoding::US_ASCII
|
|
UTF_8 = Encoding::UTF_8
|
|
EMPTY = "".force_encoding(US_ASCII).freeze
|
|
DEC2HEX = (0..255).to_a.map{ |i| ENCODE % i }.map{ |s| s.force_encoding(US_ASCII) }
|
|
|
|
ALPHA = "a-zA-Z".freeze
|
|
DIGIT = "0-9".freeze
|
|
UNRESERVED = "#{ALPHA}#{DIGIT}\\-\\._~".freeze
|
|
SUB_DELIMS = "!\\$&'\\(\\)\\*\\+,;=".freeze
|
|
|
|
ESCAPED = /%[a-zA-Z0-9]{2}/.freeze
|
|
|
|
FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/\?]/.freeze
|
|
SEGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@]/.freeze
|
|
PATH = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/]/.freeze
|
|
|
|
def escape_fragment(fragment)
|
|
escape(fragment, FRAGMENT)
|
|
end
|
|
|
|
def escape_path(path)
|
|
escape(path, PATH)
|
|
end
|
|
|
|
def escape_segment(segment)
|
|
escape(segment, SEGMENT)
|
|
end
|
|
|
|
def unescape_uri(uri)
|
|
encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding
|
|
uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack('C') }.force_encoding(encoding)
|
|
end
|
|
|
|
protected
|
|
def escape(component, pattern)
|
|
component.gsub(pattern){ |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII)
|
|
end
|
|
|
|
def percent_encode(unsafe)
|
|
safe = EMPTY.dup
|
|
unsafe.each_byte { |b| safe << DEC2HEX[b] }
|
|
safe
|
|
end
|
|
end
|
|
|
|
ENCODER = UriEncoder.new
|
|
|
|
def self.escape_path(path)
|
|
ENCODER.escape_path(path.to_s)
|
|
end
|
|
|
|
def self.escape_segment(segment)
|
|
ENCODER.escape_segment(segment.to_s)
|
|
end
|
|
|
|
def self.escape_fragment(fragment)
|
|
ENCODER.escape_fragment(fragment.to_s)
|
|
end
|
|
|
|
def self.unescape_uri(uri)
|
|
ENCODER.unescape_uri(uri)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|