Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
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
710
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
520
Ruby Trivia 3
sferik
0
660
Ruby Trivia 2
sferik
0
700
Ruby Trivia
sferik
2
1.2k
💀 Symbols
sferik
5
1.8k
Content Negotiation for REST APIs
sferik
8
940
Writing Fast Ruby
sferik
628
61k
Mutation Testing with Mutant
sferik
5
1.1k
Other Decks in Programming
See All in Programming
HTML/CSS超絶浅い説明
yuki0329
0
190
PHPで学ぶプログラミングの教訓 / Lessons in Programming Learned through PHP
nrslib
4
1.1k
chibiccをCILに移植した結果 (NGK2025S版)
kekyo
PRO
0
140
AWS re:Invent 2024個人的まとめ
satoshi256kbyte
0
110
ChatGPT とつくる PHP で OS 実装
memory1994
PRO
3
190
最近のVS Codeで気になるニュース 2025/01
74th
1
110
カンファレンス動画鑑賞会のススメ / Osaka.swift #1
hironytic
0
180
“あなた” の開発を支援する AI エージェント Bedrock Engineer / introducing-bedrock-engineer
gawa
4
430
Alba: Why, How and What's So Interesting
okuramasafumi
0
210
AppRouterを用いた大規模サービス開発におけるディレクトリ構成の変遷と問題点
eiganken
1
450
見えないメモリを観測する: PHP 8.4 `pg_result_memory_size()` とSQL結果のメモリ管理
kentaroutakeda
0
940
知られざるDMMデータエンジニアの生態 〜かつてツチノコと呼ばれし者〜
takaha4k
1
460
Featured
See All Featured
Unsuck your backbone
ammeep
669
57k
Building a Scalable Design System with Sketch
lauravandoore
460
33k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
113
50k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
47
5.1k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
Building Adaptive Systems
keathley
38
2.4k
Designing for Performance
lara
604
68k
How to train your dragon (web standard)
notwaldorf
89
5.8k
The Power of CSS Pseudo Elements
geoffreycrofte
74
5.4k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
10
870
Fantastic passwords and where to find them - at NoRuKo
philnash
50
2.9k
Making the Leap to Tech Lead
cromwellryan
133
9k
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