Replace mime-types with mini_mime to save memory

On boot, mime/types/columnar allocates **109k** objects and ends up
with a retained count of **31K** objects. This leaves objects that
need to be marked and swept every major GC and bloats ruby processes.

To circumvent, mini_mime was created. It uses the exact same database
as the full-fledged mime-types gem and is capable of only loading on
demand with a practical, safe and bound in-memory cache.

mini_mime allocates **398** objects on boot and only retain **62**
of them (due to rubygems inefficiency).

In table form:

| |boot allocations|boot retained|lookup|lookup uncached|
|-|-:|-:|-:|-:|
|mini_mime|398|62|641K/s|33K/s|
|mime-types|109796|31165|361K/s|361K/s|

The performance of mini_mime is pretty good: cached lookups are faster
than the mime-types gem and uncached lookups are 10x slower, which is
really not at huge issue considering the memory savings.
This commit is contained in:
Sam 2016-12-14 15:26:09 +11:00 committed by Jeremy Daer
parent 48cb6db25b
commit 634d91145c
No known key found for this signature in database
GPG Key ID: AB8F6399D5C60664
21 changed files with 12 additions and 289 deletions

View File

@ -21,58 +21,8 @@ rvm:
- jruby-head
- rbx-2
gemfile:
- gemfiles/mime_types_1.16.gemfile
- gemfiles/mime_types_2.0.gemfile
- gemfiles/mime_types_2.1.gemfile
- gemfiles/mime_types_2.2.gemfile
- gemfiles/mime_types_2.3.gemfile
- gemfiles/mime_types_2.4.gemfile
- gemfiles/mime_types_2.5.gemfile
- gemfiles/mime_types_2.6.gemfile
- gemfiles/mime_types_2.6_columnar.gemfile
- gemfiles/mime_types_2.99.gemfile
- gemfiles/mime_types_3.0.gemfile
- gemfiles/mime_types_edge.gemfile
matrix:
exclude:
- rvm: 1.8.7
gemfile: gemfiles/mime_types_2.0.gemfile
- rvm: 1.8.7
gemfile: gemfiles/mime_types_2.1.gemfile
- rvm: 1.8.7
gemfile: gemfiles/mime_types_2.2.gemfile
- rvm: 1.8.7
gemfile: gemfiles/mime_types_2.3.gemfile
- rvm: 1.8.7
gemfile: gemfiles/mime_types_2.4.gemfile
- rvm: 1.8.7
gemfile: gemfiles/mime_types_2.5.gemfile
- rvm: 1.8.7
gemfile: gemfiles/mime_types_2.6.gemfile
- rvm: 1.8.7
gemfile: gemfiles/mime_types_2.6_columnar.gemfile
- rvm: 1.8.7
gemfile: gemfiles/mime_types_2.99.gemfile
- rvm: 1.8.7
gemfile: gemfiles/mime_types_2.latest.gemfile
- rvm: 1.8.7
gemfile: gemfiles/mime_types_3.0.gemfile
- rvm: 1.8.7
gemfile: gemfiles/mime_types_edge.gemfile
- rvm: 1.9.2
gemfile: gemfiles/mime_types_3.0.gemfile
- rvm: 1.9.2
gemfile: gemfiles/mime_types_edge.gemfile
- rvm: 1.9.3
gemfile: gemfiles/mime_types_3.0.gemfile
- rvm: 1.9.3
gemfile: gemfiles/mime_types_edge.gemfile
- rvm: jruby
gemfile: gemfiles/mime_types_3.0.gemfile
- rvm: jruby
gemfile: gemfiles/mime_types_edge.gemfile
allow_failures:
- rvm: ruby-head
- rvm: jruby-9.0.5.0

View File

@ -1,47 +0,0 @@
appraise "mime-types-1.16" do
gem "mime-types", "~> 1.16"
end
appraise "mime-types-2.0" do
gem "mime-types", "~> 2.0.0"
end
appraise "mime-types-2.1" do
gem "mime-types", "~> 2.1.0"
end
appraise "mime-types-2.2" do
gem "mime-types", "~> 2.2.0"
end
appraise "mime-types-2.3" do
gem "mime-types", "~> 2.3.0"
end
appraise "mime-types-2.4" do
gem "mime-types", "~> 2.4.0"
end
appraise "mime-types-2.5" do
gem "mime-types", "~> 2.5.0"
end
appraise "mime-types-2.6" do
gem "mime-types", "~> 2.6.0"
end
appraise "mime-types-2.6-columnar" do
gem "mime-types", "~> 2.6.0", :require => 'mime/types/columnar'
end
appraise "mime-types-2.99" do
gem "mime-types", "~> 2.99.0"
end
appraise "mime-types-3.0" do
gem "mime-types", "~> 3.0.0"
end
appraise "mime-types-edge" do
gem "mime-types", :github => "mime-types/ruby-mime-types"
end

