Slide 1

Slide 1 text

Write simpler, faster Rails code Philly.rb, June 2017

Slide 2

Slide 2 text

Ernesto Tagwerker @etagwerker Founder & Software Developer at Ombu Labs

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Performance Optimization 4

Slide 5

Slide 5 text

Premature Optimization

Slide 6

Slide 6 text

Rails does not scale

Slide 7

Slide 7 text

Rails Worst Practices

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

It’s not someone else’s fault

Slide 10

Slide 10 text

Your code is the problem

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

Best Practices 12

Slide 13

Slide 13 text

https://github.com/bbatsov/ruby-style-guide (rubocop)

Slide 14

Slide 14 text

Ruby or Rails? http://railshurts.com/quiz/ 14

Slide 15

Slide 15 text

ActiveRecord or Array? 15

Slide 16

Slide 16 text

rubocop for performance? 16

Slide 17

Slide 17 text

Bad Code vs. Good Code 17

Slide 18

Slide 18 text

# Bad return true unless OBJECT.nil? # Good return true if OBJECT Example #1

Slide 19

Slide 19 text

require 'benchmark/ips' ["foobar", nil, 89, 99.0].each do |object| puts "Testing with #{object.class}" Benchmark.ips do |x| x.report("unless nil") do true unless object.nil? end x.report("if object") do true if object end x.compare! end end

Slide 20

Slide 20 text

String Testing with String Warming up -------------------------------------- unless nil 233.769k i/100ms if object 228.953k i/100ms Calculating ------------------------------------- unless nil 8.809M (± 4.9%) i/s - 43.949M in 5.001814s if object 10.089M (±11.7%) i/s - 49.225M in 5.008738s Comparison: if object: 10088708.4 i/s unless nil: 8808975.9 i/s - same-ish: difference falls within error

Slide 21

Slide 21 text

NilClass Testing with NilClass Warming up -------------------------------------- unless nil 226.417k i/100ms if object 248.809k i/100ms Calculating ------------------------------------- unless nil 8.869M (± 7.2%) i/s - 44.151M in 5.012598s if object 10.753M (± 5.4%) i/s - 53.743M in 5.013131s Comparison: if object: 10753466.5 i/s unless nil: 8868727.3 i/s - 1.21x slower done

Slide 22

Slide 22 text

Fixnum Testing with Fixnum Warming up -------------------------------------- unless nil 239.689k i/100ms if object 241.611k i/100ms Calculating ------------------------------------- unless nil 9.016M (± 4.9%) i/s - 45.062M in 5.009988s if object 10.604M (± 5.7%) i/s - 52.913M in 5.007991s Comparison: if object: 10603605.1 i/s unless nil: 9016116.6 i/s - 1.18x slower

Slide 23

Slide 23 text

Float Testing with Float Warming up -------------------------------------- unless nil 237.572k i/100ms if object 238.965k i/100ms Calculating ------------------------------------- unless nil 8.881M (± 4.5%) i/s - 44.426M in 5.012954s if object 10.473M (± 6.9%) i/s - 52.094M in 5.000457s Comparison: if object: 10472909.6 i/s unless nil: 8881134.7 i/s - 1.18x slower

Slide 24

Slide 24 text

# Slower, harder to read return true unless OBJECT.nil? # Faster, easier to read return true if OBJECT Example #1

Slide 25

Slide 25 text

# Slower, harder to read ARRAY.map {|x| x * 2 }.flatten # Faster, easier to read ARRAY.flat_map {|x| x * 2 } Example #2

Slide 26

Slide 26 text

$ ruby benchmarks/map_flatten_vs_flat_map.rb Ruby version: 2.3.3 Warming up -------------------------------------- map-flatten 1.926k i/100ms flat_map 2.529k i/100ms Calculating ------------------------------------- map-flatten 19.693k (± 2.3%) i/s - 100.152k in 5.088410s flat_map 25.774k (± 2.5%) i/s - 128.979k in 5.007299s Comparison: flat_map: 25774.3 i/s map-flatten: 19692.9 i/s - 1.31x slower

Slide 27

Slide 27 text

# Much Slower Time.parse("2000-01-01 06:00:00 UTC") # Faster Time.at(946685160).utc Example #3

Slide 28

