Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)

Tobias Pfeiffer
Tobias PfeifferSoftware Engineer (Student) at Devolute
The other day
iex(1)>
iex(1)> defmodule RepeatN do
...(1)> def repeat_n(_function, 0) do
...(1)> # noop
...(1)> end
...(1)> def repeat_n(function, 1) do
...(1)> function.()
...(1)> end
...(1)> def repeat_n(function, count) do
...(1)> function.()
...(1)> repeat_n(function, count - 1)
...(1)> end
...(1)> end
{:module, RepeatN, ...}
iex(1)> defmodule RepeatN do
...(1)> def repeat_n(_function, 0) do
...(1)> # noop
...(1)> end
...(1)> def repeat_n(function, 1) do
...(1)> function.()
...(1)> end
...(1)> def repeat_n(function, count) do
...(1)> function.()
...(1)> repeat_n(function, count - 1)
...(1)> end
...(1)> end
{:module, RepeatN, ...}
iex(2)> :timer.tc fn -> RepeatN.repeat_n(fn -> 0 end, 100) end
{210, 0}
iex(1)> defmodule RepeatN do
...(1)> def repeat_n(_function, 0) do
...(1)> # noop
...(1)> end
...(1)> def repeat_n(function, 1) do
...(1)> function.()
...(1)> end
...(1)> def repeat_n(function, count) do
...(1)> function.()
...(1)> repeat_n(function, count - 1)
...(1)> end
...(1)> end
{:module, RepeatN, ...}
iex(2)> :timer.tc fn -> RepeatN.repeat_n(fn -> 0 end, 100) end
{210, 0}
iex(3)> list = Enum.to_list(1..100)
[...]
iex(4)> :timer.tc fn -> Enum.each(list, fn(_) -> 0 end) end
{165, :ok}
iex(1)> defmodule RepeatN do
...(1)> def repeat_n(_function, 0) do
...(1)> # noop
...(1)> end
...(1)> def repeat_n(function, 1) do
...(1)> function.()
...(1)> end
...(1)> def repeat_n(function, count) do
...(1)> function.()
...(1)> repeat_n(function, count - 1)
...(1)> end
...(1)> end
{:module, RepeatN, ...}
iex(2)> :timer.tc fn -> RepeatN.repeat_n(fn -> 0 end, 100) end
{210, 0}
iex(3)> list = Enum.to_list(1..100)
[...]
iex(4)> :timer.tc fn -> Enum.each(list, fn(_) -> 0 end) end
{165, :ok}
iex(5)> :timer.tc fn -> Enum.each(list, fn(_) -> 0 end) end
{170, :ok}
iex(6)> :timer.tc fn -> Enum.each(list, fn(_) -> 0 end) end
{184, :ok}
Success!
The End?
Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)
● Way too few samples
● Realistic data/multiple inputs?
● No warmup
● Non production environment
● Does creating the list matter?
● Is repeating really the bottle neck?
● Repeatability?
● Setup information
● Running on battery
● Lots of applications running
Numerous Failures!
n = 10_000
range = 1..n
list = Enum.to_list range
fun = fn -> 0 end
Benchee.run %{
"Enum.each" =>
fn -> Enum.each(list, fn(_) -> fun.() end) end,
"List comprehension" =>
fn -> for _ <- list, do: fun.() end,
"Recursion" =>
fn -> RepeatN.repeat_n(fun, n) end
}
Name ips average deviation median 99th %
Recursion 6.95 K 143.80 μs ±2.76% 142 μs 156 μs
Enum.each 4.21 K 237.70 μs ±6.47% 230 μs 278 μs
List comprehension 3.07 K 325.88 μs ±10.02% 333 μs 373 μs
Comparison:
Recursion 6.95 K
Enum.each 4.21 K - 1.65x slower
List comprehension 3.07 K - 2.27x slower
Name ips average deviation median 99th %
Recursion 6.95 K 143.80 μs ±2.76% 142 μs 156 μs
Enum.each 4.21 K 237.70 μs ±6.47% 230 μs 278 μs
List comprehension 3.07 K 325.88 μs ±10.02% 333 μs 373 μs
Comparison:
Recursion 6.95 K
Enum.each 4.21 K - 1.65x slower
List comprehension 3.07 K - 2.27x slower
Iterations per Second
Stop guessing and Start Measuring
Benchmarking in Practice
Tobias Pfeiffer
@PragTob
pragtob.info
github.com/PragTob/benchee
Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)
Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)
Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)
Concept vs Tool Usage
What’s Benchmarking?
Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)
Profiling
vs
Benchmarking
Flame Graph
Elixir.Life.Board:map/2
El.. Elixir.Enum:-map/2-lc$^0/1-0-/2El.. El..El..
El.. Elixir.Life.Board:map/2Elixir.Life.Board:map/2 El..
El..
Elixir.Life.Board:map/2
Elixir.Enum:-map/2-lc$^0/1-0-/2
El..
El..Elixir.Enum:-map/2-lc$^0/1-0-/2Elixir.Enum:-map/2-lc$^0/1-0-/2
(0.47.0)
Eli..
eflame:apply1/3
Elixir.Life:run_loop/3
Elixir.Life.Board:map/2
Elixir.Enum:-map/2-lc$^0/1-0-/2
El..El.. El..
http://learningelixir.joekain.com/profiling-elixir-2/
What to benchmark?
What to measure?
Runtime?
Memory?
Throughput?
Custom?
The famous post
What to measure?
Runtime!
Memory?
Throughput?
Custom?
How long will this take?
Enum.sort/1 performance
Enum.sort/1 performance
Did we make it faster?
What’s fastest?
What's the fastest way to
sort a list of numbers
largest to smallest?
list = 1..10_000 |> Enum.to_list |> Enum.shuffle
Benchee.run %{
"sort(fun)" =>
fn -> Enum.sort(list, &(&1 > &2)) end,
"sort |> reverse" =>
fn -> list |> Enum.sort |> Enum.reverse end,
"sort_by(-value)" =>
fn -> Enum.sort_by(list, fn(val) -> -val end) end
}
list = 1..10_000 |> Enum.to_list |> Enum.shuffle
Benchee.run %{
"sort(fun)" =>
fn -> Enum.sort(list, &(&1 > &2)) end,
"sort |> reverse" =>
fn -> list |> Enum.sort |> Enum.reverse end,
"sort_by(-value)" =>
fn -> Enum.sort_by(list, fn(val) -> -val end) end
}
list = 1..10_000 |> Enum.to_list |> Enum.shuffle
Benchee.run %{
"sort(fun)" =>
fn -> Enum.sort(list, &(&1 > &2)) end,
"sort |> reverse" =>
fn -> list |> Enum.sort |> Enum.reverse end,
"sort_by(-value)" =>
fn -> Enum.sort_by(list, fn(val) -> -val end) end
}
list = 1..10_000 |> Enum.to_list |> Enum.shuffle
Benchee.run %{
"sort(fun)" =>
fn -> Enum.sort(list, &(&1 > &2)) end,
"sort |> reverse" =>
fn -> list |> Enum.sort |> Enum.reverse end,
"sort_by(-value)" =>
fn -> Enum.sort_by(list, fn(val) -> -val end) end
}
Name ips average deviation median 99th %
sort |> reverse 576.52 1.74 ms ±7.11% 1.70 ms 2.25 ms
sort(fun) 241.21 4.15 ms ±3.04% 4.15 ms 4.60 ms
sort_by(-value) 149.96 6.67 ms ±6.06% 6.50 ms 7.83 ms
Comparison:
sort |> reverse 576.52
sort(fun) 241.21 - 2.39x slower
sort_by(-value) 149.96 - 3.84x slower
“Isn’t that the
root of all evil?”
“More likely, not reading
the sources is the source
of all evil.”
Me, just now
“We should forget about small efficiencies, say
about 97% of the time: premature optimization
is the root of all evil.”
Donald Knuth, 1974
(Computing Surveys, Vol 6, No 4, December 1974)
Yup it’s there
“Yet we should not pass up our opportunities in
that critical 3%.”
Donald Knuth, 1974
(Computing Surveys, Vol 6, No 4, December 1974)
The very next sentence
“(...) a 12 % improvement, easily obtained, is
never considered marginal”
Donald Knuth, 1974
(Computing Surveys, Vol 6, No 4, December 1974)
Prior Paragraph
Different types of
benchmarks
Feature
Integration
Unit
Testing Pyramid
Application
Macro
Micro
Benchmarking Pyramid
Micro Macro Application
Micro Macro
Components involved
Application
Micro Macro
Setup Complexity
Components involved
Application
Micro Macro
Setup Complexity
Execution Time
Components involved
Application
Micro Macro
Setup Complexity
Execution Time
Confidence of Real Impact
Components involved
Application
Micro Macro
Setup Complexity
Execution Time
Confidence of Real Impact
Components involved
Chance of Interference
Application
Micro Macro
Setup Complexity
Execution Time
Confidence of Real Impact
Components involved
Chance of Interference
Golden Middle
Application
Good Benchmarking
What are you
benchmarking for?
● Elixir 1.6.1
● Erlang 20.2
● i5-7200U – 2 x 2.5GHz (Up to 3.10GHz)
● 8GB RAM
● Linux Mint 18.3 - 64 bit (Ubuntu 16.04 base)
System Specification
System Specification
tobi@comfy elixir_playground $ mix run bench/something.exs
Operating System: Linux
CPU Information: Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz
Number of Available Cores: 4
Available memory: 7.67 GB
Elixir 1.6.1
Erlang 20.2
Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
parallel: 1
inputs: none specified
Estimated total run time: 21 s
Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)
Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)
1.4!
1.3
Correct & Meaningful Setup
Interference free Environment
[info] GET /
[debug] Processing by Rumbl.PageController.index/2
Parameters: %{}
Pipelines: [:browser]
[info] Sent 200 in 46ms
[info] GET /sessions/new
[debug] Processing by Rumbl.SessionController.new/2
Parameters: %{}
Pipelines: [:browser]
[info] Sent 200 in 5ms
[info] GET /users/new
[debug] Processing by Rumbl.UserController.new/2
Parameters: %{}
Pipelines: [:browser]
[info] Sent 200 in 7ms
[info] POST /users
[debug] Processing by Rumbl.UserController.create/2
Parameters: %{"_csrf_token" => "NUEUdRMNAiBfIHEeNwZkfA05PgAOJgAAf0ACXJqCjl7YojW+trdjdg==", "_utf8" => " ", "user"✓
=> %{"name" => "asdasd", "password" => "[FILTERED]", "username" => "Homer"}}
Pipelines: [:browser]
[debug] QUERY OK db=0.1ms
begin []
[debug] QUERY OK db=0.9ms
INSERT INTO "users" ("name","password_hash","username","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING
"id" ["asdasd", "$2b$12$.qY/kpo0Dec7vMK1ClJoC.Lw77c3oGllX7uieZILMlFh2hFpJ3F.C", "Homer", {{2016, 12, 2}, {14, 10, 28, 0}},
{{2016, 12, 2}, {14, 10, 28, 0}}]
Logging & Friends
Garbage Collection
Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)
Warmup
$ time my_program
Probably not what you want
What’s important for you?
Startup
What’s important for you?
Startup Warmup
What’s important for you?
Startup Warmup Runtime
What’s important for you?
28s → 2s
Inputs matter!
Story Time
Boom
Boom
Elixir.DBConnection.ConnectionError
Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)
Benchee.run %{
"DB View" => fn ->
LatestCourierLocation
|> CourierLocation.with_courier_ids(courier_id)
|> Repo.one(timeout: :infinity)
end,
"with_courier_ids" => fn ->
CourierLocation.with_courier_ids(courier_id)
|> Ecto.Query.order_by(desc: :time)
|> Ecto.Query.limit(1)
|> Repo.one(timeout: :infinity)
end,
"full custom" => fn ->
CourierLocation
|> Ecto.Query.where(courier_id: ^courier_id)
|> Ecto.Query.order_by(desc: :time)
|> Ecto.Query.limit(1)
|> Repo.one(timeout: :infinity)
end
}
Benchee.run %{
"DB View" => fn ->
LatestCourierLocation
|> CourierLocation.with_courier_ids(courier_id)
|> Repo.one(timeout: :infinity)
end,
"with_courier_ids" => fn ->
CourierLocation.with_courier_ids(courier_id)
|> Ecto.Query.order_by(desc: :time)
|> Ecto.Query.limit(1)
|> Repo.one(timeout: :infinity)
end,
"full custom" => fn ->
CourierLocation
|> Ecto.Query.where(courier_id: ^courier_id)
|> Ecto.Query.order_by(desc: :time)
|> Ecto.Query.limit(1)
|> Repo.one(timeout: :infinity)
end
}
Database View
Benchee.run %{
"DB View" => fn ->
LatestCourierLocation
|> CourierLocation.with_courier_ids(courier_id)
|> Repo.one(timeout: :infinity)
end,
"with_courier_ids" => fn ->
CourierLocation.with_courier_ids(courier_id)
|> Ecto.Query.order_by(desc: :time)
|> Ecto.Query.limit(1)
|> Repo.one(timeout: :infinity)
end,
"full custom" => fn ->
CourierLocation
|> Ecto.Query.where(courier_id: ^courier_id)
|> Ecto.Query.order_by(desc: :time)
|> Ecto.Query.limit(1)
|> Repo.one(timeout: :infinity)
end
}
Only difference
Benchee.run %{
"DB View" => fn ->
LatestCourierLocation
|> CourierLocation.with_courier_ids(courier_id)
|> Repo.one(timeout: :infinity)
end,
"with_courier_ids" => fn ->
CourierLocation.with_courier_ids(courier_id)
|> Ecto.Query.order_by(desc: :time)
|> Ecto.Query.limit(1)
|> Repo.one(timeout: :infinity)
end,
"full custom" => fn ->
CourierLocation
|> Ecto.Query.where(courier_id: ^courier_id)
|> Ecto.Query.order_by(desc: :time)
|> Ecto.Query.limit(1)
|> Repo.one(timeout: :infinity)
end
}
Same
Name ips average deviation median 99th %
with_courier_ids 983.21 1.02 ms ±43.22% 0.91 ms 3.19 ms
full custom 855.07 1.17 ms ±53.86% 0.96 ms 4.25 ms
DB View 0.21 4704.70 ms ±4.89% 4738.83 ms 4964.49 ms
Comparison:
with_courier_ids 983.21
full custom 855.07 - 1.15x slower
DB View 0.21 - 4625.70x slower
Another job well done?
Another job well done?
Boom
Boom
Elixir.DBConnection.ConnectionError
Boom
Elixir.DBConnection.ConnectionError
Elixir.DBConnection.ConnectionError
Boom
Elixir.DBConnection.ConnectionError
Elixir.DBConnection.ConnectionError
Elixir.DBConnection.ConnectionError
Elixir.DBConnection.ConnectionError
Elixir.DBConnection.ConnectionError
Elixir.DBConnection.ConnectionError
Elixir.DBConnection.ConnectionError
Elixir.DBConnection.ConnectionError
Whaaaat?
inputs = %{
"Big 2.3 Million locations" => 3799,
"No locations" => 8901,
"~200k locations" => 4238,
"~20k locations" => 4201
}
Benchee.run %{
...
"full custom" => fn(courier_id) ->
CourierLocation
|> Ecto.Query.where(courier_id: ^courier_id)
|> Ecto.Query.order_by(desc: :time)
|> Ecto.Query.limit(1)
|> Repo.one(timeout: :infinity)
end
}, inputs: inputs, time: 25, warmup: 5
Inputs to the rescue!
inputs = %{
"Big 2.3 Million locations" => 3799,
"No locations" => 8901,
"~200k locations" => 4238,
"~20k locations" => 4201
}
Benchee.run %{
...
"full custom" => fn(courier_id) ->
CourierLocation
|> Ecto.Query.where(courier_id: ^courier_id)
|> Ecto.Query.order_by(desc: :time)
|> Ecto.Query.limit(1)
|> Repo.one(timeout: :infinity)
end
}, inputs: inputs, time: 25, warmup: 5
Inputs to the rescue!
inputs = %{
"Big 2.3 Million locations" => 3799,
"No locations" => 8901,
"~200k locations" => 4238,
"~20k locations" => 4201
}
Benchee.run %{
...
"full custom" => fn(courier_id) ->
CourierLocation
|> Ecto.Query.where(courier_id: ^courier_id)
|> Ecto.Query.order_by(desc: :time)
|> Ecto.Query.limit(1)
|> Repo.one(timeout: :infinity)
end
}, inputs: inputs, time: 25, warmup: 5
Inputs to the rescue!
inputs = %{
"Big 2.3 Million locations" => 3799,
"No locations" => 8901,
"~200k locations" => 4238,
"~20k locations" => 4201
}
Benchee.run %{
...
"full custom" => fn(courier_id) ->
CourierLocation
|> Ecto.Query.where(courier_id: ^courier_id)
|> Ecto.Query.order_by(desc: :time)
|> Ecto.Query.limit(1)
|> Repo.one(timeout: :infinity)
end
}, inputs: inputs, time: 25, warmup: 5
Inputs to the rescue!
##### With input Big 2.5 million locations #####
with_courier_ids 937.18
full custom 843.24 - 1.11x slower
DB View 0.22 - 4261.57x slower
##### With input ~200k locations #####
DB View 3.57
with_courier_ids 0.109 - 32.84x slower
full custom 0.0978 - 36.53x slower
##### With input ~20k locations #####
DB View 31.73
with_courier_ids 0.104 - 305.37x slower
full custom 0.0897 - 353.61x slower
##### With input No locations #####
Comparison:
DB View 1885.48
with_courier_ids 0.0522 - 36123.13x slower
full custom 0.0505 - 37367.23x slower
##### With input Big 2.5 million locations #####
with_courier_ids 937.18
full custom 843.24 - 1.11x slower
DB View 0.22 - 4261.57x slower
##### With input ~200k locations #####
DB View 3.57
with_courier_ids 0.109 - 32.84x slower
full custom 0.0978 - 36.53x slower
##### With input ~20k locations #####
DB View 31.73
with_courier_ids 0.104 - 305.37x slower
full custom 0.0897 - 353.61x slower
##### With input No locations #####
Comparison:
DB View 1885.48
with_courier_ids 0.0522 - 36123.13x slower
full custom 0.0505 - 37367.23x slower
woops
EXPLAIN ANALYZE
SELECT c0."id",
c0."courier_id",
c0."location",
c0."time",
c0."accuracy",
c0."inserted_at",
c0."updated_at"
FROM "courier_locations" AS c0
WHERE (c0."courier_id" = 3799)
ORDER BY c0."time" DESC LIMIT 1;
QUERY PLAN
---------------------------------------------------------------
Limit (cost=0.43..0.83 rows=1 width=72)
(actual time=1.840..1.841 rows=1 loops=1)
-> Index Scan Backward using courier_locations_time_index on
courier_locations c0
(cost=0.43..932600.17 rows=2386932 width=72)
actual time=1.837..1.837 rows=1 loops=1)
Filter: (courier_id = 3799)
Rows Removed by Filter: 1371
Planning time: 0.190 ms
Execution time: 1.894 ms
(6 rows)
EXPLAIN ANALYZE
SELECT c0."id",
c0."courier_id",
c0."location",
c0."time",
c0."accuracy",
c0."inserted_at",
c0."updated_at"
FROM "courier_locations" AS c0
WHERE (c0."courier_id" = 3799)
ORDER BY c0."time" DESC LIMIT 1;
QUERY PLAN
---------------------------------------------------------------
Limit (cost=0.43..0.83 rows=1 width=72)
(actual time=1.840..1.841 rows=1 loops=1)
-> Index Scan Backward using courier_locations_time_index on
courier_locations c0
(cost=0.43..932600.17 rows=2386932 width=72)
actual time=1.837..1.837 rows=1 loops=1)
Filter: (courier_id = 3799)
Rows Removed by Filter: 1371
Planning time: 0.190 ms
Execution time: 1.894 ms
(6 rows)
EXPLAIN ANALYZE
SELECT c0."id",
c0."courier_id",
c0."location",
c0."time",
c0."accuracy",
c0."inserted_at",
c0."updated_at"
FROM "courier_locations" AS c0
WHERE (c0."courier_id" = 3799)
ORDER BY c0."time" DESC LIMIT 1;
QUERY PLAN
---------------------------------------------------------------
Limit (cost=0.43..0.83 rows=1 width=72)
(actual time=1.840..1.841 rows=1 loops=1)
-> Index Scan Backward using courier_locations_time_index on
courier_locations c0
(cost=0.43..932600.17 rows=2386932 width=72)
actual time=1.837..1.837 rows=1 loops=1)
Filter: (courier_id = 3799)
Rows Removed by Filter: 1371
Planning time: 0.190 ms
Execution time: 1.894 ms
(6 rows)
EXPLAIN ANALYZE
SELECT c0."id",
c0."courier_id",
c0."location",
c0."time",
c0."accuracy",
c0."inserted_at",
c0."updated_at"
FROM "courier_locations" AS c0
WHERE (c0."courier_id" = 3799)
ORDER BY c0."time" DESC LIMIT 1;
QUERY PLAN
---------------------------------------------------------------
Limit (cost=0.43..0.83 rows=1 width=72)
(actual time=1.840..1.841 rows=1 loops=1)
-> Index Scan Backward using courier_locations_time_index on
courier_locations c0
(cost=0.43..932600.17 rows=2386932 width=72)
actual time=1.837..1.837 rows=1 loops=1)
Filter: (courier_id = 3799)
Rows Removed by Filter: 1371
Planning time: 0.190 ms
Execution time: 1.894 ms
(6 rows)
Combined Indexes
No locations
20k locations
200k locations
Lots of locations
Let’s talk about
Tail Call Optimization
defmodule MyMap do
def map_body([], _func), do: []
def map_body([head | tail], func) do
[func.(head) | map_body(tail, func)]
end
end
body
defmodule MyMap do
def map_body([], _func), do: []
def map_body([head | tail], func) do
[func.(head) | map_body(tail, func)]
end
end
body
defmodule MyMap do
def map_body([], _func), do: []
def map_body([head | tail], func) do
[func.(head) | map_body(tail, func)]
end
end
body
defmodule MyMap do
def map_body([], _func), do: []
def map_body([head | tail], func) do
[func.(head) | map_body(tail, func)]
end
end
body
defmodule MyMap do
def map_body([], _func), do: []
def map_body([head | tail], func) do
[func.(head) | map_body(tail, func)]
end
end
body
defmodule MyMap do
def map_tco(list, function) do
Enum.reverse do_map_tco([], list, function)
end
defp do_map_tco(acc, [], _function) do
acc
end
defp do_map_tco(acc, [head | tail], func) do
do_map_tco([func.(head) | acc], tail, func)
end
end
TCO
defmodule MyMap do
def map_tco(list, function) do
Enum.reverse do_map_tco([], list, function)
end
defp do_map_tco(acc, [], _function) do
acc
end
defp do_map_tco(acc, [head | tail], func) do
do_map_tco([func.(head) | acc], tail, func)
end
end
TCO
defmodule MyMap do
def map_tco(list, function) do
Enum.reverse do_map_tco([], list, function)
end
defp do_map_tco(acc, [], _function) do
acc
end
defp do_map_tco(acc, [head | tail], func) do
do_map_tco([func.(head) | acc], tail, func)
end
end
TCO
defmodule MyMap do
def map_tco(list, function) do
Enum.reverse do_map_tco([], list, function)
end
defp do_map_tco(acc, [], _function) do
acc
end
defp do_map_tco(acc, [head | tail], func) do
do_map_tco([func.(head) | acc], tail, func)
end
end
TCO
defmodule MyMap do
def map_tco(list, function) do
Enum.reverse do_map_tco([], list, function)
end
defp do_map_tco(acc, [], _function) do
acc
end
defp do_map_tco(acc, [head | tail], func) do
do_map_tco([func.(head) | acc], tail, func)
end
end
TCO
defmodule MyMap do
def map_tco(list, function) do
Enum.reverse do_map_tco([], list, function)
end
defp do_map_tco(acc, [], _function) do
acc
end
defp do_map_tco(acc, [head | tail], func) do
do_map_tco([func.(head) | acc], tail, func)
end
end
TCO
defmodule MyMap do
def map_tco(list, function) do
Enum.reverse do_map_tco([], list, function)
end
defp do_map_tco(acc, [], _function) do
acc
end
defp do_map_tco(acc, [head | tail], func) do
do_map_tco([func.(head) | acc], tail, func)
end
end
TCO
defmodule MyMap do
def map_tco(list, function) do
Enum.reverse do_map_tco([], list, function)
end
defp do_map_tco(acc, [], _function) do
acc
end
defp do_map_tco(acc, [head | tail], func) do
do_map_tco([func.(head) | acc], tail, func)
end
end
TCO
defmodule MyMap do
def map_tco(list, function) do
Enum.reverse do_map_tco([], list, function)
end
defp do_map_tco(acc, [], _function) do
acc
end
defp do_map_tco(acc, [head | tail], func) do
do_map_tco([func.(head) | acc], tail, func)
end
end
TCO
defmodule MyMap do
def map_tco(list, function) do
Enum.reverse do_map_tco(list, function, [])
end
defp do_map_tco([], _function, acc) do
acc
end
defp do_map_tco([head | tail], func, acc) do
do_map_tco(tail, func, [func.(head) | acc])
end
end
arg_order
map_fun = fn(i) -> i + 1 end
inputs = %{
"Small (10 Thousand)" => Enum.to_list(1..10_000),
"Middle (100 Thousand)" => Enum.to_list(1..100_000),
"Big (1 Million)" => Enum.to_list(1..1_000_000),
"Bigger (5 Million)" => Enum.to_list(1..5_000_000)
}
Benchee.run %{
"tail-recursive" =>
fn(list) -> MyMap.map_tco(list, map_fun) end,
"stdlib map" =>
fn(list) -> Enum.map(list, map_fun) end,
"body-recursive" =>
fn(list) -> MyMap.map_body(list, map_fun) end,
"tail-rec arg-order" =>
fn(list) -> MyMap.map_tco_arg_order(list, map_fun) end
}
TCO
map_fun = fn(i) -> i + 1 end
inputs = %{
"Small (10 Thousand)" => Enum.to_list(1..10_000),
"Middle (100 Thousand)" => Enum.to_list(1..100_000),
"Big (1 Million)" => Enum.to_list(1..1_000_000),
"Bigger (5 Million)" => Enum.to_list(1..5_000_000)
}
Benchee.run %{
"tail-recursive" =>
fn(list) -> MyMap.map_tco(list, map_fun) end,
"stdlib map" =>
fn(list) -> Enum.map(list, map_fun) end,
"body-recursive" =>
fn(list) -> MyMap.map_body(list, map_fun) end,
"tail-rec arg-order" =>
fn(list) -> MyMap.map_tco_arg_order(list, map_fun) end
}
TCO
map_fun = fn(i) -> i + 1 end
inputs = %{
"Small (10 Thousand)" => Enum.to_list(1..10_000),
"Middle (100 Thousand)" => Enum.to_list(1..100_000),
"Big (1 Million)" => Enum.to_list(1..1_000_000),
"Bigger (5 Million)" => Enum.to_list(1..5_000_000)
}
Benchee.run %{
"tail-recursive" =>
fn(list) -> MyMap.map_tco(list, map_fun) end,
"stdlib map" =>
fn(list) -> Enum.map(list, map_fun) end,
"body-recursive" =>
fn(list) -> MyMap.map_body(list, map_fun) end,
"tail-rec arg-order" =>
fn(list) -> MyMap.map_tco_arg_order(list, map_fun) end
}
TCO
##### With input Small (10 Thousand) #####
stdlib map 5.05 K
body-recursive 5.00 K - 1.01x slower
tail-rec arg-order 4.07 K - 1.24x slower
tail-recursive 3.76 K - 1.34x slower
##### With input Middle (100 Thousand) #####
stdlib map 468.49
body-recursive 467.04 - 1.00x slower
tail-rec arg-order 452.53 - 1.04x slower
tail-recursive 417.20 - 1.12x slower
##### With input Big (1 Million) #####
body-recursive 40.33
stdlib map 38.89 - 1.04x slower
tail-rec arg-order 37.69 - 1.07x slower
tail-recursive 33.29 - 1.21x slower
##### With input Bigger (5 Million) #####
tail-rec arg-order 6.68
tail-recursive 6.35 - 1.05x slower
stdlib map 5.60 - 1.19x slower
body-recursive 5.39 - 1.24x slower
TCO
##### With input Small (10 Thousand) #####
stdlib map 5.05 K
body-recursive 5.00 K - 1.01x slower
tail-rec arg-order 4.07 K - 1.24x slower
tail-recursive 3.76 K - 1.34x slower
##### With input Middle (100 Thousand) #####
stdlib map 468.49
body-recursive 467.04 - 1.00x slower
tail-rec arg-order 452.53 - 1.04x slower
tail-recursive 417.20 - 1.12x slower
##### With input Big (1 Million) #####
body-recursive 40.33
stdlib map 38.89 - 1.04x slower
tail-rec arg-order 37.69 - 1.07x slower
tail-recursive 33.29 - 1.21x slower
##### With input Bigger (5 Million) #####
tail-rec arg-order 6.68
tail-recursive 6.35 - 1.05x slower
stdlib map 5.60 - 1.19x slower
body-recursive 5.39 - 1.24x slower
TCO
##### With input Small (10 Thousand) #####
stdlib map 5.05 K
body-recursive 5.00 K - 1.01x slower
tail-rec arg-order 4.07 K - 1.24x slower
tail-recursive 3.76 K - 1.34x slower
##### With input Middle (100 Thousand) #####
stdlib map 468.49
body-recursive 467.04 - 1.00x slower
tail-rec arg-order 452.53 - 1.04x slower
tail-recursive 417.20 - 1.12x slower
##### With input Big (1 Million) #####
body-recursive 40.33
stdlib map 38.89 - 1.04x slower
tail-rec arg-order 37.69 - 1.07x slower
tail-recursive 33.29 - 1.21x slower
##### With input Bigger (5 Million) #####
tail-rec arg-order 6.68
tail-recursive 6.35 - 1.05x slower
stdlib map 5.60 - 1.19x slower
body-recursive 5.39 - 1.24x slower
Big Inputs
##### With input Small (10 Thousand) #####
stdlib map 5.05 K
body-recursive 5.00 K - 1.01x slower
tail-rec arg-order 4.07 K - 1.24x slower
tail-recursive 3.76 K - 1.34x slower
##### With input Middle (100 Thousand) #####
stdlib map 468.49
body-recursive 467.04 - 1.00x slower
tail-rec arg-order 452.53 - 1.04x slower
tail-recursive 417.20 - 1.12x slower
##### With input Big (1 Million) #####
body-recursive 40.33
stdlib map 38.89 - 1.04x slower
tail-rec arg-order 37.69 - 1.07x slower
tail-recursive 33.29 - 1.21x slower
##### With input Bigger (5 Million) #####
tail-rec arg-order 6.68
tail-recursive 6.35 - 1.05x slower
stdlib map 5.60 - 1.19x slower
body-recursive 5.39 - 1.24x slower
Order matters!
TCO
What about Memory?
WIP
##### With input Small (10 Thousand) #####
stdlib map 156.85 KB
body-recursive 156.85 KB - 1.00x memory usage
tail-rec arg-order 291.46 KB - 1.86x memory usage
tail-recursive 291.46 KB - 1.86x memory usage
##### With input Middle (100 Thousand) #####
stdlib map 1.53 MB
body-recursive 1.53 MB - 1.00x memory usage
tail-rec arg-order 1.80 MB - 1.18x memory usage
tail-recursive 1.80 MB - 1.18x memory usage
##### With input Big (1 Million) #####
stdlib map 15.26 MB
body-recursive 15.26 MB - 1.00x memory usage
tail-rec arg-order 28.74 MB - 1.88x memory usage
tail-recursive 28.74 MB - 1.88x memory usage
##### With input Bigger (5 Million) #####
tail-rec arg-order 150.15 MB
tail-recursive 150.15 MB - 1.00x memory usage
stdlib map 76.30 MB - 0.51x memory usage
body-recursive 76.30 MB - 0.51x memory usage
Memory
##### With input Small (10 Thousand) #####
stdlib map 156.85 KB
body-recursive 156.85 KB - 1.00x memory usage
tail-rec arg-order 291.46 KB - 1.86x memory usage
tail-recursive 291.46 KB - 1.86x memory usage
##### With input Middle (100 Thousand) #####
stdlib map 1.53 MB
body-recursive 1.53 MB - 1.00x memory usage
tail-rec arg-order 1.80 MB - 1.18x memory usage
tail-recursive 1.80 MB - 1.18x memory usage
##### With input Big (1 Million) #####
stdlib map 15.26 MB
body-recursive 15.26 MB - 1.00x memory usage
tail-rec arg-order 28.74 MB - 1.88x memory usage
tail-recursive 28.74 MB - 1.88x memory usage
##### With input Bigger (5 Million) #####
tail-rec arg-order 150.15 MB
tail-recursive 150.15 MB - 1.00x memory usage
stdlib map 76.30 MB - 0.51x memory usage
body-recursive 76.30 MB - 0.51x memory usage
Memory
What even is a
benchmark?
config
|> Benchee.init
|> Benchee.system
|> Benchee.benchmark("job", fn -> magic end)
|> Benchee.measure
|> Benchee.statistics
|> Benchee.Formatters.Console.output
|> Benchee.Formatters.HTML.output
A transformation of inputs
RUN
YOUR
OWN
BENCHMARKS
Enjoy Benchmarking ❤
Tobias Pfeiffer
@PragTob
pragtob.info
github.com/PragTob/benchee
Excursion into Statistics
average = total_time / iterations
Average
defp standard_deviation(samples, average, iterations) do
total_variance = Enum.reduce samples, 0, fn(sample, total) ->
total + :math.pow((sample - average), 2)
end
variance = total_variance / iterations
:math.sqrt variance
end
Standard Deviation
defp standard_deviation(samples, average, iterations) do
total_variance = Enum.reduce samples, 0, fn(sample, total) ->
total + :math.pow((sample - average), 2)
end
variance = total_variance / iterations
:math.sqrt variance
end
Spread of Values
Raw Run Times
Histogram
Outliers
Standard Deviation
defp compute_median(run_times, iterations) do
sorted = Enum.sort(run_times)
middle = div(iterations, 2)
if Integer.is_odd(iterations) do
sorted |> Enum.at(middle) |> to_float
else
(Enum.at(sorted, middle) +
Enum.at(sorted, middle - 1)) / 2
end
end
Median
Average
Mediam
Boxplot
1 of 147