View File

@ -3,6 +3,9 @@
Features:
* #853 - `Mail::Message#set_sort_order` overrides the default message part sort order. (rafbm)
Performance:
* #1059 - Switch from mime-types to mini_mime for a much smaller memory footprint. (SamSaffron)
Compatibility:
* #655 - Sort attachments to the end of the parts list to work around email clients that may mistake a text attachment for the message body. (npickens)

View File

@ -1,2 +1 @@
tlsmail: if ruby < 1.8.6... we could make it optional, or embed it in Mail
mime/types: I think we embed a simplified version, or help maintain it, it is old (2006)

11
Gemfile
View File

@ -4,11 +4,8 @@ gemspec
gem 'tlsmail', '~> 0.0.1' if RUBY_VERSION <= '1.8.6'
gem 'jruby-openssl', :platforms => :jruby
gem 'rake', '< 11.0', :platforms => :ruby_18
gem 'rdoc', '< 4.3', :platforms => [ :ruby_18, :ruby_19 ]
gem 'mime-types', '< 2.0', :platforms => [ :ruby_18, :ruby_19 ]
# For gems not required to run tests
group :local_development, :test do
gem 'appraisal', '~> 1.0' unless RUBY_VERSION < '1.9'
end
gem 'rake', '< 11.0' if RUBY_VERSION < '1.9.3'
gem 'rdoc', '< 4.3' if RUBY_VERSION < '2.0'
gem 'mini_mime', :github => 'discourse/mini_mime'

View File

@ -19,11 +19,5 @@ RSpec::Core::RakeTask.new(:spec) do |t|
t.rspec_opts = %w(--backtrace --color)
end
begin
require "appraisal"
rescue LoadError, SyntaxError
warn "Appraisal is only available in test/development on Ruby 1.9+"
end
# load custom rake tasks
Dir["#{File.dirname(__FILE__)}/tasks/**/*.rake"].sort.each { |ext| load ext }

View File

@ -1,14 +0,0 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "jruby-openssl", :platforms => :jruby
gem "rake", "< 11.0", :platforms => :ruby_18
gem "rdoc", "< 4.3", :platforms => [:ruby_18, :ruby_19]
gem "mime-types", "~> 1.16"
group :local_development, :test do
gem "appraisal", "~> 1.0"
end
gemspec :path => "../"

View File

@ -1,14 +0,0 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "jruby-openssl", :platforms => :jruby
gem "rake", "< 11.0", :platforms => :ruby_18
gem "rdoc", "< 4.3", :platforms => [:ruby_18, :ruby_19]
gem "mime-types", "~> 2.0.0"
group :local_development, :test do
gem "appraisal", "~> 1.0"
end
gemspec :path => "../"

View File

@ -1,14 +0,0 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "jruby-openssl", :platforms => :jruby
gem "rake", "< 11.0", :platforms => :ruby_18
gem "rdoc", "< 4.3", :platforms => [:ruby_18, :ruby_19]
gem "mime-types", "~> 2.1.0"
group :local_development, :test do
gem "appraisal", "~> 1.0"
end
gemspec :path => "../"

View File

@ -1,14 +0,0 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "jruby-openssl", :platforms => :jruby
gem "rake", "< 11.0", :platforms => :ruby_18
gem "rdoc", "< 4.3", :platforms => [:ruby_18, :ruby_19]
gem "mime-types", "~> 2.2.0"
group :local_development, :test do
gem "appraisal", "~> 1.0"
end
gemspec :path => "../"

View File

@ -1,14 +0,0 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "jruby-openssl", :platforms => :jruby
gem "rake", "< 11.0", :platforms => :ruby_18
gem "rdoc", "< 4.3", :platforms => [:ruby_18, :ruby_19]
gem "mime-types", "~> 2.3.0"
group :local_development, :test do
gem "appraisal", "~> 1.0"
end
gemspec :path => "../"

View File

