提交 c3a2b54b 编写于 作者: R Ryuta Kamizono

PERF: Improve performance of where when using an array of values

This is a smaller alternative of performance improvement, without
refactoring type casting mechanism #39009.

This is relatively a smaller change (but about 40% faster than before),
so I think this could be easier reviewed without discuss about
refactoring type casting mechanism.

This just makes `attribute.in(values)` less allocation from an array of
casted nodes to one casted array node.

```ruby
ids = (1..1000).each.map do |n|
  Post.create!.id
end

Benchmark.ips do |x|
  x.report("where with ids") do
    Post.where(id: ids).to_a
  end

  x.report("where with sanitize") do
    Post.where(ActiveRecord::Base.sanitize_sql(["id IN (?)", ids])).to_a
  end

  x.compare!
end
```

Before:

```
Warming up --------------------------------------
      where with ids     7.000  i/100ms
 where with sanitize    13.000  i/100ms

Calculating -------------------------------------
      where with ids     70.661  (± 5.7%) i/s -    357.000  in   5.072771s
 where with sanitize    130.993  (± 7.6%) i/s -    663.000  in   5.096085s

Comparison:
 where with sanitize:      131.0 i/s
      where with ids:       70.7 i/s - 1.85x  slower
```

After:

```
Warming up --------------------------------------
      where with ids    10.000  i/100ms
 where with sanitize    13.000  i/100ms

Calculating -------------------------------------
      where with ids     98.174  (± 7.1%) i/s -    490.000  in   5.012851s
 where with sanitize    132.289  (± 8.3%) i/s -    663.000  in   5.052728s

Comparison:
 where with sanitize:      132.3 i/s
      where with ids:       98.2 i/s - 1.35x  slower
```
上级 3a38c072
......@@ -20,20 +20,17 @@ def call(attribute, value)
case values.length
when 0 then NullPredicate
when 1 then predicate_builder.build(attribute, values.first)
else
values.map! do |v|
predicate_builder.build_bind_attribute(attribute.name, v)
end
values.empty? ? NullPredicate : attribute.in(values)
else attribute.in(values)
end
unless nils.empty?
if nils.empty?
return values_predicate if ranges.empty?
else
values_predicate = values_predicate.or(predicate_builder.build(attribute, nil))
end
array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) }
array_predicates.unshift(values_predicate)
array_predicates.inject(&:or)
array_predicates = ranges.map! { |range| predicate_builder.build(attribute, range) }
array_predicates.inject(values_predicate, &:or)
end
private
......
......@@ -85,7 +85,7 @@ def contradiction?
predicates.any? do |x|
case x
when Arel::Nodes::In
Array === x.right && x.right.empty?
Arel::Nodes::CastedArray === x.right && x.right.value.empty?
when Arel::Nodes::Equality
x.right.respond_to?(:unboundable?) && x.right.unboundable?
end
......
......@@ -34,6 +34,16 @@ def eql?(other)
alias :== :eql?
end
class CastedArray < Casted # :nodoc:
def value_for_database
if attribute.able_to_type_cast?
value.map { |v| attribute.type_cast_for_database(v) }
else
value
end
end
end
class Quoted < Arel::Nodes::Unary # :nodoc:
alias :value_for_database :value
alias :value_before_type_cast :value
......
......@@ -31,7 +31,7 @@ def eq_any(others)
end
def eq_all(others)
grouping_all :eq, quoted_array(others)
grouping_all :eq, others
end
def between(other)
......@@ -238,7 +238,7 @@ def quoted_node(other)
end
def quoted_array(others)
others.map { |v| quoted_node(v) }
Nodes::CastedArray.new(others, self)
end
def infinity?(value)
......
......@@ -98,6 +98,7 @@ def eql?(other)
def type_cast_for_database(attribute_name, value)
type_caster.type_cast_for_database(attribute_name, value)
rescue ::RangeError
end
def able_to_type_cast?
......
......@@ -81,6 +81,10 @@ def visit_Arel_Nodes_Exists(o, collector)
end
end
def visit_Arel_Nodes_CastedArray(o, collector)
collector << o.value_for_database.map! { |v| quote(v) }.join(", ")
end
def visit_Arel_Nodes_Casted(o, collector)
collector << quote(o.value_for_database).to_s
end
......@@ -512,12 +516,8 @@ def visit_Arel_Table(o, collector)
def visit_Arel_Nodes_In(o, collector)
attr, values = o.left, o.right
if Array === values
unless values.empty?
values.delete_if { |value| unboundable?(value) }
end
return collector << "1=0" if values.empty?
if Arel::Nodes::CastedArray === values
return collector << "1=0" if values.value.empty?
end
visit(attr, collector) << " IN ("
......@@ -527,12 +527,8 @@ def visit_Arel_Nodes_In(o, collector)
def visit_Arel_Nodes_NotIn(o, collector)
attr, values = o.left, o.right
if Array === values
unless values.empty?
values.delete_if { |value| unboundable?(value) }
end
return collector << "1=1" if values.empty?
if Arel::Nodes::CastedArray === values
return collector << "1=1" if values.value.empty?
end
visit(attr, collector) << " NOT IN ("
......
......@@ -618,14 +618,14 @@ class AttributeTest < Arel::Spec
attribute = Attribute.new nil, nil
node = attribute.between(-::Float::INFINITY..::Float::INFINITY)
_(node).must_equal Nodes::NotIn.new(attribute, [])
_(node).must_equal attribute.not_in([])
end
it "can be constructed with a quoted infinite range" do
attribute = Attribute.new nil, nil
node = attribute.between(quoted_range(-::Float::INFINITY, ::Float::INFINITY, false))
_(node).must_equal Nodes::NotIn.new(attribute, [])
_(node).must_equal attribute.not_in([])
end
it "can be constructed with a range ending at Infinity" do
......@@ -707,11 +707,7 @@ class AttributeTest < Arel::Spec
_(node).must_equal Nodes::In.new(
attribute,
[
Nodes::Casted.new(1, attribute),
Nodes::Casted.new(2, attribute),
Nodes::Casted.new(3, attribute),
]
Nodes::CastedArray.new([1, 2, 3], attribute)
)
end
......@@ -831,14 +827,14 @@ class AttributeTest < Arel::Spec
attribute = Attribute.new nil, nil
node = attribute.not_between(-::Float::INFINITY..::Float::INFINITY)
_(node).must_equal Nodes::In.new(attribute, [])
_(node).must_equal attribute.in([])
end
it "can be constructed with a quoted infinite range" do
attribute = Attribute.new nil, nil
node = attribute.not_between(quoted_range(-::Float::INFINITY, ::Float::INFINITY, false))
_(node).must_equal Nodes::In.new(attribute, [])
_(node).must_equal attribute.in([])
end
it "can be constructed with a range ending at Infinity" do
......@@ -934,11 +930,7 @@ class AttributeTest < Arel::Spec
_(node).must_equal Nodes::NotIn.new(
attribute,
[
Nodes::Casted.new(1, attribute),
Nodes::Casted.new(2, attribute),
Nodes::Casted.new(3, attribute),
]
Nodes::CastedArray.new([1, 2, 3], attribute)
)
end
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册