Skip to content

Instantly share code, notes, and snippets.

@johnathanludwig
Last active December 11, 2019 14:30
Show Gist options
  • Save johnathanludwig/96fc33fc135ee558e0f09fb23a8cf3f1 to your computer and use it in GitHub Desktop.
Save johnathanludwig/96fc33fc135ee558e0f09fb23a8cf3f1 to your computer and use it in GitHub Desktop.
Test includes vs left_joins performance
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
# Activate the gem you are reporting the issue against.
gem "activerecord", "~> 5.2.0"
gem "sqlite3"
gem "benchmark-memory"
gem "benchmark-ips"
end
require "active_record"
require "minitest/autorun"
require "logger"
require 'benchmark/ips'
require 'benchmark/memory'
# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
create_table :students, force: true
create_table :teachers, force: true
create_table :class_sessions, force: true
create_table :enrollments, force: true do |t|
t.integer :student_id
t.integer :class_session_id
end
create_table :timeslots, force: true do |t|
t.integer :teacher_id
t.integer :class_session_id
end
end
class Student < ActiveRecord::Base
has_many :enrollments
has_many :class_sessions, through: :enrollments
has_many :teachers, through: :class_sessions
end
class Enrollment < ActiveRecord::Base
belongs_to :student
belongs_to :class_session
end
class ClassSession < ActiveRecord::Base
has_many :enrollments
has_many :students, through: :enrollments
has_many :timeslots
has_many :teachers, through: :timeslots
end
class Timeslot < ActiveRecord::Base
belongs_to :teacher
belongs_to :class_session
end
class Teacher < ActiveRecord::Base
has_many :timeslots
has_many :class_sessions, through: :timeslots
has_many :students, through: :class_sessions
end
class BugTest < Minitest::Test
def test_association_stuff
# student1 = Student.create
# student2 = Student.create
# session1 = ClassSession.create
# session2 = ClassSession.create
# Enrollment.create(student: student1, class_session: session1)
# Enrollment.create(student: student1, class_session: session2)
# Enrollment.create(student: student2, class_session: session1)
# Enrollment.create(student: student2, class_session: session2)
test_case = Proc.new do |benchmark|
benchmark.report('includes') { Student.includes(:teachers).where(teachers: {id: nil}) }
benchmark.report('left_joins') { Student.left_joins(:teachers).where(teachers: {id: nil}) }
benchmark.compare!
end
Benchmark.memory { |x| test_case.call(x) }
Benchmark.ips { |x| test_case.call(x) }
end
end
# Results
# Calculating -------------------------------------
# includes 144.015k memsize ( 4.784k retained)
# 953.000 objects ( 69.000 retained)
# 50.000 strings ( 12.000 retained)
# left_joins 4.320k memsize ( 0.000 retained)
# 78.000 objects ( 0.000 retained)
# 3.000 strings ( 0.000 retained)
# Comparison:
# left_joins: 4320 allocated
# includes: 144015 allocated - 33.34x more
# Warming up --------------------------------------
# includes 2.111k i/100ms
# left_joins 1.970k i/100ms
# Calculating -------------------------------------
# includes 20.722k (± 4.8%) i/s - 103.439k in 5.003226s
# left_joins 21.110k (± 6.7%) i/s - 106.380k in 5.063396s
# Comparison:
# left_joins: 21110.3 i/s
# includes: 20722.3 i/s - same-ish: difference falls within error
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment