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
def initialize(name, *tags)
def initialize(name, tags=[])
@name = name
@tags = tags.flatten.compact
@tags = tags
end
def to_s

View File

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

View File

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

View File

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

View File

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

View File

@ -17,25 +17,25 @@ class ComparableSetTests < Test::Unit::TestCase
def test_comparison_prefers_larger
@set << X11Dependency.new
@set << X11Dependency.new('x11', '2.6')
@set << X11Dependency.new('x11', %w{2.6})
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
def test_comparison_does_not_merge_smaller
@set << X11Dependency.new('x11', '2.6')
@set << X11Dependency.new('x11', %w{2.6})
@set << X11Dependency.new
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
def test_merging_sets
@set << X11Dependency.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_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

View File

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

View File

@ -30,7 +30,7 @@ class DependencyCollectorTests < Test::Unit::TestCase
end
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', [:universal]).options.include? '--universal'
assert_empty Dependency.new('foo').tags

View File

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