Reduce allocations in dependency construction

By always passing around a single, unnested array rather than splatting
and then defensively flattening and compacting things, we can avoid
allocating a bunch of unnecessary arrays. This gives a performance boost
of roughly 4% when enumerating 2500 formulae, and has the side effect of
cleaning up the dependency API.
This commit is contained in:
Jack Nagel 2013-05-06 16:08:50 -05:00
parent 894a6c9776
commit b322020338
9 changed files with 58 additions and 61 deletions

View File

@ -6,9 +6,9 @@ class Dependency
attr_reader :name, :tags attr_reader :name, :tags
def initialize(name, *tags) def initialize(name, tags=[])
@name = name @name = name
@tags = tags.flatten.compact @tags = tags
end end
def to_s def to_s

View File

@ -38,87 +38,89 @@ class DependencyCollector
end end
def build(spec) def build(spec)
spec, tag = case spec spec, tags = case spec
when Hash then spec.shift when Hash then spec.shift
else spec else spec
end end
parse_spec(spec, tag) parse_spec(spec, Array(tags))
end end
private private
def parse_spec spec, tag def parse_spec(spec, tags)
case spec case spec
when String when String
parse_string_spec(spec, tag) parse_string_spec(spec, tags)
when Symbol when Symbol
parse_symbol_spec(spec, tag) parse_symbol_spec(spec, tags)
when Requirement, Dependency when Requirement, Dependency
spec spec
when Class when Class
parse_class_spec(spec, tag) parse_class_spec(spec, tags)
else else
raise TypeError, "Unsupported type #{spec.class} for #{spec}" raise TypeError, "Unsupported type #{spec.class} for #{spec}"
end end
end end
def parse_string_spec(spec, tag) def parse_string_spec(spec, tags)
if tag && LANGUAGE_MODULES.include?(tag) if tags.empty?
Dependency.new(spec, tags)
elsif (tag = tags.first) && LANGUAGE_MODULES.include?(tag)
LanguageModuleDependency.new(tag, spec) LanguageModuleDependency.new(tag, spec)
else else
Dependency.new(spec, tag) Dependency.new(spec, tags)
end end
end end
def parse_symbol_spec spec, tag def parse_symbol_spec(spec, tags)
case spec case spec
when :autoconf, :automake, :bsdmake, :libtool, :libltdl when :autoconf, :automake, :bsdmake, :libtool, :libltdl
# Xcode no longer provides autotools or some other build tools # Xcode no longer provides autotools or some other build tools
autotools_dep(spec, tag) autotools_dep(spec, tags)
when :x11 then X11Dependency.new(spec.to_s, tag) when :x11 then X11Dependency.new(spec.to_s, tags)
when *X11Dependency::Proxy::PACKAGES when *X11Dependency::Proxy::PACKAGES
x11_dep(spec, tag) x11_dep(spec, tags)
when :cairo, :pixman when :cairo, :pixman
# We no longer use X11 psuedo-deps for cairo or pixman, # We no longer use X11 psuedo-deps for cairo or pixman,
# so just return a standard formula dependency. # so just return a standard formula dependency.
Dependency.new(spec.to_s, tag) Dependency.new(spec.to_s, tags)
when :xcode then XcodeDependency.new(tag) when :xcode then XcodeDependency.new(tags)
when :mysql then MysqlDependency.new(tag) when :mysql then MysqlDependency.new(tags)
when :postgresql then PostgresqlDependency.new(tag) when :postgresql then PostgresqlDependency.new(tags)
when :tex then TeXDependency.new(tag) when :tex then TeXDependency.new(tags)
when :clt then CLTDependency.new(tag) when :clt then CLTDependency.new(tags)
when :arch then ArchRequirement.new(tag) when :arch then ArchRequirement.new(tags)
when :hg then MercurialDependency.new(tag) when :hg then MercurialDependency.new(tags)
else else
raise "Unsupported special dependency #{spec}" raise "Unsupported special dependency #{spec}"
end end
end end
def parse_class_spec(spec, tag) def parse_class_spec(spec, tags)
if spec < Requirement if spec < Requirement
spec.new(tag) spec.new(tags)
else else
raise TypeError, "#{spec} is not a Requirement subclass" raise TypeError, "#{spec} is not a Requirement subclass"
end end
end end
def x11_dep(spec, tag) def x11_dep(spec, tags)
if MacOS.version >= :mountain_lion if MacOS.version >= :mountain_lion
Dependency.new(spec.to_s, tag) Dependency.new(spec.to_s, tags)
else else
X11Dependency::Proxy.for(spec.to_s, tag) X11Dependency::Proxy.for(spec.to_s, tags)
end end
end end
def autotools_dep(spec, tag) def autotools_dep(spec, tags)
case spec
when :libltdl then spec, tag = :libtool, Array(tag)
else tag = Array(tag) << :build
end
unless MacOS::Xcode.provides_autotools? unless MacOS::Xcode.provides_autotools?
Dependency.new(spec.to_s, tag) case spec
when :libltdl then spec = :libtool
else tags << :build
end
Dependency.new(spec.to_s, tags)
end end
end end
end end

View File

@ -10,8 +10,8 @@ class Requirement
attr_reader :tags, :name attr_reader :tags, :name
def initialize(*tags) def initialize(tags=[])
@tags = tags.flatten.compact @tags = tags
@tags << :build if self.class.build @tags << :build if self.class.build
@name ||= infer_name @name ||= infer_name
end end

View File

@ -7,7 +7,7 @@ class LanguageModuleDependency < Requirement
@language = language @language = language
@module_name = module_name @module_name = module_name
@import_name = import_name @import_name = import_name
super super([language, module_name, import_name])
end end
satisfy { quiet_system(*the_test) } satisfy { quiet_system(*the_test) }

