Davy Stevenson
August 12, 2014

# The Science of Success - Cascadia Ruby 2014

Software is approached mainly from the angle of engineering. Let's step back and take a look at software as science. How can we increase the quality of our code, tune our minds to efficiently solve problems, and correctly reapply known solutions to new problems? Learn a few new tricks about how to easily benchmark your code and how to analyze code complexity.

Framed by the story of my own path from would-be Physicist to happy Software Engineer, let's look at using the scientific method as a guide for software development.

August 12, 2014

## Transcript

JD Hancock

42. ### Jarvis March • Find one edge: O(n) • Find right-most

point: O(n) • Repeat for each point on hull: O(h) • O(n + n * h) => O(n h) • Worst case: O(n2)

45. ### 1 2 3 4 5 6 7 8 9 Plejaden

M45 by Carsten Frenzi
46. ### 1 2 3 4 5 6 7 8 9 Plejaden

M45 by Carsten Frenzi

Frenzi

64. ### Monotone Chain • Sort points: O(n log n) • Construct

lower hull: O(n) • Construct upper hull: O(n) • O(n log n + (n + n)) => O(n log n) • O(n) if already sorted

68. ### require 'benchmark' n = 100_000 size = 10000 array =

(0...size).to_a.shuffle ! Benchmark.bm do |x| x.report("#at") { n.times { array.at rand(size)} } x.report("#index") { n.times { array.index rand(size) } } x.report("#index-miss") { n.times { array.index (size + rand(size)) } } end
69. ### require 'benchmark' n = 100_000 size = 10000 array =

(0...size).to_a.shuffle ! Benchmark.bm do |x| x.report("#at") { n.times { array.at rand(size)} } x.report("#index") { n.times { array.index rand(size) } } x.report("#index-miss") { n.times { array.index (size + rand(size)) } } end
70. ### require 'benchmark' n = 100_000 size = 10000 array =

(0...size).to_a.shuffle ! Benchmark.bm do |x| x.report("#at") { n.times { array.at rand(size)} } x.report("#index") { n.times { array.index rand(size) } } x.report("#index-miss") { n.times { array.index (size + rand(size)) } } end
71. ### require 'benchmark' n = 100_000 size = 10000 array =

(0...size).to_a.shuffle ! Benchmark.bm do |x| x.report("#at") { n.times { array.at rand(size)} } x.report("#index") { n.times { array.index rand(size) } } x.report("#index-miss") { n.times { array.index (size + rand(size)) } } end
72. ### require 'benchmark' n = 100_000 size = 10000 array =

(0...size).to_a.shuffle ! Benchmark.bm do |x| x.report("#at") { n.times { array.at rand(size)} } x.report("#index") { n.times { array.index rand(size) } } x.report("#index-miss") { n.times { array.index (size + rand(size)) } } end
73. ### \$ ruby examples/bm.rb user system total real #at 0.020000 0.000000

0.020000 ( 0.023444) #index 2.380000 0.000000 2.380000 ( 2.391208) #index-miss 4.770000 0.000000 4.770000 ( 4.786147)
74. ### \$ ruby examples/bm.rb user system total real #at 0.020000 0.000000

0.020000 ( 0.023444) #index 2.380000 0.000000 2.380000 ( 2.391208) #index-miss 4.770000 0.000000 4.770000 ( 4.786147)
75. ### require 'benchmark' n = 100_000 size = 10000 array =

(0...size).to_a.shuffle ! Benchmark.bm do |x| x.report("#at") { n.times { array.at rand(size)} } x.report("#index") { n.times { array.index rand(size) } } x.report("#index-miss") { n.times { array.index (size + rand(size)) } } end
76. ### require 'benchmark/ips' ! size = 10000 array = (0...size).to_a.shuffle !

Benchmark.ips do |x| x.report("#at") { array.at rand(size) } x.report("#index") { array.index rand(size) } x.report("#index-miss") { array.index (size + rand(size)) } x.compare! end
77. ### require 'benchmark/ips' ! size = 10000 array = (0...size).to_a.shuffle !

Benchmark.ips do |x| x.report("#at") { array.at rand(size) } x.report("#index") { array.index rand(size) } x.report("#index-miss") { array.index (size + rand(size)) } x.compare! end
78. ### \$ ruby examples/ips.rb ! ! ! Calculating ------------------------------------- #at 79155

