1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/activerecord/test/cases/result_test.rb
Aaron Patterson 7d58fa87ef
Speed up homogeneous AR lists / reduce allocations
This commit speeds up allocating homogeneous lists of AR objects.  We
can know if the result set contains an STI column before initializing
every AR object, so this change pulls the "does this result set contain
an STI column?" test up, then uses a specialized instantiation function.
This way we only have to check for an STI column once rather than N
times.

This change also introduces a new initialization function that is meant
for use when allocating AR objects that come from the database.  Doing
this allows us to eliminate one hash allocation per AR instance.

Here is a benchmark:

```ruby
require 'active_record'
require 'benchmark/ips'

ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"

ActiveRecord::Migration.verbose = false

ActiveRecord::Schema.define do
  create_table :users, force: true do |t|
    t.string :name
    t.timestamps null: false
  end
end

class User < ActiveRecord::Base; end

2000.times do
  User.create!(name: "Gorby")
end

Benchmark.ips do |x|
  x.report("find") do
    User.limit(2000).to_a
  end
end
```

Results:

Before:

```
[aaron@TC activerecord (master)]$ be ruby -I lib:~/git/allocation_tracer/lib speed.rb
Warming up --------------------------------------
                find     5.000  i/100ms
Calculating -------------------------------------
                find     56.192  (± 3.6%) i/s -    285.000  in   5.080940s
```

After:

```
[aaron@TC activerecord (homogeneous-allocation)]$ be ruby -I lib:~/git/allocation_tracer/lib speed.rb
Warming up --------------------------------------
                find     7.000  i/100ms
Calculating -------------------------------------
                find     72.204  (± 2.8%) i/s -    364.000  in   5.044592s
```
2018-06-25 16:00:04 -07:00

95 lines
2.8 KiB
Ruby

# frozen_string_literal: true
require "cases/helper"
module ActiveRecord
class ResultTest < ActiveRecord::TestCase
def result
Result.new(["col_1", "col_2"], [
["row 1 col 1", "row 1 col 2"],
["row 2 col 1", "row 2 col 2"],
["row 3 col 1", "row 3 col 2"],
])
end
test "includes_column?" do
assert result.includes_column?("col_1")
assert_not result.includes_column?("foo")
end
test "length" do
assert_equal 3, result.length
end
test "to_hash returns row_hashes" do
assert_equal [
{ "col_1" => "row 1 col 1", "col_2" => "row 1 col 2" },
{ "col_1" => "row 2 col 1", "col_2" => "row 2 col 2" },
{ "col_1" => "row 3 col 1", "col_2" => "row 3 col 2" },
], result.to_hash
end
test "first returns first row as a hash" do
assert_equal(
{ "col_1" => "row 1 col 1", "col_2" => "row 1 col 2" }, result.first)
end
test "last returns last row as a hash" do
assert_equal(
{ "col_1" => "row 3 col 1", "col_2" => "row 3 col 2" }, result.last)
end
test "each with block returns row hashes" do
result.each do |row|
assert_equal ["col_1", "col_2"], row.keys
end
end
test "each without block returns an enumerator" do
result.each.with_index do |row, index|
assert_equal ["col_1", "col_2"], row.keys
assert_kind_of Integer, index
end
end
test "each without block returns a sized enumerator" do
assert_equal 3, result.each.size
end
test "cast_values returns rows after type casting" do
values = [["1.1", "2.2"], ["3.3", "4.4"]]
columns = ["col1", "col2"]
types = { "col1" => Type::Integer.new, "col2" => Type::Float.new }
result = Result.new(columns, values, types)
assert_equal [[1, 2.2], [3, 4.4]], result.cast_values
end
test "cast_values uses identity type for unknown types" do
values = [["1.1", "2.2"], ["3.3", "4.4"]]
columns = ["col1", "col2"]
types = { "col1" => Type::Integer.new }
result = Result.new(columns, values, types)
assert_equal [[1, "2.2"], [3, "4.4"]], result.cast_values
end
test "cast_values returns single dimensional array if single column" do
values = [["1.1"], ["3.3"]]
columns = ["col1"]
types = { "col1" => Type::Integer.new }
result = Result.new(columns, values, types)
assert_equal [1, 3], result.cast_values
end
test "cast_values can receive types to use instead" do
values = [["1.1", "2.2"], ["3.3", "4.4"]]
columns = ["col1", "col2"]
types = { "col1" => Type::Integer.new, "col2" => Type::Float.new }
result = Result.new(columns, values, types)
assert_equal [[1.1, 2.2], [3.3, 4.4]], result.cast_values("col1" => Type::Float.new)
end
end
end