Recommended

Build a compiler in 2hrs - NCrafts Paris 2015 by
Build a compiler in 2hrs -  NCrafts Paris 2015Build a compiler in 2hrs -  NCrafts Paris 2015
Build a compiler in 2hrs - NCrafts Paris 2015Phillip Trelford
2.3K views32 slides
How fast ist it really? Benchmarking in practice by
How fast ist it really? Benchmarking in practiceHow fast ist it really? Benchmarking in practice
How fast ist it really? Benchmarking in practiceTobias Pfeiffer
4.7K views140 slides
Elixir & Phoenix – fast, concurrent and explicit by
Elixir & Phoenix – fast, concurrent and explicitElixir & Phoenix – fast, concurrent and explicit
Elixir & Phoenix – fast, concurrent and explicitTobias Pfeiffer
5.8K views85 slides
Elixir & Phoenix – fast, concurrent and explicit by
Elixir & Phoenix – fast, concurrent and explicitElixir & Phoenix – fast, concurrent and explicit
Elixir & Phoenix – fast, concurrent and explicitTobias Pfeiffer
2.1K views83 slides
PubNative Tracker by
PubNative TrackerPubNative Tracker
PubNative TrackerAndrew Djoga
369 views23 slides
How fast is it really? Benchmarking in Practice (Ruby Version) by
How fast is it really? Benchmarking in Practice (Ruby Version)How fast is it really? Benchmarking in Practice (Ruby Version)
How fast is it really? Benchmarking in Practice (Ruby Version)Tobias Pfeiffer
2.3K views107 slides