i/100ms #index 4039 i/100ms #index-miss 2055 i/100ms ------------------------------------------------- #at 3165995.2 (±10.7%) i/s - 15593535 in 5.006746s #index 41873.8 (±6.3%) i/s - 210028 in 5.040799s #index-miss 20592.7 (±7.5%) i/s - 102750 in 5.022168s ! Comparison: #at: 3165995.2 i/s #index: 41873.8 i/s - 75.61x slower #index-miss: 20592.7 i/s - 153.74x slower
79. ### \$ ruby examples/ips.rb ! ! ! Calculating ------------------------------------- #at 79155

i/100ms #index 4039 i/100ms #index-miss 2055 i/100ms ------------------------------------------------- #at 3165995.2 (±10.7%) i/s - 15593535 in 5.006746s #index 41873.8 (±6.3%) i/s - 210028 in 5.040799s #index-miss 20592.7 (±7.5%) i/s - 102750 in 5.022168s ! Comparison: #at: 3165995.2 i/s #index: 41873.8 i/s - 75.61x slower #index-miss: 20592.7 i/s - 153.74x slower
80. ### \$ ruby examples/ips.rb ! ! ! Calculating ------------------------------------- #at 79155

i/100ms #index 4039 i/100ms #index-miss 2055 i/100ms ------------------------------------------------- #at 3165995.2 (±10.7%) i/s - 15593535 in 5.006746s #index 41873.8 (±6.3%) i/s - 210028 in 5.040799s #index-miss 20592.7 (±7.5%) i/s - 102750 in 5.022168s ! Comparison: #at: 3165995.2 i/s #index: 41873.8 i/s - 75.61x slower #index-miss: 20592.7 i/s - 153.74x slower
81. ### \$ ruby examples/ips.rb ! ! ! Calculating ------------------------------------- #at 79155

i/100ms #index 4039 i/100ms #index-miss 2055 i/100ms ------------------------------------------------- #at 3165995.2 (±10.7%) i/s - 15593535 in 5.006746s #index 41873.8 (±6.3%) i/s - 210028 in 5.040799s #index-miss 20592.7 (±7.5%) i/s - 102750 in 5.022168s ! Comparison: #at: 3165995.2 i/s #index: 41873.8 i/s - 75.61x slower #index-miss: 20592.7 i/s - 153.74x slower
82. ### \$ ruby examples/ips.rb ! ! ! Calculating ------------------------------------- #at 79155

i/100ms #index 4039 i/100ms #index-miss 2055 i/100ms ------------------------------------------------- #at 3165995.2 (±10.7%) i/s - 15593535 in 5.006746s #index 41873.8 (±6.3%) i/s - 210028 in 5.040799s #index-miss 20592.7 (±7.5%) i/s - 102750 in 5.022168s ! Comparison: #at: 3165995.2 i/s #index: 41873.8 i/s - 75.61x slower #index-miss: 20592.7 i/s - 153.74x slower
83. ### \$ ruby examples/ips.rb ! ! ! Calculating ------------------------------------- #at 79155

i/100ms #index 4039 i/100ms #index-miss 2055 i/100ms ------------------------------------------------- #at 3165995.2 (±10.7%) i/s - 15593535 in 5.006746s #index 41873.8 (±6.3%) i/s - 210028 in 5.040799s #index-miss 20592.7 (±7.5%) i/s - 102750 in 5.022168s ! Comparison: #at: 3165995.2 i/s #index: 41873.8 i/s - 75.61x slower #index-miss: 20592.7 i/s - 153.74x slower
84. None

86. ### Benchmark Big O • github.com/davy/benchmark-bigo • Provides Big O notation

benchmarking for Ruby
87. ### require 'benchmark/ips' ! size = 10000 array = (0...size).to_a.shuffle !

Benchmark.ips do |x| x.report("#at") { array.at rand(size) } x.report("#index") { array.index rand(size) } x.report("#index-miss") { array.index (size + rand(size)) } x.compare! end
88. ### require 'benchmark/bigo' ! Benchmark.bigo do |x| x.generate :array x.linear 2000

! x.report("#at") {|array, size| array.at rand(size) } x.report("#index") {|array, size| array.index rand(size) } x.report("#index-miss") {|array, size| array.index (size + rand(size)) } x.chart! 'array.html' end
89. ### require 'benchmark/bigo' ! Benchmark.bigo do |x| x.generate :array x.linear 2000

