Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up for free
The Value of Being Lazy
Erik Berlin
November 24, 2015
Programming
3
460
The Value of Being Lazy
…or How I Made OpenStruct 10X Faster
Presented at Rails Israel 2015.
Erik Berlin
November 24, 2015
Tweet
Share
More Decks by Erik Berlin
See All by Erik Berlin
Enumerator::Lazy
sferik
1
220
Ruby Trivia 3
sferik
0
370
Ruby Trivia 2
sferik
0
440
Ruby Trivia
sferik
2
960
💀 Symbols
sferik
5
1.3k
Content Negotiation for REST APIs
sferik
8
650
Writing Fast Ruby
sferik
612
57k
Mutation Testing with Mutant
sferik
5
890
Other Decks in Programming
See All in Programming
こそこそアジャイル導入しようぜ!
ichimichi
0
1k
Migrating to Kotlin State & Shared Flows
heyitsmohit
1
180
エンジニアによる事業指標計測のススメ
doyaaaaaken
1
170
ドメインモデル方式のクラス設計 座談会
masuda220
PRO
3
1k
Beyond Micro Frontends: Frontend Moduliths for the Enterprise @wad2022
manfredsteyer
PRO
0
120
シェーダー氷山発掘記
logilabo
0
140
JetPackComposeは宣言型プログラミングパラダイムって実はよくわかってないんですが、別に使ってもいいんですよね、
conigashima
0
170
Custom Design Systems in Compose UI
rharter
5
510
GitHub Actions を導入した経緯
tamago3keran
1
420
Reactive Java Microservices on Kubernetes with Spring and JHipster
deepu105
1
160
Web API連携でCSRF対策がどう実装されてるか調べた / how to implements csrf-detection on Web API
yasuakiomokawa
2
220
I/O Extended 2022 in Android ~ Whats new in Android development tools
pluu
0
510
Featured
See All Featured
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
226
15k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
212
20k
The Pragmatic Product Professional
lauravandoore
19
2.9k
Fantastic passwords and where to find them - at NoRuKo
philnash
27
1.5k
4 Signs Your Business is Dying
shpigford
169
20k
Rails Girls Zürich Keynote
gr2m
86
12k
Designing Experiences People Love
moore
130
22k
Why You Should Never Use an ORM
jnunemaker
PRO
47
7k
Typedesign – Prime Four
hannesfritz
33
1.3k
Design by the Numbers
sachag
271
17k
Fashionably flexible responsive web design (full day workshop)
malarkey
396
62k
Designing for humans not robots
tammielis
241
23k
Transcript
THE VALUE OF BEING LAZY or How I Made OpenStruct
10X Faster Erik Michaels-Ober @sferik
In Ruby, everything is an object. ∀ thing thing.is_a?(Object) #=>
true
In Ruby, every object has a class. ∀ object object.respond_to?(:class)
#=> true
In Ruby, every class has a class. ∴ Object.respond_to?(:class) #=>
true Object.class #=> Class
You can use classes to create new objects: object =
Object.new object.class #=> Object
You can use classes to create new classes: klass =
Class.new klass.class #=> Class
Usually, we create classes like this: class Point attr_accessor :x,
:y def initialize(x, y) @x, @y = x, y end end
You can replace such simple classes with structs: Point =
Struct.new(:x, :y)
OpenStruct requires even less definition: point = OpenStruct.new point.x =
1 point.y = 2
In this way, OpenStruct is similar to Hash: point =
Hash.new point[:x] = 1 point[:y] = 2
You can even initialize OpenStruct with a Hash: point =
OpenStruct.new(x: 1, y: 2) point.x #=> 1 point.y #=> 2
So why use OpenStruct instead of Hash?
Test double validator = OpenStruct.new expect(validator).to receive(:validate) code = PostalCode.new("94102",
validator) code.valid?
API response user = OpenStruct.new(JSON.parse(response)) user.name #=> Erik
Configuration object def options opts = OpenStruct.new yield opts opts
end
So OpenStruct is useful…but slow.
None
Steps to optimize code 1. Complain that code is slow
on Twitter 2. ??? 3. Profit
Actual steps to optimize code 1. Benchmark 2. Read code
3. Profit
Actual steps to optimize code 1. Benchmark 2. Read code
3. Profit
require "benchmark/ips" Point = Struct.new(:x, :y) def struct Point.new(0, 1)
end def ostruct OpenStruct.new(x: 0, y: 1) end Benchmark.ips do |x| x.report("ostruct") { ostruct } x.report("struct") { struct } end
Comparison: struct: 2927800.2 i/s ostruct: 84741.1 i/s - 34.55x slower
Actual steps to optimize code 1. Benchmark 2. Read code
3. Profit
def initialize(hash = nil) @table = {} if hash hash.each_pair
do |k, v| k = k.to_sym @table[k] = v new_ostruct_member(k) end end end
def new_ostruct_member(name) name = name.to_sym unless respond_to?(name) define_singleton_method(name) { @table[name]
} define_singleton_method("#{name}=") { |x| @table[name] = x } end name end
def method_missing(mid, *args) len = args.length if mname = mid[/.*(?==\z)/m]
@table[new_ostruct_member(mname)] = args[0] elsif len == 0 if @table.key?(mid) new_ostruct_member(mid) @table[mid] end end end
def initialize(hash = nil) @table = {} if hash hash.each_pair
do |k, v| k = k.to_sym @table[k] = v new_ostruct_member(k) end end end
Before: struct: 2927800.2 i/s ostruct: 84741.1 i/s - 34.55x slower
After: struct: 2927800.2 i/s ostruct: 940170.4 i/s - 3.11x slower
None
None
git log --reverse lib/ostruct.rb
None
Lazy evaluation
Enumerator::Lazy
lazy_integers = (1..Float::INFINITY).lazy lazy_integers.collect { |x| x ** 2 }.
select { |x| x.even? }. reject { |x| x < 1000 }. first(5) #=> [1024, 1156, 1296, 1444, 1600]
require "prime" lazy_primes = Prime.lazy lazy_primes.select { |x| (x -
2).prime? }. collect { |x| [x - 2, x] }. first(5) #=> [[3, 5], [5, 7], [11, 13], [17, 19], [29, 31]]
module Enumerable def repeat_after_first unless block_given? return to_enum(__method__) { size
* 2 - 1 if size } end each.with_index do |*val, index| index == 0 ? yield *val : 2.times { yield *val } end end end
require "prime" lazy_primes = Prime.lazy lazy_primes.repeat_after_first. each_slice(2). select { |x,
y| x + 2 == y }. first(5) #=> [[3, 5], [5, 7], [11, 13], [17, 19], [29, 31]]
require "date" lazy_dates = (Date.today..Date.new(9999)).lazy lazy_dates.select { |d| d.day ==
13 }. select { |d| d.friday? }. first(10)
lazy_file = File.readlines("/path/to/file").lazy lazy_file.detect { |x| x =~ /regexp/ }
Being lazy is efficient.
Being lazy is elegant.
Thanks to: Zachary Scott ROSS Conf Rails Israel
Thank you