More Related Content

What's hot

Very basic functional design patterns by
Very basic functional design patternsVery basic functional design patterns
Very basic functional design patternsTomasz Kowal
820 views77 slides
The Art, Joy, and Power of Creating Musical Programs (JFugue at SXSW Interact... by
The Art, Joy, and Power of Creating Musical Programs (JFugue at SXSW Interact...The Art, Joy, and Power of Creating Musical Programs (JFugue at SXSW Interact...
The Art, Joy, and Power of Creating Musical Programs (JFugue at SXSW Interact...David Koelle
2.2K views59 slides
tensorflow/keras model coding tutorial 勉強会 by
tensorflow/keras model coding tutorial 勉強会tensorflow/keras model coding tutorial 勉強会
tensorflow/keras model coding tutorial 勉強会RyoyaKatafuchi
70 views24 slides
JFugue, Music, and the Future of Java [JavaOne 2016, CON1851] by
JFugue, Music, and the Future of Java [JavaOne 2016, CON1851]JFugue, Music, and the Future of Java [JavaOne 2016, CON1851]
JFugue, Music, and the Future of Java [JavaOne 2016, CON1851]David Koelle
334 views24 slides
Python chapter 2 by
Python chapter 2Python chapter 2
Python chapter 2Raghu nath
728 views125 slides
python chapter 1 by
python chapter 1python chapter 1
python chapter 1Raghu nath
741 views106 slides

What's hot(20)

Very basic functional design patterns by Tomasz Kowal
Very basic functional design patternsVery basic functional design patterns
Very basic functional design patterns
Tomasz Kowal820 views
The Art, Joy, and Power of Creating Musical Programs (JFugue at SXSW Interact... by David Koelle
The Art, Joy, and Power of Creating Musical Programs (JFugue at SXSW Interact...The Art, Joy, and Power of Creating Musical Programs (JFugue at SXSW Interact...
The Art, Joy, and Power of Creating Musical Programs (JFugue at SXSW Interact...
David Koelle2.2K views
tensorflow/keras model coding tutorial 勉強会 by RyoyaKatafuchi
tensorflow/keras model coding tutorial 勉強会tensorflow/keras model coding tutorial 勉強会
tensorflow/keras model coding tutorial 勉強会
RyoyaKatafuchi70 views
JFugue, Music, and the Future of Java [JavaOne 2016, CON1851] by David Koelle
JFugue, Music, and the Future of Java [JavaOne 2016, CON1851]JFugue, Music, and the Future of Java [JavaOne 2016, CON1851]
JFugue, Music, and the Future of Java [JavaOne 2016, CON1851]
David Koelle334 views
Python chapter 2 by Raghu nath
Python chapter 2Python chapter 2
Python chapter 2
Raghu nath728 views
python chapter 1 by Raghu nath
python chapter 1python chapter 1
python chapter 1
Raghu nath741 views
Some Pry Features by Yann VERY
Some Pry FeaturesSome Pry Features
Some Pry Features
Yann VERY138 views
Palestra sobre Collections com Python by pugpe
Palestra sobre Collections com PythonPalestra sobre Collections com Python
Palestra sobre Collections com Python
pugpe1.5K views
"PostgreSQL and Python" Lightning Talk @EuroPython2014 by Henning Jacobs
"PostgreSQL and Python" Lightning Talk @EuroPython2014"PostgreSQL and Python" Lightning Talk @EuroPython2014
"PostgreSQL and Python" Lightning Talk @EuroPython2014
Henning Jacobs1.2K views
Clustering com numpy e cython by Anderson Dantas
Clustering com numpy e cythonClustering com numpy e cython
Clustering com numpy e cython
Anderson Dantas1.4K views
Kotlin - Coroutine by Sean Tsai
Kotlin - CoroutineKotlin - Coroutine
Kotlin - Coroutine
Sean Tsai658 views
Python postgre sql a wonderful wedding by Stéphane Wirtel
Python postgre sql   a wonderful weddingPython postgre sql   a wonderful wedding
Python postgre sql a wonderful wedding
Stéphane Wirtel1.9K views
Elixir -Tolerância a Falhas para Adultos - GDG Campinas by Fabio Akita
Elixir  -Tolerância a Falhas para Adultos - GDG CampinasElixir  -Tolerância a Falhas para Adultos - GDG Campinas
Elixir -Tolerância a Falhas para Adultos - GDG Campinas
Fabio Akita525 views
Sequencing Audio Using React and the Web Audio API by Vincent Riemer
Sequencing Audio Using React and the Web Audio APISequencing Audio Using React and the Web Audio API
Sequencing Audio Using React and the Web Audio API
Vincent Riemer988 views
Tackling Asynchrony with Kotlin Coroutines by Tech Triveni
Tackling Asynchrony with Kotlin CoroutinesTackling Asynchrony with Kotlin Coroutines
Tackling Asynchrony with Kotlin Coroutines
Tech Triveni26 views

Similar to Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)

Introducción a Elixir by
Introducción a ElixirIntroducción a Elixir
Introducción a ElixirSvet Ivantchev
68 views40 slides
Python Functions (PyAtl Beginners Night) by
Python Functions (PyAtl Beginners Night)Python Functions (PyAtl Beginners Night)
Python Functions (PyAtl Beginners Night)Rick Copeland
2.4K views97 slides
Writing Faster Python 3 by
Writing Faster Python 3Writing Faster Python 3
Writing Faster Python 3Sebastian Witowski
18 views134 slides
PythonOOP by
PythonOOPPythonOOP
PythonOOPVeera Pendyala
907 views157 slides
Kernel Recipes 2017 - Understanding the Linux kernel via ftrace - Steven Rostedt by
Kernel Recipes 2017 - Understanding the Linux kernel via ftrace - Steven RostedtKernel Recipes 2017 - Understanding the Linux kernel via ftrace - Steven Rostedt
Kernel Recipes 2017 - Understanding the Linux kernel via ftrace - Steven RostedtAnne Nicolas
5.6K views123 slides
Pre-Bootcamp introduction to Elixir by
Pre-Bootcamp introduction to ElixirPre-Bootcamp introduction to Elixir
Pre-Bootcamp introduction to ElixirPaweł Dawczak
128 views175 slides

Similar to Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)(20)

Python Functions (PyAtl Beginners Night) by Rick Copeland
Python Functions (PyAtl Beginners Night)Python Functions (PyAtl Beginners Night)
Python Functions (PyAtl Beginners Night)
Rick Copeland2.4K views
Kernel Recipes 2017 - Understanding the Linux kernel via ftrace - Steven Rostedt by Anne Nicolas
Kernel Recipes 2017 - Understanding the Linux kernel via ftrace - Steven RostedtKernel Recipes 2017 - Understanding the Linux kernel via ftrace - Steven Rostedt
Kernel Recipes 2017 - Understanding the Linux kernel via ftrace - Steven Rostedt
Anne Nicolas5.6K views
Pre-Bootcamp introduction to Elixir by Paweł Dawczak
Pre-Bootcamp introduction to ElixirPre-Bootcamp introduction to Elixir
Pre-Bootcamp introduction to Elixir
Paweł Dawczak128 views
Introduction to Elixir and Phoenix.pdf by Ridwan Fadjar
Introduction to Elixir and Phoenix.pdfIntroduction to Elixir and Phoenix.pdf
Introduction to Elixir and Phoenix.pdf
Ridwan Fadjar18 views
Alexander Reelsen - Seccomp for Developers by DevDay Dresden
Alexander Reelsen - Seccomp for DevelopersAlexander Reelsen - Seccomp for Developers
Alexander Reelsen - Seccomp for Developers
DevDay Dresden116 views
Puppet Language 4.0 - PuppetConf 2014 by Puppet
Puppet Language 4.0 - PuppetConf 2014Puppet Language 4.0 - PuppetConf 2014
Puppet Language 4.0 - PuppetConf 2014
Puppet4.9K views
GenServer in Action – Yurii Bodarev by Elixir Club
GenServer in Action – Yurii Bodarev   GenServer in Action – Yurii Bodarev
GenServer in Action – Yurii Bodarev
Elixir Club624 views
Comparative Genomics with GMOD and BioPerl by Jason Stajich
Comparative Genomics with GMOD and BioPerlComparative Genomics with GMOD and BioPerl
Comparative Genomics with GMOD and BioPerl
Jason Stajich1.5K views
NTU ML TENSORFLOW by Mark Chang
NTU ML TENSORFLOWNTU ML TENSORFLOW
NTU ML TENSORFLOW
Mark Chang4.9K views

More from Tobias Pfeiffer

Metaphors are everywhere: Ideas to Improve Software Development by
 Metaphors are everywhere: Ideas to Improve Software Development  Metaphors are everywhere: Ideas to Improve Software Development
Metaphors are everywhere: Ideas to Improve Software Development Tobias Pfeiffer
11 views126 slides
Stories in Open Source by
Stories in Open SourceStories in Open Source
Stories in Open SourceTobias Pfeiffer
2.9K views68 slides
Elixir & Phoenix – Fast, Concurrent and Explicit by
Elixir & Phoenix – Fast, Concurrent and ExplicitElixir & Phoenix – Fast, Concurrent and Explicit
Elixir & Phoenix – Fast, Concurrent and ExplicitTobias Pfeiffer
3.4K views88 slides
Functioning Among Humans by
Functioning Among HumansFunctioning Among Humans
Functioning Among HumansTobias Pfeiffer
3.3K views58 slides
Functioning Among Humans by
Functioning Among HumansFunctioning Among Humans
Functioning Among HumansTobias Pfeiffer
3K views57 slides
Do You Need That Validation? Let Me Call You Back About It by
Do You Need That Validation? Let Me Call You Back About ItDo You Need That Validation? Let Me Call You Back About It
Do You Need That Validation? Let Me Call You Back About ItTobias Pfeiffer
2.1K views142 slides

More from Tobias Pfeiffer(20)

Metaphors are everywhere: Ideas to Improve Software Development by Tobias Pfeiffer
 Metaphors are everywhere: Ideas to Improve Software Development  Metaphors are everywhere: Ideas to Improve Software Development
Metaphors are everywhere: Ideas to Improve Software Development
Tobias Pfeiffer11 views
Elixir & Phoenix – Fast, Concurrent and Explicit by Tobias Pfeiffer
Elixir & Phoenix – Fast, Concurrent and ExplicitElixir & Phoenix – Fast, Concurrent and Explicit
Elixir & Phoenix – Fast, Concurrent and Explicit
Tobias Pfeiffer3.4K views
Do You Need That Validation? Let Me Call You Back About It by Tobias Pfeiffer
Do You Need That Validation? Let Me Call You Back About ItDo You Need That Validation? Let Me Call You Back About It
Do You Need That Validation? Let Me Call You Back About It
Tobias Pfeiffer2.1K views
Elixir, your Monolith and You by Tobias Pfeiffer
Elixir, your Monolith and YouElixir, your Monolith and You
Elixir, your Monolith and You
Tobias Pfeiffer3.5K views
It's About the Humans, Stupid (Lightning) by Tobias Pfeiffer
It's About the Humans, Stupid (Lightning)It's About the Humans, Stupid (Lightning)
It's About the Humans, Stupid (Lightning)
Tobias Pfeiffer2.1K views
Code, Comments, Concepts, Comprehension – Conclusion? by Tobias Pfeiffer
Code, Comments, Concepts, Comprehension – Conclusion?Code, Comments, Concepts, Comprehension – Conclusion?
Code, Comments, Concepts, Comprehension – Conclusion?
Tobias Pfeiffer2K views
What did AlphaGo do to beat the strongest human Go player? by Tobias Pfeiffer
What did AlphaGo do to beat the strongest human Go player?What did AlphaGo do to beat the strongest human Go player?
What did AlphaGo do to beat the strongest human Go player?
Tobias Pfeiffer2.2K views
What did AlphaGo do to beat the strongest human Go player? by Tobias Pfeiffer
What did AlphaGo do to beat the strongest human Go player?What did AlphaGo do to beat the strongest human Go player?
What did AlphaGo do to beat the strongest human Go player?
Tobias Pfeiffer1.4K views
What did AlphaGo do to beat the strongest human Go player? (Strange Group Ver... by Tobias Pfeiffer
What did AlphaGo do to beat the strongest human Go player? (Strange Group Ver...What did AlphaGo do to beat the strongest human Go player? (Strange Group Ver...
What did AlphaGo do to beat the strongest human Go player? (Strange Group Ver...
Tobias Pfeiffer290 views
Ruby to Elixir - what's great and what you might miss by Tobias Pfeiffer
Ruby to Elixir - what's great and what you might missRuby to Elixir - what's great and what you might miss
Ruby to Elixir - what's great and what you might miss
Tobias Pfeiffer420 views
Elixir & Phoenix - fast, concurrent and explicit by Tobias Pfeiffer
Elixir & Phoenix - fast, concurrent and explicitElixir & Phoenix - fast, concurrent and explicit
Elixir & Phoenix - fast, concurrent and explicit
Tobias Pfeiffer668 views
Beating Go Thanks to the Power of Randomness by Tobias Pfeiffer
Beating Go Thanks to the Power of RandomnessBeating Go Thanks to the Power of Randomness
Beating Go Thanks to the Power of Randomness
Tobias Pfeiffer332 views
Code is read many mor times than written - short by Tobias Pfeiffer
Code is read many mor times than written - shortCode is read many mor times than written - short
Code is read many mor times than written - short
Tobias Pfeiffer721 views
Code is read many more times than written by Tobias Pfeiffer
Code is read many more times than writtenCode is read many more times than written
Code is read many more times than written
Tobias Pfeiffer1.9K views

Recently uploaded

360 graden fabriek by
360 graden fabriek360 graden fabriek
360 graden fabriekinfo33492
165 views25 slides
EV Charging App Case by
EV Charging App Case EV Charging App Case
EV Charging App Case iCoderz Solutions
9 views1 slide
What is API by
What is APIWhat is API
What is APIartembondar5
13 views15 slides
How to build dyanmic dashboards and ensure they always work by
How to build dyanmic dashboards and ensure they always workHow to build dyanmic dashboards and ensure they always work
How to build dyanmic dashboards and ensure they always workWiiisdom
14 views13 slides
ADDO_2022_CICID_Tom_Halpin.pdf by
ADDO_2022_CICID_Tom_Halpin.pdfADDO_2022_CICID_Tom_Halpin.pdf
ADDO_2022_CICID_Tom_Halpin.pdfTomHalpin9
5 views33 slides
How To Make Your Plans Suck Less — Maarten Dalmijn at the 57th Hands-on Agile... by
How To Make Your Plans Suck Less — Maarten Dalmijn at the 57th Hands-on Agile...How To Make Your Plans Suck Less — Maarten Dalmijn at the 57th Hands-on Agile...
How To Make Your Plans Suck Less — Maarten Dalmijn at the 57th Hands-on Agile...Stefan Wolpers
42 views38 slides

Recently uploaded(20)

360 graden fabriek by info33492
360 graden fabriek360 graden fabriek
360 graden fabriek
info33492165 views
How to build dyanmic dashboards and ensure they always work by Wiiisdom
How to build dyanmic dashboards and ensure they always workHow to build dyanmic dashboards and ensure they always work
How to build dyanmic dashboards and ensure they always work
Wiiisdom14 views
ADDO_2022_CICID_Tom_Halpin.pdf by TomHalpin9
ADDO_2022_CICID_Tom_Halpin.pdfADDO_2022_CICID_Tom_Halpin.pdf
ADDO_2022_CICID_Tom_Halpin.pdf
TomHalpin95 views
How To Make Your Plans Suck Less — Maarten Dalmijn at the 57th Hands-on Agile... by Stefan Wolpers
How To Make Your Plans Suck Less — Maarten Dalmijn at the 57th Hands-on Agile...How To Make Your Plans Suck Less — Maarten Dalmijn at the 57th Hands-on Agile...
How To Make Your Plans Suck Less — Maarten Dalmijn at the 57th Hands-on Agile...
Stefan Wolpers42 views
Bootstrapping vs Venture Capital.pptx by Zeljko Svedic
Bootstrapping vs Venture Capital.pptxBootstrapping vs Venture Capital.pptx
Bootstrapping vs Venture Capital.pptx
Zeljko Svedic15 views
AI and Ml presentation .pptx by FayazAli87
AI and Ml presentation .pptxAI and Ml presentation .pptx
AI and Ml presentation .pptx
FayazAli8714 views
Automated Testing of Microsoft Power BI Reports by RTTS
Automated Testing of Microsoft Power BI ReportsAutomated Testing of Microsoft Power BI Reports
Automated Testing of Microsoft Power BI Reports
RTTS10 views
Introduction to Git Source Control by John Valentino
Introduction to Git Source ControlIntroduction to Git Source Control
Introduction to Git Source Control
John Valentino7 views
Dev-HRE-Ops - Addressing the _Last Mile DevOps Challenge_ in Highly Regulated... by TomHalpin9
Dev-HRE-Ops - Addressing the _Last Mile DevOps Challenge_ in Highly Regulated...Dev-HRE-Ops - Addressing the _Last Mile DevOps Challenge_ in Highly Regulated...
Dev-HRE-Ops - Addressing the _Last Mile DevOps Challenge_ in Highly Regulated...
TomHalpin96 views
Dapr Unleashed: Accelerating Microservice Development by Miroslav Janeski
Dapr Unleashed: Accelerating Microservice DevelopmentDapr Unleashed: Accelerating Microservice Development
Dapr Unleashed: Accelerating Microservice Development
Miroslav Janeski15 views
JioEngage_Presentation.pptx by admin125455
JioEngage_Presentation.pptxJioEngage_Presentation.pptx
JioEngage_Presentation.pptx
admin1254558 views

Stop Guessing and Start Measuring - Benchmarking in Practice (Lambdadays)

  • 3. iex(1)> defmodule RepeatN do ...(1)> def repeat_n(_function, 0) do ...(1)> # noop ...(1)> end ...(1)> def repeat_n(function, 1) do ...(1)> function.() ...(1)> end ...(1)> def repeat_n(function, count) do ...(1)> function.() ...(1)> repeat_n(function, count - 1) ...(1)> end ...(1)> end {:module, RepeatN, ...}
  • 4. iex(1)> defmodule RepeatN do ...(1)> def repeat_n(_function, 0) do ...(1)> # noop ...(1)> end ...(1)> def repeat_n(function, 1) do ...(1)> function.() ...(1)> end ...(1)> def repeat_n(function, count) do ...(1)> function.() ...(1)> repeat_n(function, count - 1) ...(1)> end ...(1)> end {:module, RepeatN, ...} iex(2)> :timer.tc fn -> RepeatN.repeat_n(fn -> 0 end, 100) end {210, 0}
  • 5. iex(1)> defmodule RepeatN do ...(1)> def repeat_n(_function, 0) do ...(1)> # noop ...(1)> end ...(1)> def repeat_n(function, 1) do ...(1)> function.() ...(1)> end ...(1)> def repeat_n(function, count) do ...(1)> function.() ...(1)> repeat_n(function, count - 1) ...(1)> end ...(1)> end {:module, RepeatN, ...} iex(2)> :timer.tc fn -> RepeatN.repeat_n(fn -> 0 end, 100) end {210, 0} iex(3)> list = Enum.to_list(1..100) [...] iex(4)> :timer.tc fn -> Enum.each(list, fn(_) -> 0 end) end {165, :ok}
  • 6. iex(1)> defmodule RepeatN do ...(1)> def repeat_n(_function, 0) do ...(1)> # noop ...(1)> end ...(1)> def repeat_n(function, 1) do ...(1)> function.() ...(1)> end ...(1)> def repeat_n(function, count) do ...(1)> function.() ...(1)> repeat_n(function, count - 1) ...(1)> end ...(1)> end {:module, RepeatN, ...} iex(2)> :timer.tc fn -> RepeatN.repeat_n(fn -> 0 end, 100) end {210, 0} iex(3)> list = Enum.to_list(1..100) [...] iex(4)> :timer.tc fn -> Enum.each(list, fn(_) -> 0 end) end {165, :ok} iex(5)> :timer.tc fn -> Enum.each(list, fn(_) -> 0 end) end {170, :ok} iex(6)> :timer.tc fn -> Enum.each(list, fn(_) -> 0 end) end {184, :ok}
  • 10. ● Way too few samples ● Realistic data/multiple inputs? ● No warmup ● Non production environment ● Does creating the list matter? ● Is repeating really the bottle neck? ● Repeatability? ● Setup information ● Running on battery ● Lots of applications running Numerous Failures!
  • 11. n = 10_000 range = 1..n list = Enum.to_list range fun = fn -> 0 end Benchee.run %{ "Enum.each" => fn -> Enum.each(list, fn(_) -> fun.() end) end, "List comprehension" => fn -> for _ <- list, do: fun.() end, "Recursion" => fn -> RepeatN.repeat_n(fun, n) end }
  • 12. Name ips average deviation median 99th % Recursion 6.95 K 143.80 μs ±2.76% 142 μs 156 μs Enum.each 4.21 K 237.70 μs ±6.47% 230 μs 278 μs List comprehension 3.07 K 325.88 μs ±10.02% 333 μs 373 μs Comparison: Recursion 6.95 K Enum.each 4.21 K - 1.65x slower List comprehension 3.07 K - 2.27x slower
  • 13. Name ips average deviation median 99th % Recursion 6.95 K 143.80 μs ±2.76% 142 μs 156 μs Enum.each 4.21 K 237.70 μs ±6.47% 230 μs 278 μs List comprehension 3.07 K 325.88 μs ±10.02% 333 μs 373 μs Comparison: Recursion 6.95 K Enum.each 4.21 K - 1.65x slower List comprehension 3.07 K - 2.27x slower Iterations per Second
  • 14. Stop guessing and Start Measuring Benchmarking in Practice Tobias Pfeiffer @PragTob pragtob.info github.com/PragTob/benchee
  • 22. Flame Graph Elixir.Life.Board:map/2 El.. Elixir.Enum:-map/2-lc$^0/1-0-/2El.. El..El.. El.. Elixir.Life.Board:map/2Elixir.Life.Board:map/2 El.. El.. Elixir.Life.Board:map/2 Elixir.Enum:-map/2-lc$^0/1-0-/2 El.. El..Elixir.Enum:-map/2-lc$^0/1-0-/2Elixir.Enum:-map/2-lc$^0/1-0-/2 (0.47.0) Eli.. eflame:apply1/3 Elixir.Life:run_loop/3 Elixir.Life.Board:map/2 Elixir.Enum:-map/2-lc$^0/1-0-/2 El..El.. El.. http://learningelixir.joekain.com/profiling-elixir-2/
  • 27. How long will this take?
  • 30. Did we make it faster?
  • 32. What's the fastest way to sort a list of numbers largest to smallest?
  • 33. list = 1..10_000 |> Enum.to_list |> Enum.shuffle Benchee.run %{ "sort(fun)" => fn -> Enum.sort(list, &(&1 > &2)) end, "sort |> reverse" => fn -> list |> Enum.sort |> Enum.reverse end, "sort_by(-value)" => fn -> Enum.sort_by(list, fn(val) -> -val end) end }
  • 34. list = 1..10_000 |> Enum.to_list |> Enum.shuffle Benchee.run %{ "sort(fun)" => fn -> Enum.sort(list, &(&1 > &2)) end, "sort |> reverse" => fn -> list |> Enum.sort |> Enum.reverse end, "sort_by(-value)" => fn -> Enum.sort_by(list, fn(val) -> -val end) end }
  • 35. list = 1..10_000 |> Enum.to_list |> Enum.shuffle Benchee.run %{ "sort(fun)" => fn -> Enum.sort(list, &(&1 > &2)) end, "sort |> reverse" => fn -> list |> Enum.sort |> Enum.reverse end, "sort_by(-value)" => fn -> Enum.sort_by(list, fn(val) -> -val end) end }
  • 36. list = 1..10_000 |> Enum.to_list |> Enum.shuffle Benchee.run %{ "sort(fun)" => fn -> Enum.sort(list, &(&1 > &2)) end, "sort |> reverse" => fn -> list |> Enum.sort |> Enum.reverse end, "sort_by(-value)" => fn -> Enum.sort_by(list, fn(val) -> -val end) end }
  • 37. Name ips average deviation median 99th % sort |> reverse 576.52 1.74 ms ±7.11% 1.70 ms 2.25 ms sort(fun) 241.21 4.15 ms ±3.04% 4.15 ms 4.60 ms sort_by(-value) 149.96 6.67 ms ±6.06% 6.50 ms 7.83 ms Comparison: sort |> reverse 576.52 sort(fun) 241.21 - 2.39x slower sort_by(-value) 149.96 - 3.84x slower
  • 38. “Isn’t that the root of all evil?”
  • 39. “More likely, not reading the sources is the source of all evil.” Me, just now
  • 40. “We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.” Donald Knuth, 1974 (Computing Surveys, Vol 6, No 4, December 1974) Yup it’s there
  • 41. “Yet we should not pass up our opportunities in that critical 3%.” Donald Knuth, 1974 (Computing Surveys, Vol 6, No 4, December 1974) The very next sentence
  • 42. “(...) a 12 % improvement, easily obtained, is never considered marginal” Donald Knuth, 1974 (Computing Surveys, Vol 6, No 4, December 1974) Prior Paragraph
  • 49. Micro Macro Setup Complexity Execution Time Components involved Application
  • 50. Micro Macro Setup Complexity Execution Time Confidence of Real Impact Components involved Application
  • 51. Micro Macro Setup Complexity Execution Time Confidence of Real Impact Components involved Chance of Interference Application
  • 52. Micro Macro Setup Complexity Execution Time Confidence of Real Impact Components involved Chance of Interference Golden Middle Application
  • 55. ● Elixir 1.6.1 ● Erlang 20.2 ● i5-7200U – 2 x 2.5GHz (Up to 3.10GHz) ● 8GB RAM ● Linux Mint 18.3 - 64 bit (Ubuntu 16.04 base) System Specification
  • 56. System Specification tobi@comfy elixir_playground $ mix run bench/something.exs Operating System: Linux CPU Information: Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz Number of Available Cores: 4 Available memory: 7.67 GB Elixir 1.6.1 Erlang 20.2 Benchmark suite executing with the following configuration: warmup: 2 s time: 5 s parallel: 1 inputs: none specified Estimated total run time: 21 s
  • 59. 1.4!
  • 60. 1.3
  • 63. [info] GET / [debug] Processing by Rumbl.PageController.index/2 Parameters: %{} Pipelines: [:browser] [info] Sent 200 in 46ms [info] GET /sessions/new [debug] Processing by Rumbl.SessionController.new/2 Parameters: %{} Pipelines: [:browser] [info] Sent 200 in 5ms [info] GET /users/new [debug] Processing by Rumbl.UserController.new/2 Parameters: %{} Pipelines: [:browser] [info] Sent 200 in 7ms [info] POST /users [debug] Processing by Rumbl.UserController.create/2 Parameters: %{"_csrf_token" => "NUEUdRMNAiBfIHEeNwZkfA05PgAOJgAAf0ACXJqCjl7YojW+trdjdg==", "_utf8" => " ", "user"✓ => %{"name" => "asdasd", "password" => "[FILTERED]", "username" => "Homer"}} Pipelines: [:browser] [debug] QUERY OK db=0.1ms begin [] [debug] QUERY OK db=0.9ms INSERT INTO "users" ("name","password_hash","username","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" ["asdasd", "$2b$12$.qY/kpo0Dec7vMK1ClJoC.Lw77c3oGllX7uieZILMlFh2hFpJ3F.C", "Homer", {{2016, 12, 2}, {14, 10, 28, 0}}, {{2016, 12, 2}, {14, 10, 28, 0}}] Logging & Friends
  • 67. $ time my_program Probably not what you want
  • 71. Startup Warmup Runtime What’s important for you?
  • 75. Boom
  • 78. Benchee.run %{ "DB View" => fn -> LatestCourierLocation |> CourierLocation.with_courier_ids(courier_id) |> Repo.one(timeout: :infinity) end, "with_courier_ids" => fn -> CourierLocation.with_courier_ids(courier_id) |> Ecto.Query.order_by(desc: :time) |> Ecto.Query.limit(1) |> Repo.one(timeout: :infinity) end, "full custom" => fn -> CourierLocation |> Ecto.Query.where(courier_id: ^courier_id) |> Ecto.Query.order_by(desc: :time) |> Ecto.Query.limit(1) |> Repo.one(timeout: :infinity) end }
  • 79. Benchee.run %{ "DB View" => fn -> LatestCourierLocation |> CourierLocation.with_courier_ids(courier_id) |> Repo.one(timeout: :infinity) end, "with_courier_ids" => fn -> CourierLocation.with_courier_ids(courier_id) |> Ecto.Query.order_by(desc: :time) |> Ecto.Query.limit(1) |> Repo.one(timeout: :infinity) end, "full custom" => fn -> CourierLocation |> Ecto.Query.where(courier_id: ^courier_id) |> Ecto.Query.order_by(desc: :time) |> Ecto.Query.limit(1) |> Repo.one(timeout: :infinity) end } Database View
  • 80. Benchee.run %{ "DB View" => fn -> LatestCourierLocation |> CourierLocation.with_courier_ids(courier_id) |> Repo.one(timeout: :infinity) end, "with_courier_ids" => fn -> CourierLocation.with_courier_ids(courier_id) |> Ecto.Query.order_by(desc: :time) |> Ecto.Query.limit(1) |> Repo.one(timeout: :infinity) end, "full custom" => fn -> CourierLocation |> Ecto.Query.where(courier_id: ^courier_id) |> Ecto.Query.order_by(desc: :time) |> Ecto.Query.limit(1) |> Repo.one(timeout: :infinity) end } Only difference
  • 81. Benchee.run %{ "DB View" => fn -> LatestCourierLocation |> CourierLocation.with_courier_ids(courier_id) |> Repo.one(timeout: :infinity) end, "with_courier_ids" => fn -> CourierLocation.with_courier_ids(courier_id) |> Ecto.Query.order_by(desc: :time) |> Ecto.Query.limit(1) |> Repo.one(timeout: :infinity) end, "full custom" => fn -> CourierLocation |> Ecto.Query.where(courier_id: ^courier_id) |> Ecto.Query.order_by(desc: :time) |> Ecto.Query.limit(1) |> Repo.one(timeout: :infinity) end } Same
  • 82. Name ips average deviation median 99th % with_courier_ids 983.21 1.02 ms ±43.22% 0.91 ms 3.19 ms full custom 855.07 1.17 ms ±53.86% 0.96 ms 4.25 ms DB View 0.21 4704.70 ms ±4.89% 4738.83 ms 4964.49 ms Comparison: with_courier_ids 983.21 full custom 855.07 - 1.15x slower DB View 0.21 - 4625.70x slower Another job well done?
  • 84. Boom
  • 89. inputs = %{ "Big 2.3 Million locations" => 3799, "No locations" => 8901, "~200k locations" => 4238, "~20k locations" => 4201 } Benchee.run %{ ... "full custom" => fn(courier_id) -> CourierLocation |> Ecto.Query.where(courier_id: ^courier_id) |> Ecto.Query.order_by(desc: :time) |> Ecto.Query.limit(1) |> Repo.one(timeout: :infinity) end }, inputs: inputs, time: 25, warmup: 5 Inputs to the rescue!
  • 90. inputs = %{ "Big 2.3 Million locations" => 3799, "No locations" => 8901, "~200k locations" => 4238, "~20k locations" => 4201 } Benchee.run %{ ... "full custom" => fn(courier_id) -> CourierLocation |> Ecto.Query.where(courier_id: ^courier_id) |> Ecto.Query.order_by(desc: :time) |> Ecto.Query.limit(1) |> Repo.one(timeout: :infinity) end }, inputs: inputs, time: 25, warmup: 5 Inputs to the rescue!
  • 91. inputs = %{ "Big 2.3 Million locations" => 3799, "No locations" => 8901, "~200k locations" => 4238, "~20k locations" => 4201 } Benchee.run %{ ... "full custom" => fn(courier_id) -> CourierLocation |> Ecto.Query.where(courier_id: ^courier_id) |> Ecto.Query.order_by(desc: :time) |> Ecto.Query.limit(1) |> Repo.one(timeout: :infinity) end }, inputs: inputs, time: 25, warmup: 5 Inputs to the rescue!
  • 92. inputs = %{ "Big 2.3 Million locations" => 3799, "No locations" => 8901, "~200k locations" => 4238, "~20k locations" => 4201 } Benchee.run %{ ... "full custom" => fn(courier_id) -> CourierLocation |> Ecto.Query.where(courier_id: ^courier_id) |> Ecto.Query.order_by(desc: :time) |> Ecto.Query.limit(1) |> Repo.one(timeout: :infinity) end }, inputs: inputs, time: 25, warmup: 5 Inputs to the rescue!
  • 93. ##### With input Big 2.5 million locations ##### with_courier_ids 937.18 full custom 843.24 - 1.11x slower DB View 0.22 - 4261.57x slower ##### With input ~200k locations ##### DB View 3.57 with_courier_ids 0.109 - 32.84x slower full custom 0.0978 - 36.53x slower ##### With input ~20k locations ##### DB View 31.73 with_courier_ids 0.104 - 305.37x slower full custom 0.0897 - 353.61x slower ##### With input No locations ##### Comparison: DB View 1885.48 with_courier_ids 0.0522 - 36123.13x slower full custom 0.0505 - 37367.23x slower
  • 94. ##### With input Big 2.5 million locations ##### with_courier_ids 937.18 full custom 843.24 - 1.11x slower DB View 0.22 - 4261.57x slower ##### With input ~200k locations ##### DB View 3.57 with_courier_ids 0.109 - 32.84x slower full custom 0.0978 - 36.53x slower ##### With input ~20k locations ##### DB View 31.73 with_courier_ids 0.104 - 305.37x slower full custom 0.0897 - 353.61x slower ##### With input No locations ##### Comparison: DB View 1885.48 with_courier_ids 0.0522 - 36123.13x slower full custom 0.0505 - 37367.23x slower woops
  • 95. EXPLAIN ANALYZE SELECT c0."id", c0."courier_id", c0."location", c0."time", c0."accuracy", c0."inserted_at", c0."updated_at" FROM "courier_locations" AS c0 WHERE (c0."courier_id" = 3799) ORDER BY c0."time" DESC LIMIT 1; QUERY PLAN --------------------------------------------------------------- Limit (cost=0.43..0.83 rows=1 width=72) (actual time=1.840..1.841 rows=1 loops=1) -> Index Scan Backward using courier_locations_time_index on courier_locations c0 (cost=0.43..932600.17 rows=2386932 width=72) actual time=1.837..1.837 rows=1 loops=1) Filter: (courier_id = 3799) Rows Removed by Filter: 1371 Planning time: 0.190 ms Execution time: 1.894 ms (6 rows)
  • 96. EXPLAIN ANALYZE SELECT c0."id", c0."courier_id", c0."location", c0."time", c0."accuracy", c0."inserted_at", c0."updated_at" FROM "courier_locations" AS c0 WHERE (c0."courier_id" = 3799) ORDER BY c0."time" DESC LIMIT 1; QUERY PLAN --------------------------------------------------------------- Limit (cost=0.43..0.83 rows=1 width=72) (actual time=1.840..1.841 rows=1 loops=1) -> Index Scan Backward using courier_locations_time_index on courier_locations c0 (cost=0.43..932600.17 rows=2386932 width=72) actual time=1.837..1.837 rows=1 loops=1) Filter: (courier_id = 3799) Rows Removed by Filter: 1371 Planning time: 0.190 ms Execution time: 1.894 ms (6 rows)
  • 97. EXPLAIN ANALYZE SELECT c0."id", c0."courier_id", c0."location", c0."time", c0."accuracy", c0."inserted_at", c0."updated_at" FROM "courier_locations" AS c0 WHERE (c0."courier_id" = 3799) ORDER BY c0."time" DESC LIMIT 1; QUERY PLAN --------------------------------------------------------------- Limit (cost=0.43..0.83 rows=1 width=72) (actual time=1.840..1.841 rows=1 loops=1) -> Index Scan Backward using courier_locations_time_index on courier_locations c0 (cost=0.43..932600.17 rows=2386932 width=72) actual time=1.837..1.837 rows=1 loops=1) Filter: (courier_id = 3799) Rows Removed by Filter: 1371 Planning time: 0.190 ms Execution time: 1.894 ms (6 rows)
  • 98. EXPLAIN ANALYZE SELECT c0."id", c0."courier_id", c0."location", c0."time", c0."accuracy", c0."inserted_at", c0."updated_at" FROM "courier_locations" AS c0 WHERE (c0."courier_id" = 3799) ORDER BY c0."time" DESC LIMIT 1; QUERY PLAN --------------------------------------------------------------- Limit (cost=0.43..0.83 rows=1 width=72) (actual time=1.840..1.841 rows=1 loops=1) -> Index Scan Backward using courier_locations_time_index on courier_locations c0 (cost=0.43..932600.17 rows=2386932 width=72) actual time=1.837..1.837 rows=1 loops=1) Filter: (courier_id = 3799) Rows Removed by Filter: 1371 Planning time: 0.190 ms Execution time: 1.894 ms (6 rows)
  • 104. Let’s talk about Tail Call Optimization
  • 105. defmodule MyMap do def map_body([], _func), do: [] def map_body([head | tail], func) do [func.(head) | map_body(tail, func)] end end body
  • 106. defmodule MyMap do def map_body([], _func), do: [] def map_body([head | tail], func) do [func.(head) | map_body(tail, func)] end end body
  • 107. defmodule MyMap do def map_body([], _func), do: [] def map_body([head | tail], func) do [func.(head) | map_body(tail, func)] end end body
  • 108. defmodule MyMap do def map_body([], _func), do: [] def map_body([head | tail], func) do [func.(head) | map_body(tail, func)] end end body
  • 109. defmodule MyMap do def map_body([], _func), do: [] def map_body([head | tail], func) do [func.(head) | map_body(tail, func)] end end body
  • 110. defmodule MyMap do def map_tco(list, function) do Enum.reverse do_map_tco([], list, function) end defp do_map_tco(acc, [], _function) do acc end defp do_map_tco(acc, [head | tail], func) do do_map_tco([func.(head) | acc], tail, func) end end TCO
  • 111. defmodule MyMap do def map_tco(list, function) do Enum.reverse do_map_tco([], list, function) end defp do_map_tco(acc, [], _function) do acc end defp do_map_tco(acc, [head | tail], func) do do_map_tco([func.(head) | acc], tail, func) end end TCO
  • 112. defmodule MyMap do def map_tco(list, function) do Enum.reverse do_map_tco([], list, function) end defp do_map_tco(acc, [], _function) do acc end defp do_map_tco(acc, [head | tail], func) do do_map_tco([func.(head) | acc], tail, func) end end TCO
  • 113. defmodule MyMap do def map_tco(list, function) do Enum.reverse do_map_tco([], list, function) end defp do_map_tco(acc, [], _function) do acc end defp do_map_tco(acc, [head | tail], func) do do_map_tco([func.(head) | acc], tail, func) end end TCO
  • 114. defmodule MyMap do def map_tco(list, function) do Enum.reverse do_map_tco([], list, function) end defp do_map_tco(acc, [], _function) do acc end defp do_map_tco(acc, [head | tail], func) do do_map_tco([func.(head) | acc], tail, func) end end TCO
  • 115. defmodule MyMap do def map_tco(list, function) do Enum.reverse do_map_tco([], list, function) end defp do_map_tco(acc, [], _function) do acc end defp do_map_tco(acc, [head | tail], func) do do_map_tco([func.(head) | acc], tail, func) end end TCO
  • 116. defmodule MyMap do def map_tco(list, function) do Enum.reverse do_map_tco([], list, function) end defp do_map_tco(acc, [], _function) do acc end defp do_map_tco(acc, [head | tail], func) do do_map_tco([func.(head) | acc], tail, func) end end TCO
  • 117. defmodule MyMap do def map_tco(list, function) do Enum.reverse do_map_tco([], list, function) end defp do_map_tco(acc, [], _function) do acc end defp do_map_tco(acc, [head | tail], func) do do_map_tco([func.(head) | acc], tail, func) end end TCO
  • 118. defmodule MyMap do def map_tco(list, function) do Enum.reverse do_map_tco([], list, function) end defp do_map_tco(acc, [], _function) do acc end defp do_map_tco(acc, [head | tail], func) do do_map_tco([func.(head) | acc], tail, func) end end TCO
  • 119. defmodule MyMap do def map_tco(list, function) do Enum.reverse do_map_tco(list, function, []) end defp do_map_tco([], _function, acc) do acc end defp do_map_tco([head | tail], func, acc) do do_map_tco(tail, func, [func.(head) | acc]) end end arg_order
  • 120. map_fun = fn(i) -> i + 1 end inputs = %{ "Small (10 Thousand)" => Enum.to_list(1..10_000), "Middle (100 Thousand)" => Enum.to_list(1..100_000), "Big (1 Million)" => Enum.to_list(1..1_000_000), "Bigger (5 Million)" => Enum.to_list(1..5_000_000) } Benchee.run %{ "tail-recursive" => fn(list) -> MyMap.map_tco(list, map_fun) end, "stdlib map" => fn(list) -> Enum.map(list, map_fun) end, "body-recursive" => fn(list) -> MyMap.map_body(list, map_fun) end, "tail-rec arg-order" => fn(list) -> MyMap.map_tco_arg_order(list, map_fun) end } TCO
  • 121. map_fun = fn(i) -> i + 1 end inputs = %{ "Small (10 Thousand)" => Enum.to_list(1..10_000), "Middle (100 Thousand)" => Enum.to_list(1..100_000), "Big (1 Million)" => Enum.to_list(1..1_000_000), "Bigger (5 Million)" => Enum.to_list(1..5_000_000) } Benchee.run %{ "tail-recursive" => fn(list) -> MyMap.map_tco(list, map_fun) end, "stdlib map" => fn(list) -> Enum.map(list, map_fun) end, "body-recursive" => fn(list) -> MyMap.map_body(list, map_fun) end, "tail-rec arg-order" => fn(list) -> MyMap.map_tco_arg_order(list, map_fun) end } TCO
  • 122. map_fun = fn(i) -> i + 1 end inputs = %{ "Small (10 Thousand)" => Enum.to_list(1..10_000), "Middle (100 Thousand)" => Enum.to_list(1..100_000), "Big (1 Million)" => Enum.to_list(1..1_000_000), "Bigger (5 Million)" => Enum.to_list(1..5_000_000) } Benchee.run %{ "tail-recursive" => fn(list) -> MyMap.map_tco(list, map_fun) end, "stdlib map" => fn(list) -> Enum.map(list, map_fun) end, "body-recursive" => fn(list) -> MyMap.map_body(list, map_fun) end, "tail-rec arg-order" => fn(list) -> MyMap.map_tco_arg_order(list, map_fun) end } TCO
  • 123. ##### With input Small (10 Thousand) ##### stdlib map 5.05 K body-recursive 5.00 K - 1.01x slower tail-rec arg-order 4.07 K - 1.24x slower tail-recursive 3.76 K - 1.34x slower ##### With input Middle (100 Thousand) ##### stdlib map 468.49 body-recursive 467.04 - 1.00x slower tail-rec arg-order 452.53 - 1.04x slower tail-recursive 417.20 - 1.12x slower ##### With input Big (1 Million) ##### body-recursive 40.33 stdlib map 38.89 - 1.04x slower tail-rec arg-order 37.69 - 1.07x slower tail-recursive 33.29 - 1.21x slower ##### With input Bigger (5 Million) ##### tail-rec arg-order 6.68 tail-recursive 6.35 - 1.05x slower stdlib map 5.60 - 1.19x slower body-recursive 5.39 - 1.24x slower TCO
  • 124. ##### With input Small (10 Thousand) ##### stdlib map 5.05 K body-recursive 5.00 K - 1.01x slower tail-rec arg-order 4.07 K - 1.24x slower tail-recursive 3.76 K - 1.34x slower ##### With input Middle (100 Thousand) ##### stdlib map 468.49 body-recursive 467.04 - 1.00x slower tail-rec arg-order 452.53 - 1.04x slower tail-recursive 417.20 - 1.12x slower ##### With input Big (1 Million) ##### body-recursive 40.33 stdlib map 38.89 - 1.04x slower tail-rec arg-order 37.69 - 1.07x slower tail-recursive 33.29 - 1.21x slower ##### With input Bigger (5 Million) ##### tail-rec arg-order 6.68 tail-recursive 6.35 - 1.05x slower stdlib map 5.60 - 1.19x slower body-recursive 5.39 - 1.24x slower TCO
  • 125. ##### With input Small (10 Thousand) ##### stdlib map 5.05 K body-recursive 5.00 K - 1.01x slower tail-rec arg-order 4.07 K - 1.24x slower tail-recursive 3.76 K - 1.34x slower ##### With input Middle (100 Thousand) ##### stdlib map 468.49 body-recursive 467.04 - 1.00x slower tail-rec arg-order 452.53 - 1.04x slower tail-recursive 417.20 - 1.12x slower ##### With input Big (1 Million) ##### body-recursive 40.33 stdlib map 38.89 - 1.04x slower tail-rec arg-order 37.69 - 1.07x slower tail-recursive 33.29 - 1.21x slower ##### With input Bigger (5 Million) ##### tail-rec arg-order 6.68 tail-recursive 6.35 - 1.05x slower stdlib map 5.60 - 1.19x slower body-recursive 5.39 - 1.24x slower Big Inputs
  • 126. ##### With input Small (10 Thousand) ##### stdlib map 5.05 K body-recursive 5.00 K - 1.01x slower tail-rec arg-order 4.07 K - 1.24x slower tail-recursive 3.76 K - 1.34x slower ##### With input Middle (100 Thousand) ##### stdlib map 468.49 body-recursive 467.04 - 1.00x slower tail-rec arg-order 452.53 - 1.04x slower tail-recursive 417.20 - 1.12x slower ##### With input Big (1 Million) ##### body-recursive 40.33 stdlib map 38.89 - 1.04x slower tail-rec arg-order 37.69 - 1.07x slower tail-recursive 33.29 - 1.21x slower ##### With input Bigger (5 Million) ##### tail-rec arg-order 6.68 tail-recursive 6.35 - 1.05x slower stdlib map 5.60 - 1.19x slower body-recursive 5.39 - 1.24x slower Order matters!
  • 127. TCO
  • 129. WIP
  • 130. ##### With input Small (10 Thousand) ##### stdlib map 156.85 KB body-recursive 156.85 KB - 1.00x memory usage tail-rec arg-order 291.46 KB - 1.86x memory usage tail-recursive 291.46 KB - 1.86x memory usage ##### With input Middle (100 Thousand) ##### stdlib map 1.53 MB body-recursive 1.53 MB - 1.00x memory usage tail-rec arg-order 1.80 MB - 1.18x memory usage tail-recursive 1.80 MB - 1.18x memory usage ##### With input Big (1 Million) ##### stdlib map 15.26 MB body-recursive 15.26 MB - 1.00x memory usage tail-rec arg-order 28.74 MB - 1.88x memory usage tail-recursive 28.74 MB - 1.88x memory usage ##### With input Bigger (5 Million) ##### tail-rec arg-order 150.15 MB tail-recursive 150.15 MB - 1.00x memory usage stdlib map 76.30 MB - 0.51x memory usage body-recursive 76.30 MB - 0.51x memory usage Memory
  • 131. ##### With input Small (10 Thousand) ##### stdlib map 156.85 KB body-recursive 156.85 KB - 1.00x memory usage tail-rec arg-order 291.46 KB - 1.86x memory usage tail-recursive 291.46 KB - 1.86x memory usage ##### With input Middle (100 Thousand) ##### stdlib map 1.53 MB body-recursive 1.53 MB - 1.00x memory usage tail-rec arg-order 1.80 MB - 1.18x memory usage tail-recursive 1.80 MB - 1.18x memory usage ##### With input Big (1 Million) ##### stdlib map 15.26 MB body-recursive 15.26 MB - 1.00x memory usage tail-rec arg-order 28.74 MB - 1.88x memory usage tail-recursive 28.74 MB - 1.88x memory usage ##### With input Bigger (5 Million) ##### tail-rec arg-order 150.15 MB tail-recursive 150.15 MB - 1.00x memory usage stdlib map 76.30 MB - 0.51x memory usage body-recursive 76.30 MB - 0.51x memory usage Memory
  • 132. What even is a benchmark?
  • 133. config |> Benchee.init |> Benchee.system |> Benchee.benchmark("job", fn -> magic end) |> Benchee.measure |> Benchee.statistics |> Benchee.Formatters.Console.output |> Benchee.Formatters.HTML.output A transformation of inputs
  • 135. Enjoy Benchmarking ❤ Tobias Pfeiffer @PragTob pragtob.info github.com/PragTob/benchee
  • 137. average = total_time / iterations Average
  • 138. defp standard_deviation(samples, average, iterations) do total_variance = Enum.reduce samples, 0, fn(sample, total) -> total + :math.pow((sample - average), 2) end variance = total_variance / iterations :math.sqrt variance end Standard Deviation
  • 139. defp standard_deviation(samples, average, iterations) do total_variance = Enum.reduce samples, 0, fn(sample, total) -> total + :math.pow((sample - average), 2) end variance = total_variance / iterations :math.sqrt variance end Spread of Values
  • 144. defp compute_median(run_times, iterations) do sorted = Enum.sort(run_times) middle = div(iterations, 2) if Integer.is_odd(iterations) do sorted |> Enum.at(middle) |> to_float else (Enum.at(sorted, middle) + Enum.at(sorted, middle - 1)) / 2 end end Median
  • 146. Mediam