! x.report("#at") {|array, size| array.at rand(size) } x.report("#index") {|array, size| array.index rand(size) } x.report("#index-miss") {|array, size| array.index (size + rand(size)) } x.chart! 'array.html' end
90. ### require 'benchmark/bigo' ! Benchmark.bigo do |x| x.generate :array x.exponential !

x.report("#at") {|array, size| array.at rand(size) } x.report("#index") {|array, size| array.index rand(size) } x.report("#index-miss") {|array, size| array.index (size + rand(size)) } x.chart! 'array.html' end
91. ### require 'benchmark/bigo' ! Benchmark.bigo do |x| x.generate :array x.exponential !

x.report("#at") {|array, size| array.at rand(size) } x.report("#index") {|array, size| array.index rand(size) } x.report("#index-miss") {|array, size| array.index (size + rand(size)) } x.chart! 'array.html' end
92. ### require 'benchmark/bigo' ! Benchmark.bigo do |x| x.generate :array x.exponential !

x.report("#at") {|array, size| array.at rand(size) } x.report("#index") {|array, size| array.index rand(size) } x.report("#index-miss") {|array, size| array.index (size + rand(size)) } x.chart! 'array.html' end
93. ### \$ ruby examples/bigo-exp.rb ! Calculating ------------------------------------- #at 1 68448 i/100ms

#at 10 63310 i/100ms #at 100 67388 i/100ms #at 1000 65912 i/100ms #at 10000 65518 i/100ms #index 1 66714 i/100ms #index 10 63736 i/100ms #index 100 50055 i/100ms #index 1000 18838 i/100ms #index 10000 3752 i/100ms #index-miss 1 61960 i/100ms #index-miss 10 58333 i/100ms #index-miss 100 45989 i/100ms #index-miss 1000 14698 i/100ms #index-miss 10000 1898 i/100ms ------------------------------------------------- #at 1 2084640.3 (±9.5%) i/s - 10335648 in 5.029076s #at 10 2006228.2 (±8.3%) i/s - 9939670 in 5.003949s #at 100 1871633.1 (±18.1%) i/s - 8962604 in 5.025712s #at 1000 2055675.6 (±8.7%) i/s - 10216360 in 5.029822s #at 10000 2000566.4 (±8.6%) i/s - 9893218 in 4.999732s #index 1 1992529.4 (±8.2%) i/s - 9940386 in 5.035758s #index 10 1810344.6 (±8.3%) i/s - 8986776 in 5.012351s #index 100 1304666.0 (±9.2%) i/s - 6457095 in 5.010804s #index 1000 347986.2 (±6.0%) i/s - 1733096 in 5.002854s #index 10000 40728.2 (±10.7%) i/s - 202608 in 5.059052s #index-miss 1 1984727.2 (±9.4%) i/s - 9851640 in 5.028868s #index-miss 10 1762957.2 (±7.6%) i/s - 8749950 in 5.004258s #index-miss 100 986260.8 (±10.2%) i/s - 4874834 in 5.010465s #index-miss 1000 187159.1 (±7.9%) i/s - 940672 in 5.063904s #index-miss 10000 19330.9 (±12.8%) i/s - 94900 in 5.001322s
94. None

97. ### Terraformer Ruby • github.com/esripdx/terraformer-ruby • A geometric toolkit for dealing

with geometry, geography, formats • Can calculate Convex Hull

100. None
101. None
102. ### module Terraformer::Benchmark # portland LON = -122.6764 LAT = 45.5165

! # calculate convex hull with given implementation def self.convex_hull obj, impl Terraformer::ConvexHull.impl = impl obj.convex_hull end end ! class Float def perm self + rand(-100..100) / 10000.0 end end
103. ### module Terraformer::Benchmark # portland LON = -122.6764 LAT = 45.5165

! # calculate convex hull with given implementation def self.convex_hull obj, impl Terraformer::ConvexHull.impl = impl obj.convex_hull end end ! class Float def perm self + rand(-100..100) / 10000.0 end end
104. ### module Terraformer::Benchmark # portland LON = -122.6764 LAT = 45.5165

! # calculate convex hull with given implementation def self.convex_hull obj, impl Terraformer::ConvexHull.impl = impl obj.convex_hull end end ! class Float def perm self + rand(-100..100) / 10000.0 end end
105. ### module Terraformer::Benchmark ! # generates a Line String representing a

random walk def self.random_walk size walk = [[LON, LAT]] # start in pdx ! size.times do walk << walk.last.map{|i| i.perm } end ! # create line string from the random walk ls = Terraformer::LineString.new(walk) ! # convert to feature ls.to_feature end end
106. ### module Terraformer::Benchmark ! # generates a Line String representing a