View File

@ -9,8 +9,7 @@ class X11Dependency < Requirement
env { ENV.x11 } env { ENV.x11 }
def initialize(name="x11", *tags) def initialize(name="x11", tags=[])
tags.flatten!
@name = name @name = name
@min_version = tags.shift if /(\d\.)+\d/ === tags.first @min_version = tags.shift if /(\d\.)+\d/ === tags.first
super(tags) super(tags)
@ -66,19 +65,19 @@ class X11Dependency < Requirement
end end
end end
def for(name, *tags) def for(name, tags=[])
constant = name.capitalize constant = name.capitalize
if defines_const?(constant) if defines_const?(constant)
klass = const_get(constant) klass = const_get(constant)
else else
klass = Class.new(self) do klass = Class.new(self) do
def initialize(name, *tags) super end def initialize(name, tags) super end
end end
const_set(constant, klass) const_set(constant, klass)
end end
klass.new(name, *tags) klass.new(name, tags)
end end
end end
end end

View File

@ -17,25 +17,25 @@ class ComparableSetTests < Test::Unit::TestCase
def test_comparison_prefers_larger def test_comparison_prefers_larger
@set << X11Dependency.new @set << X11Dependency.new
@set << X11Dependency.new('x11', '2.6') @set << X11Dependency.new('x11', %w{2.6})
assert_equal 1, @set.count assert_equal 1, @set.count
assert_equal [X11Dependency.new('x11', '2.6')], @set.to_a assert_equal [X11Dependency.new('x11', %w{2.6})], @set.to_a
end end
def test_comparison_does_not_merge_smaller def test_comparison_does_not_merge_smaller
@set << X11Dependency.new('x11', '2.6') @set << X11Dependency.new('x11', %w{2.6})
@set << X11Dependency.new @set << X11Dependency.new
assert_equal 1, @set.count assert_equal 1, @set.count
assert_equal [X11Dependency.new('x11', '2.6')], @set.to_a assert_equal [X11Dependency.new('x11', %w{2.6})], @set.to_a
end end
def test_merging_sets def test_merging_sets
@set << X11Dependency.new @set << X11Dependency.new
@set << Requirement.new @set << Requirement.new
reqs = Set.new [X11Dependency.new('x11', '2.6'), Requirement.new] reqs = Set.new [X11Dependency.new('x11', %w{2.6}), Requirement.new]
assert_same @set, @set.merge(reqs) assert_same @set, @set.merge(reqs)
assert_equal 2, @set.count assert_equal 2, @set.count
assert_equal X11Dependency.new('x11', '2.6'), @set.find {|r| r.is_a? X11Dependency} assert_equal X11Dependency.new('x11', %w{2.6}), @set.find {|r| r.is_a? X11Dependency}
end end
end end

View File

@ -20,7 +20,7 @@ end
class DependencyTests < Test::Unit::TestCase class DependencyTests < Test::Unit::TestCase
def test_accepts_single_tag def test_accepts_single_tag
dep = Dependency.new("foo", "bar") dep = Dependency.new("foo", %w{bar})
assert_equal %w{bar}, dep.tags assert_equal %w{bar}, dep.tags
end end
@ -30,7 +30,7 @@ class DependencyTests < Test::Unit::TestCase
end end
def test_preserves_symbol_tags def test_preserves_symbol_tags
dep = Dependency.new("foo", :build) dep = Dependency.new("foo", [:build])
assert_equal [:build], dep.tags assert_equal [:build], dep.tags
end end

View File

@ -30,7 +30,7 @@ class DependencyCollectorTests < Test::Unit::TestCase
end end
def test_dependency_tags def test_dependency_tags
assert Dependency.new('foo', :build).build? assert Dependency.new('foo', [:build]).build?
assert Dependency.new('foo', [:build, :optional]).optional? assert Dependency.new('foo', [:build, :optional]).optional?
assert Dependency.new('foo', [:universal]).options.include? '--universal' assert Dependency.new('foo', [:universal]).options.include? '--universal'
assert_empty Dependency.new('foo').tags assert_empty Dependency.new('foo').tags

View File

@ -3,27 +3,23 @@ require 'requirement'
class RequirementTests < Test::Unit::TestCase class RequirementTests < Test::Unit::TestCase
def test_accepts_single_tag def test_accepts_single_tag
dep = Requirement.new("bar") dep = Requirement.new(%w{bar})
assert_equal %w{bar}, dep.tags assert_equal %w{bar}, dep.tags
end end
def test_accepts_multiple_tags def test_accepts_multiple_tags
dep = Requirement.new(%w{bar baz}) dep = Requirement.new(%w{bar baz})
assert_equal %w{bar baz}.sort, dep.tags.sort assert_equal %w{bar baz}.sort, dep.tags.sort
dep = Requirement.new(*%w{bar baz})
assert_equal %w{bar baz}.sort, dep.tags.sort
end end
def test_preserves_symbol_tags def test_preserves_symbol_tags
dep = Requirement.new(:build) dep = Requirement.new([:build])
assert_equal [:build], dep.tags assert_equal [:build], dep.tags
end end
def test_accepts_symbol_and_string_tags def test_accepts_symbol_and_string_tags
dep = Requirement.new([:build, "bar"]) dep = Requirement.new([:build, "bar"])
assert_equal [:build, "bar"], dep.tags assert_equal [:build, "bar"], dep.tags
dep = Requirement.new(:build, "bar")
assert_equal [:build, "bar"], dep.tags
end end
def test_dsl_fatal def test_dsl_fatal