@ -1,14 +0,0 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "jruby-openssl", :platforms => :jruby
gem "rake", "< 11.0", :platforms => :ruby_18
gem "rdoc", "< 4.3", :platforms => [:ruby_18, :ruby_19]
gem "mime-types", "~> 2.4.0"
group :local_development, :test do
gem "appraisal", "~> 1.0"
end
gemspec :path => "../"

View File

@ -1,14 +0,0 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "jruby-openssl", :platforms => :jruby
gem "rake", "< 11.0", :platforms => :ruby_18
gem "rdoc", "< 4.3", :platforms => [:ruby_18, :ruby_19]
gem "mime-types", "~> 2.5.0"
group :local_development, :test do
gem "appraisal", "~> 1.0"
end
gemspec :path => "../"

View File

@ -1,14 +0,0 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "jruby-openssl", :platforms => :jruby
gem "rake", "< 11.0", :platforms => :ruby_18
gem "rdoc", "< 4.3", :platforms => [:ruby_18, :ruby_19]
gem "mime-types", "~> 2.6.0"
group :local_development, :test do
gem "appraisal", "~> 1.0"
end
gemspec :path => "../"

View File

@ -1,14 +0,0 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "jruby-openssl", :platforms => :jruby
gem "rake", "< 11.0", :platforms => :ruby_18
gem "rdoc", "< 4.3", :platforms => [:ruby_18, :ruby_19]
gem "mime-types", "~> 2.6.0", :require => "mime/types/columnar"
group :local_development, :test do
gem "appraisal", "~> 1.0"
end
gemspec :path => "../"

View File

@ -1,14 +0,0 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "jruby-openssl", :platforms => :jruby
gem "rake", "< 11.0", :platforms => :ruby_18
gem "rdoc", "< 4.3", :platforms => [:ruby_18, :ruby_19]
gem "mime-types", "~> 2.99.0"
group :local_development, :test do
gem "appraisal", "~> 1.0"
end
gemspec :path => "../"

View File

@ -1,14 +0,0 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "jruby-openssl", :platforms => :jruby
gem "rake", "< 11.0", :platforms => :ruby_18
gem "rdoc", "< 4.3", :platforms => [:ruby_18, :ruby_19]
gem "mime-types", "~> 3.0.0"
group :local_development, :test do
gem "appraisal", "~> 1.0"
end
gemspec :path => "../"

View File

@ -1,14 +0,0 @@
# This file was generated by Appraisal
source "https://rubygems.org"
gem "jruby-openssl", :platforms => :jruby
gem "rake", "< 11.0", :platforms => :ruby_18
gem "rdoc", "< 4.3", :platforms => [:ruby_18, :ruby_19]
gem "mime-types", :github => "mime-types/ruby-mime-types"
group :local_development, :test do
gem "appraisal", "~> 1.0"
end
gemspec :path => "../"

View File

@ -7,13 +7,7 @@ module Mail # :doc:
require 'uri'
require 'net/smtp'
begin
# Use mime/types/columnar if available, for reduced memory usage
require 'mime/types/columnar'
rescue LoadError
require 'mime/types'
end
require 'mini_mime'
if RUBY_VERSION <= '1.8.6'
begin

View File

@ -60,7 +60,7 @@ module Mail
if value[:mime_type]
default_values[:content_type] = value.delete(:mime_type)
@mime_type = MIME::Types[default_values[:content_type]].first
@mime_type = MiniMime.lookup_by_content_type(default_values[:content_type])
default_values[:content_transfer_encoding] ||= guess_encoding
end
@ -99,7 +99,8 @@ module Mail
filename = filename.encode(Encoding::UTF_8) if filename.respond_to?(:encode)
end
@mime_type = MIME::Types.type_for(filename).first
@mime_type = MiniMime.lookup_by_filename(filename)
@mime_type && @mime_type.content_type
end
end

View File

@ -14,7 +14,7 @@ Gem::Specification.new do |s|
s.extra_rdoc_files = ["README.md", "CONTRIBUTING.md", "CHANGELOG.rdoc", "TODO.rdoc"]
s.rdoc_options << '--exclude' << 'lib/mail/values/unicode_tables.dat'
s.add_dependency('mime-types', [">= 1.16", "< 4"])
s.add_dependency('mini_mime', '>= 0.1.1')
s.add_development_dependency('bundler', '>= 1.0.3')
s.add_development_dependency('rake', '> 0.8.7')