Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
The Value of Being Lazy
Search
Erik Berlin
November 24, 2015
Programming
3
820
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
2
600
Ruby Trivia 3
sferik
0
730
Ruby Trivia 2
sferik
0
790
Ruby Trivia
sferik
2
1.3k
💀 Symbols
sferik
5
1.9k
Content Negotiation for REST APIs
sferik
8
1k
Writing Fast Ruby
sferik
630
62k
Mutation Testing with Mutant
sferik
5
1.1k
Other Decks in Programming
See All in Programming
非同期処理の迷宮を抜ける: 初学者がつまづく構造的な原因
pd1xx
1
680
251126 TestState APIってなんだっけ?Step Functionsテストどう変わる?
east_takumi
0
310
AIエンジニアリングのご紹介 / Introduction to AI Engineering
rkaga
5
1.9k
C-Shared Buildで突破するAI Agent バックテストの壁
po3rin
0
370
リリース時」テストから「デイリー実行」へ!開発マネージャが取り組んだ、レガシー自動テストのモダン化戦略
goataka
0
120
分散DBって何者なんだ... Spannerから学ぶRDBとの違い
iwashi623
0
180
AIコードレビューがチームの"文脈"を 読めるようになるまで
marutaku
0
340
WebRTC と Rust と8K 60fps
tnoho
2
1.9k
CloudNative Days Winter 2025: 一週間で作る低レイヤコンテナランタイム
ternbusty
7
2k
Tinkerbellから学ぶ、Podで DHCPをリッスンする手法
tomokon
0
110
AIと協働し、イベントソーシングとアクターモデルで作る後悔しないアーキテクチャ Regret-Free Architecture with AI, Event Sourcing, and Actors
tomohisa
5
19k
DSPy Meetup Tokyo #1 - はじめてのDSPy
masahiro_nishimi
1
160
Featured
See All Featured
Typedesign – Prime Four
hannesfritz
42
2.9k
The Illustrated Children's Guide to Kubernetes
chrisshort
51
51k
Imperfection Machines: The Place of Print at Facebook
scottboms
269
13k
For a Future-Friendly Web
brad_frost
180
10k
Code Review Best Practice
trishagee
73
19k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
1.6k
Designing Experiences People Love
moore
143
24k
Why You Should Never Use an ORM
jnunemaker
PRO
60
9.6k
Art, The Web, and Tiny UX
lynnandtonic
303
21k
The Power of CSS Pseudo Elements
geoffreycrofte
80
6.1k
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.4k
Stop Working from a Prison Cell
hatefulcrawdad
273
21k
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