Permalink
Browse files

Drop tens of thousands of object allocations on require

This patch utilizes a few techniques to cut down object allocations.and speed up requiring `mime/types`

- Use frozen strings and in place string manipulation
- Use self modifying operators when possible i.e. gsub!
- Avoiding assigning multiple variables using the splat operator (i.e. `a, b = *[1,2]`) as this allocates an extra un-needed array.

The call to `self.friendly({})` does nothing, it only serves to set the instance variable. The method call here merges an empty hash with an empty hash we can get rid of it.


To benchmark:

```
require 'allocation_tracer'

$LOAD_PATH << "/Users/schneems/Documents/projects/mime-types/lib"

ObjectSpace::AllocationTracer.trace do
  require 'mime/types'
end

puts :TOTAL => ObjectSpace::AllocationTracer.allocated_count_table.values.inject(:+)
```

Run without patch:

```
{:TOTAL=>195692}
```

Run with patch:

```
{:TOTAL=>158240}
```
  • Loading branch information...
schneems committed Mar 30, 2015
1 parent d0e3898 commit 3aad2228f907e21d8fac302c3f6334231baf2315
Showing with 42 additions and 8 deletions.
  1. +28 −0 Rakefile
  2. +14 −8 lib/mime/type.rb
@@ -33,6 +33,34 @@ spec = Hoe.spec 'mime-types' do
self.extra_dev_deps << ['coveralls', '~> 0.7']
end

desc 'Allocation counts'
task allocations: :support do
require 'pp'

if RUBY_VERSION < '2.1'
$stderr.puts "Cannot count allocations on #{RUBY_VERSION}."
exit 1
end

begin
require 'allocation_tracer'
rescue LoadError => e
puts e
$stderr.puts "Allocation tracking requires the gem 'allocation_tracer'."
exit 1
end

r = ObjectSpace::AllocationTracer.trace do
require 'mime/types'
end

count = ObjectSpace::AllocationTracer.allocated_count_table.values.
inject(:+)

pp r.sort {|x, y| x.last.first <=> y.last.first }.reverse.first(100)
puts "TOTAL Allocations: #{count}"
end

task :support do
%w(lib support).each { |path|
$LOAD_PATH.unshift(File.join(Rake.application.original_dir, path))
@@ -93,6 +93,7 @@ def to_s
# extensions.
# * Otherwise, the content_type will be used as a string.
def initialize(content_type) # :yields self:
@friendly = {}
self.system = nil
self.obsolete = false
self.registered = nil
@@ -114,7 +115,6 @@ def initialize(content_type) # :yields self:
self.extensions ||= []
self.docs ||= nil
self.encoding ||= :default
self.friendly({})
# This value will be deprecated in the future, as it will be an
# alternative view on #xrefs. Silence an unnecessary warning for now by
# assigning directly to the instance variable.
@@ -618,8 +618,10 @@ def simplified(content_type)

if matchdata
matchdata.captures.map { |e|
e.downcase.gsub(UNREGISTERED_RE, '')
}.join('/')
e.downcase!
e.gsub!(UNREGISTERED_RE, ''.freeze)
e
}.join('/'.freeze)
end
end

@@ -635,8 +637,11 @@ def i18n_key(content_type)

if matchdata
matchdata.captures.map { |e|
e.downcase.gsub(UNREGISTERED_RE, '').gsub(I18N_RE, '-')
}.join('.')
e.downcase!
e.gsub!(UNREGISTERED_RE, ''.freeze)
e.gsub!(I18N_RE, '-'.freeze)
e
}.join('.'.freeze)
end
end

@@ -742,10 +747,11 @@ def content_type=(type_string)
raise InvalidContentType, type_string if match.nil?

@content_type = type_string
@raw_media_type, @raw_sub_type = *match.captures
@raw_media_type, @raw_sub_type = match.captures
@simplified = MIME::Type.simplified(match)
@i18n_key = MIME::Type.i18n_key(match)
@media_type, @sub_type =
*MEDIA_TYPE_RE.match(@simplified).captures
captures = MEDIA_TYPE_RE.match(@simplified).captures
@media_type = captures.first
@sub_type = captures.last
end
end

0 comments on commit 3aad222

Please sign in to comment.