random walk def self.random_walk size walk = [[LON, LAT]] # start in pdx ! size.times do walk << walk.last.map{|i| i.perm } end ! # create line string from the random walk ls = Terraformer::LineString.new(walk) ! # convert to feature ls.to_feature end end
107. None
108. ### Benchmark.bigo do |x| x.generator {|size| Terraformer::Benchmark.random_walk size } ! x.time

= 20 # sample each point for 20 seconds x.linear 200 x.increments = 5 # 200..1000 ! x.report("#rand-jarvis") {|f, _| Terraformer::Benchmark.convex_hull f, :jarvis_march } ! x.report("#rand-monotone") {|f, _| Terraformer::Benchmark.convex_hull f, :monotone} ! x.chart! 'data/chart_random_walk.html' x.compare! end
109. ### Benchmark.bigo do |x| x.generator {|size| Terraformer::Benchmark.random_walk size } ! x.time

= 20 # sample each point for 20 seconds x.linear 200 x.increments = 5 # 200..1000 ! x.report("#rand-jarvis") {|f, _| Terraformer::Benchmark.convex_hull f, :jarvis_march } ! x.report("#rand-monotone") {|f, _| Terraformer::Benchmark.convex_hull f, :monotone} ! x.chart! 'data/chart_random_walk.html' x.compare! end
110. ### Benchmark.bigo do |x| x.generator {|size| Terraformer::Benchmark.random_walk size } ! x.time

= 20 # sample each point for 20 seconds x.linear 200 x.increments = 5 # 200..1000 ! x.report("#rand-jarvis") {|f, _| Terraformer::Benchmark.convex_hull f, :jarvis_march } ! x.report("#rand-monotone") {|f, _| Terraformer::Benchmark.convex_hull f, :monotone} ! x.chart! 'data/chart_random_walk.html' x.compare! end
111. None
112. None
113. None

116. ### module Terraformer::Benchmark ! # generates a circle feature with size

points def self.circle size ! # minimum size is three size = 3 if size < 3 ! # expand the diameter as size increases diam = [100, size].max ! # generate circle c = Terraformer::Circle.new([LON, LAT], diam, size) ! # convert to feature c.to_feature end end
117. ### module Terraformer::Benchmark ! # generates a circle feature with size

points def self.circle size ! # minimum size is three size = 3 if size < 3 ! # expand the diameter as size increases diam = [100, size].max ! # generate circle c = Terraformer::Circle.new([LON, LAT], diam, size) ! # convert to feature c.to_feature end end
118. None
119. ### Benchmark.bigo do |x| x.generator {|size| Terraformer::Benchmark.circle size } ! x.time

= 20 # sample each point for 20 seconds x.linear 200 x.increments = 5 # 200..1000 ! x.report("#circ-jarvis") {|f, _| Terraformer::Benchmark.convex_hull f, :jarvis_march } ! x.report("#circ-monotone") {|f, _| Terraformer::Benchmark.convex_hull f, :monotone} ! x.chart! 'data/chart_circle.html' x.compare! end
120. None
121. None
122. None
123. None
124. None
125. ### Benchmark Terraformer • github.com/davy/benchmark-terraformer • Helper code • Benchmark code

• Map Viewer • Example charts, geojson & raw data • Array benchmark code

131. None

155. ### Resources Benchmark IPS - github.com/evanphx/benchmark-ips Benchmark BigO - github.com/davy/benchmark-bigo Terraformer.rb

- github.com/esripdx/terraformer-ruby ! Benchmark Terraformer Results- github.com/davy/ benchmark-terraformer ! Maps by Leaﬂet - github.com/Leaﬂet/Leaﬂet extended with Esri Leaﬂet - github.com/Esri/esri-leaﬂet and Leaﬂet Ajax - github.com/calvinmetcalf/leaﬂet-ajax
156. ### Attributions From the Noun Project: ! “Erlenmeyer Flask” by Emily

van den Heever “Stopwatch” by Irit Barzily “Diamond” by Ryan Beck “Question” by Brennan Novak “Check Mark” by Brennan Novak “Arrow” by José Campos
157. ### Attributions Backgrounds: Clouds by Joshua Rappeneker First Stars by Paul

Chaloner grassy by cometwendy Drops on Bright Orange Flower by A Guy Taking Pictures Lightning by Stuart Williams Beach Palm Trees by Grand Velas Riviera Maya ! !