Slide 28 text

$ bundle exec ruby benchmarks/time/parse_vs_at.rb Ruby version: 2.3.3 Warming up -------------------------------------- Time.parse 3.287k i/100ms Time.at 66.086k i/100ms Calculating ------------------------------------- Time.parse 34.052k (± 3.2%) i/s - 170.924k in 5.024737s Time.at 842.576k (± 2.2%) i/s - 4.230M in 5.022207s Comparison: Time.at: 842576.2 i/s Time.parse: 34051.9 i/s - 24.74x slower

Slide 29

Slide 29 text

# Less Readable, Slightly Faster "yay" if !OBJECT.blank? # More Readable, Slightly Slower "yay" if OBJECT.present? Example #4

Slide 30

Slide 30 text

String Testing with String Warming up -------------------------------------- !blank? 105.143k i/100ms present? 106.503k i/100ms Calculating ------------------------------------- !blank? 1.704M (± 5.5%) i/s - 8.517M in 5.015480s present? 1.623M (± 5.8%) i/s - 8.201M in 5.071236s Comparison: !blank?: 1703644.7 i/s present?: 1623253.4 i/s - same-ish: difference falls within error

Slide 31

Slide 31 text

NilClass Testing with NilClass Warming up -------------------------------------- !blank? 214.770k i/100ms present? 209.608k i/100ms Calculating ------------------------------------- !blank? 7.903M (± 8.1%) i/s - 39.303M in 5.013230s present? 6.673M (±15.2%) i/s - 31.860M in 5.018127s Comparison: !blank?: 7902595.4 i/s present?: 6673257.2 i/s - same-ish: difference falls within error

Slide 32

Slide 32 text

Fixnum Testing with Fixnum Warming up -------------------------------------- !blank? 204.972k i/100ms present? 212.165k i/100ms Calculating ------------------------------------- !blank? 8.525M (± 6.6%) i/s - 42.429M in 5.007297s present? 7.061M (± 4.5%) i/s - 35.432M in 5.028556s Comparison: !blank?: 8525338.3 i/s present?: 7061102.1 i/s - 1.21x slower

Slide 33

Slide 33 text

Float Testing with Float Warming up -------------------------------------- !blank? 228.892k i/100ms present? 222.103k i/100ms Calculating ------------------------------------- !blank? 8.441M (± 4.4%) i/s - 42.345M in 5.026892s present? 7.228M (± 4.1%) i/s - 36.203M in 5.016829s Comparison: !blank?: 8440759.9 i/s present?: 7228480.9 i/s - 1.17x slower

Slide 34

Slide 34 text

Why? !OBJECT.blank? faster than OBJECT.present?

Slide 35

Slide 35 text

Why?

Slide 36

Slide 36 text

# Slower phone = Hash.new(number: number) phone[:number] # Faster Phone = Struct.new(:number) phone = Phone.new(number) phone.number Example #5

Slide 37

Slide 37 text

$ bundle exec ruby benchmarks/struct_vs_hash.rb Ruby version: 2.3.3 Warming up -------------------------------------- struct 22.148k i/100ms hash 4.688k i/100ms Calculating ------------------------------------- struct 240.460k (± 2.7%) i/s - 1.218M in 5.069592s hash 47.926k (± 3.9%) i/s - 243.776k in 5.094413s Comparison: struct: 240459.9 i/s hash: 47926.1 i/s - 5.02x slower

Slide 38

Slide 38 text

# Slower Post.select(:id).map(&:id) # Faster Post.pluck(:id) Example #6

Slide 39

Slide 39 text

$ bundle exec rake benches:pluck_vs_map Ruby version: 2.3.3 Warming up -------------------------------------- map(&:id) 11.000 i/100ms pluck(:id) 80.000 i/100ms Calculating ------------------------------------- map(&:id) 124.205 (±11.3%) i/s - 616.000 in 5.026999s pluck(:id) 807.127 (± 2.0%) i/s - 4.080k in 5.057062s Comparison: pluck(:id): 807.1 i/s map(&:id): 124.2 i/s - 6.50x slower

Slide 40

Slide 40 text

Lesson.select(:id).limit(10).map(&:id) Lesson Load (1.9ms) SELECT "lessons"."id" FROM "lessons" LIMIT $1 [["LIMIT", 10]] vs. Lesson.limit(10).pluck(:id) (0.4ms) SELECT "lessons"."id" FROM "lessons" LIMIT $1 [["LIMIT", 10]]

Slide 41

Slide 41 text

# Slooow Post.where(title: nil).present? # Better Post.where(title: nil).any? # Fastest Post.where(title: nil).exists? Example #7

Slide 42

Slide 42 text

$ bundle exec rake benches:exists_vs_any Warming up -------------------------------------- present? 7.000 i/100ms any? 98.000 i/100ms exists? 174.000 i/100ms Calculating ------------------------------------- present? 89.888 (±12.2%) i/s - 448.000 in 5.059417s any? 1.088k (±14.1%) i/s - 5.292k in 5.025290s exists? 1.817k (± 7.7%) i/s - 9.048k in 5.013826s Comparison: exists?: 1816.5 i/s any?: 1087.6 i/s - 1.67x slower present?: 89.9 i/s - 20.21x slower

Slide 43

Slide 43 text

> Lesson.where(id: 2).present? Lesson Load (1.5ms) SELECT "lessons".* FROM "lessons" WHERE "lessons"."id" = $1 [["id", 2]] => false vs. > Lesson.where(id: 2).any? (0.4ms) SELECT COUNT(*) FROM "lessons" WHERE "lessons"."id" = $1 [["id", 2]] => false vs. > Lesson.where(id: 2).exists? Lesson Exists (0.4ms) SELECT 1 AS one FROM "lessons" WHERE "lessons"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]] => false

Slide 44

Slide 44 text

Tools 44

Slide 45

Slide 45 text

https://github.com/ evanphx/benchmark-ips Compare the iterations per seconds between two or more blocks of code

Slide 46

Slide 46 text

Benchmark.ips do |x| x.report("present?") do Post.where.not(title: nil).present? end x.report("any?") do Post.where.not(title: nil).any? end x.report("exists?") do Post.where.not(title: nil).exists? end x.compare! end

Slide 47

Slide 47 text

# RSpec + benchmark/ips matchers describe "#all_hours_new" do let(:days) { Date::DAYNAMES } context "a venue is always closed" do it "performs adequately" do expect do subject.all_hours_new end.to iterate_per_second(190) end end end

Slide 48

Slide 48 text

https://github.com/ JuanitoFatas/fast-ruby A collection of Ruby code comparisons

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

https://github.com/ DamirSvrtan/fasterer A Ruby gem based on fast-ruby

Slide 52

Slide 52 text

ombushop $ fasterer app/controllers/admin/base_controller.rb Using tr is faster than gsub when replacing a single character in a string with another single character. Occurred at lines: 141. ... spec/requests/steps/checkout_steps.rb Hash#fetch with second argument is slower than Hash#fetch with block. Occurred at lines: 13, 14, 20. 909 files inspected, 29 offenses detected

Slide 53

Slide 53 text

# .fasterer.yml speedups: rescue_vs_respond_to: true module_eval: true shuffle_first_vs_sample: true for_loop_vs_each: true ... block_vs_symbol_to_proc: true proc_call_vs_yield: true gsub_vs_tr: true select_last_vs_reverse_detect: true getter_vs_attr_reader: true setter_vs_attr_writer: true exclude_paths: - 'db/schema.rb' Configuration

Slide 54

Slide 54 text

Measure twice, cut once 54

Slide 55

Slide 55 text

* Warning: Use with caution

Slide 56

Slide 56 text

Resources 56

Slide 57

Slide 57 text

https://github.com/ombulabs/benches/

Slide 58

Slide 58 text

https://github.com/evanphx/benchmark-ips https://github.com/evanphx/benchmark.fyi https://speakerdeck.com/sferik/writing-fast-ruby https://github.com/JuanitoFatas/fast-ruby https://github.com/DamirSvrtan/fasterer

Slide 59

Slide 59 text

https://github.com/flyerhzm/bullet https://github.com/presidentbeef/brakeman https://github.com/LendingHome/zero_downtime_migrations https://github.com/brigade/overcommit https://github.com/schneems/derailed_benchmarks https://github.com/seattlerb/debride

Slide 60

Slide 60 text

Thank you! @etagwerker 60