Benchmarking Elixir Argument Call Order

I was just curious how the recent OTP versions changed argument processing speed, it used to be that changing the ‘last’ argument but keeping the early ones the same was the most efficient but it seems indications are a bit more… odd now, so a quick benchmark!

First, changing all the arguments:

iex(1)> defmodule Testering do
...(1)>   def recurse1([]=l), do: l
...(1)>   def recurse1([_h|t]), do: recurse1(t)
...(1)> 
...(1)>   def recurse2f([], acc), do: acc
...(1)>   def recurse2f([h|t], acc), do: recurse2f(t, acc+h)
...(1)> 
...(1)>   def recurse2b(acc, []), do: acc
...(1)>   def recurse2b(acc, [h|t]), do: recurse2b(acc+h, t)
...(1)> 
...(1)>   def recurse5f([], a0, a1, a2, a3), do: {a0, a1, a2, a3}
...(1)>   def recurse5f([h|t], a0, a1, a2, a3), do: recurse5f(t, a0+h, a1+h+1, a2+h+2, a3+h+3)
...(1)> 
...(1)>   def recurse5b(a0, a1, a2, a3, []), do: {a0, a1, a2, a3}
...(1)>   def recurse5b(a0, a1, a2, a3, [h|t]), do: recurse5b(a0+h, a1+h+1, a2+h+2, a3+h+3, t)
...(1)> 
...(1)>   def recurse9f([], a0, a1, a2, a3, a4, a5, a6, a7), do: {a0, a1, a2, a3, a4, a5, a6, a7}
...(1)>   def recurse9f([h|t], a0, a1, a2, a3, a4, a5, a6, a7), do: recurse9f(t, a0+h, a1+h+1, a2+h+2, a3+h+3, a4+h+4, a5+h+5, a6+h+6, a7+h+7)
...(1)> 
...(1)>   def recurse9b(a0, a1, a2, a3, a4, a5, a6, a7, []), do: {a0, a1, a2, a3, a4, a5, a6, a7}
...(1)>   def recurse9b(a0, a1, a2, a3, a4, a5, a6, a7, [h|t]), do: recurse9b(a0+h, a1+h+1, a2+h+2, a3+h+3, a4+h+4, a5+h+5, a6+h+6, a7+h+7, t)
...(1)> end
{:module, Testering,
 <<70, 79, 82, 49, 0, 0, 11, 12, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 194,                                                       
   0, 0, 0, 20, 16, 69, 108, 105, 120, 105, 114, 46, 84, 101, 115, 116, 101,                                                          
   114, 105, 110, 103, 8, 95, 95, 105, 110, 102, ...>>, {:recurse9b, 9}}
iex(2)> 
nil
iex(3)> inputs = %{
...(3)>   "10" => :lists.seq(1, 10),
...(3)>   "1000" => :lists.seq(1, 1000),
...(3)>   "100000" => :lists.seq(1, 100000),
...(3)> }
%{
  "10" => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
  "1000" => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
   20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
   39, 40, 41, 42, 43, 44, 45, 46, 47, 48, ...],                                                                                      
  "100000" => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
   19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
   38, 39, 40, 41, 42, 43, 44, 45, 46, 47, ...]
}
iex(4)> 
nil
iex(5)> actions = %{
...(5)>   "recurse1" => &Testering.recurse1(&1),
...(5)>   "recurse2f" => &Testering.recurse2f(&1, 0),
...(5)>   "recurse2b" => &Testering.recurse2b(0, &1),
...(5)>   "recurse5f" => &Testering.recurse5f(&1, 0, 1, 2, 3),
...(5)>   "recurse5b" => &Testering.recurse5b(0, 1, 2, 3, &1),
...(5)>   "recurse9f" => &Testering.recurse9f(&1, 0, 1, 2, 3, 4, 5, 6, 7),
...(5)>   "recurse9b" => &Testering.recurse9b(0, 1, 2, 3, 4, 5, 6, 7, &1),
...(5)> }
%{
  "recurse1" => &Testering.recurse1/1,
  "recurse2b" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse2f" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse5b" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse5f" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse9b" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse9f" => #Function<6.128620087/1 in :erl_eval.expr/5>                                                                         
}
iex(6)> 
nil
iex(7)> results = Benchee.run(actions, inputs: inputs, time: 5, warmup: 5, print: %{fast_warning: false}); :ok
Operating System: Linux
CPU Information: Blah
Number of Available Cores: 6
Available memory: 16.430148 GB
Elixir 1.7.4
Erlang 21.1.1
Benchmark suite executing with the following configuration:
warmup: 5.00 s
time: 5.00 s
parallel: 1
inputs: 10, 1000, 100000
Estimated total run time: 3.50 min



Benchmarking with input 10:
Benchmarking recurse1...
Benchmarking recurse2b...
Benchmarking recurse2f...
Benchmarking recurse5b...
Benchmarking recurse5f...
Benchmarking recurse9b...
Benchmarking recurse9f...

Benchmarking with input 1000:
Benchmarking recurse1...
Benchmarking recurse2b...
Benchmarking recurse2f...
Benchmarking recurse5b...
Benchmarking recurse5f...
Benchmarking recurse9b...
Benchmarking recurse9f...

Benchmarking with input 100000:
Benchmarking recurse1...
Benchmarking recurse2b...
Benchmarking recurse2f...
Benchmarking recurse5b...
Benchmarking recurse5f...
Benchmarking recurse9b...
Benchmarking recurse9f...

Result:

##### With input 10 #####
Name                ips        average  deviation         median
recurse1     10508.51 K      0.0952 μs   ±165.42%      0.0900 μs
recurse2b      394.27 K        2.54 μs  ±1538.40%        2.00 μs
recurse2f      393.46 K        2.54 μs  ±1504.22%        2.00 μs
recurse5b      240.24 K        4.16 μs   ±784.57%        4.00 μs
recurse5f      219.89 K        4.55 μs   ±977.01%        4.00 μs
recurse9f      174.10 K        5.74 μs   ±638.64%        5.00 μs
recurse9b      167.04 K        5.99 μs   ±726.56%        5.00 μs

Comparison: 
recurse1     10508.51 K
recurse2b      394.27 K - 26.65x slower
recurse2f      393.46 K - 26.71x slower
recurse5b      240.24 K - 43.74x slower
recurse5f      219.89 K - 47.79x slower
recurse9f      174.10 K - 60.36x slower
recurse9b      167.04 K - 62.91x slower

##### With input 1000 #####
Name                ips        average  deviation         median
recurse1       203.42 K        4.92 μs    ±11.57%        4.80 μs
recurse2f       92.04 K       10.86 μs   ±178.86%       10.00 μs
recurse2b       78.58 K       12.73 μs   ±190.28%       12.00 μs
recurse5b       20.49 K       48.79 μs    ±16.04%       48.00 μs
recurse5f       17.35 K       57.65 μs     ±8.96%       56.00 μs
recurse9b       12.76 K       78.39 μs     ±7.43%       77.00 μs
recurse9f       11.16 K       89.63 μs     ±6.63%       88.00 μs

Comparison: 
recurse1       203.42 K
recurse2f       92.04 K - 2.21x slower
recurse2b       78.58 K - 2.59x slower
recurse5b       20.49 K - 9.93x slower
recurse5f       17.35 K - 11.73x slower
recurse9b       12.76 K - 15.95x slower
recurse9f       11.16 K - 18.23x slower

##### With input 100000 #####
Name                ips        average  deviation         median
recurse1        1909.38        0.52 ms     ±6.17%        0.51 ms
recurse2f       1125.82        0.89 ms    ±12.92%        0.87 ms
recurse2b        908.18        1.10 ms    ±11.61%        1.08 ms
recurse5b        212.14        4.71 ms     ±5.44%        4.65 ms
recurse5f        182.85        5.47 ms     ±5.60%        5.40 ms
recurse9b        130.85        7.64 ms     ±7.98%        7.55 ms
recurse9f        113.08        8.84 ms     ±5.17%        8.75 ms

Comparison: 
recurse1        1909.38
recurse2f       1125.82 - 1.70x slower
recurse2b        908.18 - 2.10x slower
recurse5b        212.14 - 9.00x slower
recurse5f        182.85 - 10.44x slower
recurse9b        130.85 - 14.59x slower
recurse9f        113.08 - 16.88x slower

So a baseline, now testing with only mutating the front or back of the arguments only:

iex(1)> defmodule Testering do
...(1)>   def recurse1([]=l), do: l
...(1)>   def recurse1([_h|t]), do: recurse1(t)
...(1)> 
...(1)>   def recurse2f([], acc), do: acc
...(1)>   def recurse2f([h|t], acc), do: recurse2f(t, acc+h)
...(1)> 
...(1)>   def recurse2fn([], acc), do: acc
...(1)>   def recurse2fn([h|t], acc), do: recurse2fn(t, acc)
...(1)> 
...(1)>   def recurse2b(acc, []), do: acc
...(1)>   def recurse2b(acc, [h|t]), do: recurse2b(acc+h, t)
...(1)> 
...(1)>   def recurse2bn(acc, []), do: acc
...(1)>   def recurse2bn(acc, [h|t]), do: recurse2bn(acc, t)
...(1)> 
...(1)>   def recurse5f([], a0, a1, a2, a3), do: {a0, a1, a2, a3}
...(1)>   def recurse5f([h|t], a0, a1, a2, a3), do: recurse5f(t, a0+h, a1, a2, a3)
...(1)> 
...(1)>   def recurse5b(a0, a1, a2, a3, []), do: {a0, a1, a2, a3}
...(1)>   def recurse5b(a0, a1, a2, a3, [h|t]), do: recurse5b(a0, a1, a2, a3+h, t)
...(1)> 
...(1)>   def recurse5m(a0, a1, a2, a3, []), do: {a0, a1, a2, a3}
...(1)>   def recurse5m(a0, a1, a2, a3, [h|t]), do: recurse5m(a0+h, a1, a2, a3, t)
...(1)> 
...(1)>   def recurse9f([], a0, a1, a2, a3, a4, a5, a6, a7), do: {a0, a1, a2, a3, a4, a5, a6, a7}
...(1)>   def recurse9f([h|t], a0, a1, a2, a3, a4, a5, a6, a7), do: recurse9f(t, a0+h, a1, a2, a3, a4, a5, a6, a7)
...(1)> 
...(1)>   def recurse9b(a0, a1, a2, a3, a4, a5, a6, a7, []), do: {a0, a1, a2, a3, a4, a5, a6, a7}
...(1)>   def recurse9b(a0, a1, a2, a3, a4, a5, a6, a7, [h|t]), do: recurse9b(a0, a1, a2, a3, a4, a5, a6, a7+h, t)
...(1)> 
...(1)>   def recurse9m(a0, a1, a2, a3, a4, a5, a6, a7, []), do: {a0, a1, a2, a3, a4, a5, a6, a7}
...(1)>   def recurse9m(a0, a1, a2, a3, a4, a5, a6, a7, [h|t]), do: recurse9m(a0+h, a1, a2, a3, a4, a5, a6, a7, t)
...(1)> end
warning: variable "h" is unused
  iex:9

warning: variable "h" is unused
  iex:15

{:module, Testering,
 <<70, 79, 82, 49, 0, 0, 11, 60, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 236,                                                       
   0, 0, 0, 24, 16, 69, 108, 105, 120, 105, 114, 46, 84, 101, 115, 116, 101,                                                          
   114, 105, 110, 103, 8, 95, 95, 105, 110, 102, ...>>, {:recurse9m, 9}}
iex(2)> 
nil
iex(3)> inputs = %{
...(3)>   "10" => :lists.seq(1, 10),
...(3)>   "1000" => :lists.seq(1, 1000),
...(3)>   "100000" => :lists.seq(1, 100000),
...(3)>   "10000000" => :lists.seq(1, 10000000),
...(3)> }
%{
  "10" => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
  "1000" => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
   20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
   39, 40, 41, 42, 43, 44, 45, 46, 47, 48, ...],                                                                                      
  "100000" => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
   19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
   38, 39, 40, 41, 42, 43, 44, 45, 46, 47, ...]
}
iex(4)> 
nil
iex(5)> actions = %{
...(5)>   "recurse1" => &Testering.recurse1(&1),
...(5)>   "recurse2f" => &Testering.recurse2f(&1, 0),
...(5)>   "recurse2fn" => &Testering.recurse2fn(&1, 0),
...(5)>   "recurse2b" => &Testering.recurse2b(0, &1),
...(5)>   "recurse2bn" => &Testering.recurse2bn(0, &1),
...(5)>   "recurse5f" => &Testering.recurse5f(&1, 0, 1, 2, 3),
...(5)>   "recurse5b" => &Testering.recurse5b(0, 1, 2, 3, &1),
...(5)>   "recurse5m" => &Testering.recurse5m(0, 1, 2, 3, &1),
...(5)>   "recurse9f" => &Testering.recurse9f(&1, 0, 1, 2, 3, 4, 5, 6, 7),
...(5)>   "recurse9b" => &Testering.recurse9b(0, 1, 2, 3, 4, 5, 6, 7, &1),
...(5)>   "recurse9m" => &Testering.recurse9m(0, 1, 2, 3, 4, 5, 6, 7, &1),
...(5)> }
%{
  "recurse1" => &Testering.recurse1/1,
  "recurse2b" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse2bn" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse2f" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse2fn" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse5b" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse5f" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse5m" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse9b" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse9f" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse9m" => #Function<6.128620087/1 in :erl_eval.expr/5>
}
iex(6)> 
nil
iex(7)> results = Benchee.run(actions, inputs: inputs, time: 5, warmup: 5, print: %{fast_warning: false}); :ok
Operating System: Linux
CPU Information: Blah
Number of Available Cores: 6
Available memory: 16.430148 GB
Elixir 1.7.4
Erlang 21.1.1
Benchmark suite executing with the following configuration:
warmup: 5.00 s
time: 5.00 s
parallel: 1
inputs: 10, 1000, 100000
Estimated total run time: 5.50 min



Benchmarking with input 10:
Benchmarking recurse1...
Benchmarking recurse2b...
Benchmarking recurse2bn...
Benchmarking recurse2f...
Benchmarking recurse2fn...
Benchmarking recurse5b...
Benchmarking recurse5f...
Benchmarking recurse5m...
Benchmarking recurse9b...
Benchmarking recurse9f...
Benchmarking recurse9m...

Benchmarking with input 1000:
Benchmarking recurse1...
Benchmarking recurse2b...
Benchmarking recurse2bn...
Benchmarking recurse2f...
Benchmarking recurse2fn...
Benchmarking recurse5b...
Benchmarking recurse5f...
Benchmarking recurse5m...
Benchmarking recurse9b...
Benchmarking recurse9f...
Benchmarking recurse9m...

Benchmarking with input 100000:
Benchmarking recurse1...
Benchmarking recurse2b...
Benchmarking recurse2bn...
Benchmarking recurse2f...
Benchmarking recurse2fn...
Benchmarking recurse5b...
Benchmarking recurse5f...
Benchmarking recurse5m...
Benchmarking recurse9b...
Benchmarking recurse9f...
Benchmarking recurse9m...

Benchmarking with input 10000000:
Benchmarking recurse1...
Benchmarking recurse2b...
Benchmarking recurse2bn...
Benchmarking recurse2f...
Benchmarking recurse2fn...
Benchmarking recurse5b...
Benchmarking recurse5f...
Benchmarking recurse5m...
Benchmarking recurse9b...
Benchmarking recurse9f...
Benchmarking recurse9m...

And results:


##### With input 10 #####
Name                 ips        average  deviation         median
recurse1      10576.33 K      0.0946 μs   ±153.38%      0.0900 μs
recurse2b       392.87 K        2.55 μs  ±1634.44%        2.00 μs
recurse2bn      390.07 K        2.56 μs  ±1648.19%        2.00 μs
recurse2f       389.76 K        2.57 μs  ±1658.86%        2.00 μs
recurse2fn      384.92 K        2.60 μs  ±1781.32%        2.00 μs
recurse5f       269.04 K        3.72 μs  ±1206.63%        3.00 μs
recurse5m       267.45 K        3.74 μs  ±1176.88%        3.00 μs
recurse5b       263.16 K        3.80 μs   ±925.19%        3.00 μs
recurse9f       204.08 K        4.90 μs   ±689.09%        4.00 μs
recurse9b       200.81 K        4.98 μs   ±848.10%        4.00 μs
recurse9m       194.60 K        5.14 μs   ±768.05%        4.00 μs

Comparison: 
recurse1      10576.33 K
recurse2b       392.87 K - 26.92x slower
recurse2bn      390.07 K - 27.11x slower
recurse2f       389.76 K - 27.14x slower
recurse2fn      384.92 K - 27.48x slower
recurse5f       269.04 K - 39.31x slower
recurse5m       267.45 K - 39.55x slower
recurse5b       263.16 K - 40.19x slower
recurse9f       204.08 K - 51.82x slower
recurse9b       200.81 K - 52.67x slower
recurse9m       194.60 K - 54.35x slower

##### With input 1000 #####
Name                 ips        average  deviation         median
recurse1        225.96 K        4.43 μs    ±20.29%        4.00 μs
recurse2bn      134.61 K        7.43 μs   ±416.65%        7.00 μs
recurse2fn      133.89 K        7.47 μs   ±437.14%        7.00 μs
recurse2f        94.55 K       10.58 μs   ±287.21%       10.00 μs
recurse5f        84.26 K       11.87 μs   ±296.58%       11.00 μs
recurse2b        79.13 K       12.64 μs   ±181.35%       12.00 μs
recurse9f        75.06 K       13.32 μs   ±223.36%       13.00 μs
recurse5m        73.19 K       13.66 μs   ±141.63%       13.00 μs
recurse5b        71.95 K       13.90 μs   ±169.30%       13.00 μs
recurse9b        66.46 K       15.05 μs   ±175.73%       14.00 μs
recurse9m        65.93 K       15.17 μs   ±199.94%       14.00 μs

Comparison: 
recurse1        225.96 K
recurse2bn      134.61 K - 1.68x slower
recurse2fn      133.89 K - 1.69x slower
recurse2f        94.55 K - 2.39x slower
recurse5f        84.26 K - 2.68x slower
recurse2b        79.13 K - 2.86x slower
recurse9f        75.06 K - 3.01x slower
recurse5m        73.19 K - 3.09x slower
recurse5b        71.95 K - 3.14x slower
recurse9b        66.46 K - 3.40x slower
recurse9m        65.93 K - 3.43x slower

##### With input 100000 #####
Name                 ips        average  deviation         median
recurse2fn        1.95 K      512.46 μs     ±9.66%      503.00 μs
recurse2bn        1.92 K      521.48 μs     ±9.22%      509.00 μs
recurse1          1.89 K      530.38 μs     ±6.23%      507.00 μs
recurse2f         1.12 K      891.94 μs     ±7.84%      873.00 μs
recurse9f         1.11 K      897.43 μs     ±5.22%      876.00 μs
recurse5f         1.09 K      915.82 μs     ±8.50%      887.00 μs
recurse9m         0.93 K     1079.86 μs     ±6.91%     1063.00 μs
recurse5b         0.92 K     1083.50 μs    ±11.10%     1063.00 μs
recurse9b         0.92 K     1092.23 μs     ±6.83%     1066.00 μs
recurse2b         0.90 K     1105.06 μs     ±5.81%     1076.00 μs
recurse5m         0.90 K     1109.84 μs    ±12.90%     1074.00 μs

Comparison: 
recurse2fn        1.95 K
recurse2bn        1.92 K - 1.02x slower
recurse1          1.89 K - 1.03x slower
recurse2f         1.12 K - 1.74x slower
recurse9f         1.11 K - 1.75x slower
recurse5f         1.09 K - 1.79x slower
recurse9m         0.93 K - 2.11x slower
recurse5b         0.92 K - 2.11x slower
recurse9b         0.92 K - 2.13x slower
recurse2b         0.90 K - 2.16x slower
recurse5m         0.90 K - 2.17x slower
##### With input 10000000 #####
Name                 ips        average  deviation         median
recurse2fn         17.23       58.03 ms     ±1.68%       57.72 ms
recurse1           16.85       59.35 ms     ±2.72%       58.84 ms
recurse2bn         15.02       66.58 ms     ±2.62%       65.96 ms
recurse2f          10.44       95.80 ms     ±2.48%       94.71 ms
recurse5f          10.37       96.42 ms     ±2.22%       95.46 ms
recurse9f           9.50      105.24 ms     ±1.88%      104.57 ms
recurse5m           8.72      114.63 ms     ±1.65%      114.20 ms
recurse9b           8.71      114.80 ms     ±2.10%      114.03 ms
recurse5b           8.70      114.94 ms     ±1.99%      114.11 ms
recurse9m           8.69      115.09 ms     ±1.49%      114.54 ms
recurse2b           8.62      115.96 ms     ±1.85%      115.10 ms

Comparison: 
recurse2fn         17.23
recurse1           16.85 - 1.02x slower
recurse2bn         15.02 - 1.15x slower
recurse2f          10.44 - 1.65x slower
recurse5f          10.37 - 1.66x slower
recurse9f           9.50 - 1.81x slower
recurse5m           8.72 - 1.98x slower
recurse9b           8.71 - 1.98x slower
recurse5b           8.70 - 1.98x slower
recurse9m           8.69 - 1.98x slower
recurse2b           8.62 - 2.00x slower

So interestingly it seems mutating the front arguments is faster now! That’s entirely backwards from what I remembered testing back in the old OTP-14 or so days! Quite interesting. :slight_smile:

So what about non-TCO:

iex(1)> defmodule Testering do
...(1)>   def recurse1([]=l), do: 0
...(1)>   def recurse1([_h|t]), do: 1+recurse1(t)
...(1)> 
...(1)>   def recurse2f([], acc), do: acc
...(1)>   def recurse2f([h|t], acc), do: 1+recurse2f(t, acc+h)
...(1)> 
...(1)>   def recurse2fn([], acc), do: acc
...(1)>   def recurse2fn([h|t], acc), do: 1+recurse2fn(t, acc)
...(1)> 
...(1)>   def recurse2b(acc, []), do: acc
...(1)>   def recurse2b(acc, [h|t]), do: 1+recurse2b(acc+h, t)
...(1)> 
...(1)>   def recurse2bn(acc, []), do: acc
...(1)>   def recurse2bn(acc, [h|t]), do: 1+recurse2bn(acc, t)
...(1)> 
...(1)>   def recurse5f([], a0, a1, a2, a3), do: a0
...(1)>   def recurse5f([h|t], a0, a1, a2, a3), do: 1+recurse5f(t, a0+h, a1, a2, a3)
...(1)> 
...(1)>   def recurse5b(a0, a1, a2, a3, []), do: a3
...(1)>   def recurse5b(a0, a1, a2, a3, [h|t]), do: 1+recurse5b(a0, a1, a2, a3+h, t)
...(1)> 
...(1)>   def recurse5m(a0, a1, a2, a3, []), do: a0
...(1)>   def recurse5m(a0, a1, a2, a3, [h|t]), do: 1+recurse5m(a0+h, a1, a2, a3, t)
...(1)> 
...(1)>   def recurse9f([], a0, a1, a2, a3, a4, a5, a6, a7), do: a0
...(1)>   def recurse9f([h|t], a0, a1, a2, a3, a4, a5, a6, a7), do: 1+recurse9f(t, a0+h, a1, a2, a3, a4, a5, a6, a7)
...(1)> 
...(1)>   def recurse9b(a0, a1, a2, a3, a4, a5, a6, a7, []), do: a7
...(1)>   def recurse9b(a0, a1, a2, a3, a4, a5, a6, a7, [h|t]), do: 1+recurse9b(a0, a1, a2, a3, a4, a5, a6, a7+h, t)
...(1)> 
...(1)>   def recurse9m(a0, a1, a2, a3, a4, a5, a6, a7, []), do: a0
...(1)>   def recurse9m(a0, a1, a2, a3, a4, a5, a6, a7, [h|t]), do: 1+recurse9m(a0+h, a1, a2, a3, a4, a5, a6, a7, t)
...(1)> end
warning: variable "l" is unused
  iex:2

warning: variable "h" is unused
  iex:9

warning: variable "h" is unused
  iex:15

warning: variable "a1" is unused
  iex:17

warning: variable "a2" is unused
  iex:17

warning: variable "a3" is unused
  iex:17

warning: variable "a0" is unused
  iex:20

warning: variable "a1" is unused
  iex:20

warning: variable "a2" is unused
  iex:20

warning: variable "a1" is unused
  iex:23

warning: variable "a2" is unused
  iex:23

warning: variable "a3" is unused
  iex:23

warning: variable "a1" is unused
  iex:26

warning: variable "a2" is unused
  iex:26

warning: variable "a3" is unused
  iex:26

warning: variable "a4" is unused
  iex:26

warning: variable "a5" is unused
  iex:26

warning: variable "a6" is unused
  iex:26

warning: variable "a7" is unused
  iex:26

warning: variable "a0" is unused
  iex:29

warning: variable "a1" is unused
  iex:29

warning: variable "a2" is unused
  iex:29

warning: variable "a3" is unused
  iex:29

warning: variable "a4" is unused
  iex:29

warning: variable "a5" is unused
  iex:29

warning: variable "a6" is unused
  iex:29

warning: variable "a1" is unused
  iex:32

warning: variable "a2" is unused
  iex:32

warning: variable "a3" is unused
  iex:32

warning: variable "a4" is unused
  iex:32

warning: variable "a5" is unused
  iex:32

warning: variable "a6" is unused
  iex:32

warning: variable "a7" is unused
  iex:32

{:module, Testering,
 <<70, 79, 82, 49, 0, 0, 11, 136, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 236,                                                      
   0, 0, 0, 24, 16, 69, 108, 105, 120, 105, 114, 46, 84, 101, 115, 116, 101,                                                          
   114, 105, 110, 103, 8, 95, 95, 105, 110, 102, ...>>, {:recurse9m, 9}}
iex(2)> 
nil
iex(3)> inputs = %{
...(3)>   "10" => :lists.seq(1, 10),
...(3)>   "1000" => :lists.seq(1, 1000),
...(3)>   "100000" => :lists.seq(1, 100000),
...(3)>   "10000000" => :lists.seq(1, 10000000),
...(3)> }
%{
  "10" => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
  "1000" => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
   20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
   39, 40, 41, 42, 43, 44, 45, 46, 47, 48, ...],                                                                                      
  "100000" => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
   19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
   38, 39, 40, 41, 42, 43, 44, 45, 46, 47, ...],
  "10000000" => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
   19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
   38, 39, 40, 41, 42, 43, 44, 45, 46, ...]
}
iex(4)> 
nil
iex(5)> actions = %{
...(5)>   "recurse1" => &Testering.recurse1(&1),
...(5)>   "recurse2f" => &Testering.recurse2f(&1, 0),
...(5)>   "recurse2fn" => &Testering.recurse2fn(&1, 0),
...(5)>   "recurse2b" => &Testering.recurse2b(0, &1),
...(5)>   "recurse2bn" => &Testering.recurse2bn(0, &1),
...(5)>   "recurse5f" => &Testering.recurse5f(&1, 0, 1, 2, 3),
...(5)>   "recurse5b" => &Testering.recurse5b(0, 1, 2, 3, &1),
...(5)>   "recurse5m" => &Testering.recurse5m(0, 1, 2, 3, &1),
...(5)>   "recurse9f" => &Testering.recurse9f(&1, 0, 1, 2, 3, 4, 5, 6, 7),
...(5)>   "recurse9b" => &Testering.recurse9b(0, 1, 2, 3, 4, 5, 6, 7, &1),
...(5)>   "recurse9m" => &Testering.recurse9m(0, 1, 2, 3, 4, 5, 6, 7, &1),
...(5)> }
%{
  "recurse1" => &Testering.recurse1/1,
  "recurse2b" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse2bn" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse2f" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse2fn" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse5b" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse5f" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse5m" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse9b" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse9f" => #Function<6.128620087/1 in :erl_eval.expr/5>,
  "recurse9m" => #Function<6.128620087/1 in :erl_eval.expr/5>                                                                         
}
iex(6)> 
nil
iex(7)> results = Benchee.run(actions, inputs: inputs, time: 5, warmup: 5, print: %{fast_warning: false}); :ok
Operating System: Linux
CPU Information: Blah
Number of Available Cores: 6
Available memory: 16.430148 GB
Elixir 1.7.4
Erlang 21.1.1
Benchmark suite executing with the following configuration:
warmup: 5.00 s
time: 5.00 s
parallel: 1
inputs: 10, 1000, 100000, 10000000
Estimated total run time: 7.33 min



Benchmarking with input 10:
Benchmarking recurse1...
Benchmarking recurse2b...
Benchmarking recurse2bn...
Benchmarking recurse2f...
Benchmarking recurse2fn...
Benchmarking recurse5b...
Benchmarking recurse5f...
Benchmarking recurse5m...
Benchmarking recurse9b...
Benchmarking recurse9f...
Benchmarking recurse9m...

Benchmarking with input 1000:
Benchmarking recurse1...
Benchmarking recurse2b...
Benchmarking recurse2bn...
Benchmarking recurse2f...
Benchmarking recurse2fn...
Benchmarking recurse5b...
Benchmarking recurse5f...
Benchmarking recurse5m...
Benchmarking recurse9b...
Benchmarking recurse9f...
Benchmarking recurse9m...

Benchmarking with input 100000:
Benchmarking recurse1...
Benchmarking recurse2b...
Benchmarking recurse2bn...
Benchmarking recurse2f...
Benchmarking recurse2fn...
Benchmarking recurse5b...
Benchmarking recurse5f...
Benchmarking recurse5m...
Benchmarking recurse9b...
Benchmarking recurse9f...
Benchmarking recurse9m...

Benchmarking with input 10000000:
Benchmarking recurse1...
Benchmarking recurse2b...
Benchmarking recurse2bn...
Benchmarking recurse2f...
Benchmarking recurse2fn...
Benchmarking recurse5b...
Benchmarking recurse5f...
Benchmarking recurse5m...
Benchmarking recurse9b...
Benchmarking recurse9f...
Benchmarking recurse9m...

Results:

##### With input 10 #####
Name                 ips        average  deviation         median
recurse1       5884.23 K       0.170 μs   ±217.91%       0.160 μs
recurse2bn      395.73 K        2.53 μs  ±1552.52%        2.00 μs
recurse2f       395.55 K        2.53 μs  ±1662.25%        2.00 μs
recurse2fn      391.58 K        2.55 μs  ±1703.19%        2.00 μs
recurse2b       391.20 K        2.56 μs  ±1664.04%        2.00 μs
recurse5m       267.55 K        3.74 μs  ±1022.72%        3.00 μs
recurse5b       264.32 K        3.78 μs  ±1237.89%        3.00 μs
recurse5f       258.29 K        3.87 μs   ±892.53%        3.00 μs
recurse9f       204.89 K        4.88 μs   ±716.82%        4.00 μs
recurse9m       203.65 K        4.91 μs   ±714.30%        4.00 μs
recurse9b       203.62 K        4.91 μs   ±713.38%        4.00 μs

Comparison: 
recurse1       5884.23 K
recurse2bn      395.73 K - 14.87x slower
recurse2f       395.55 K - 14.88x slower
recurse2fn      391.58 K - 15.03x slower
recurse2b       391.20 K - 15.04x slower
recurse5m       267.55 K - 21.99x slower
recurse5b       264.32 K - 22.26x slower
recurse5f       258.29 K - 22.78x slower
recurse9f       204.89 K - 28.72x slower
recurse9m       203.65 K - 28.89x slower
recurse9b       203.62 K - 28.90x slower

##### With input 1000 #####
Name                 ips        average  deviation         median
recurse1         97.01 K       10.31 μs   ±118.39%       10.00 μs
recurse2fn       76.58 K       13.06 μs   ±182.34%       13.00 μs
recurse2bn       73.59 K       13.59 μs   ±218.43%       13.00 μs
recurse2f        58.18 K       17.19 μs   ±216.01%       17.00 μs
recurse5f        54.10 K       18.49 μs    ±57.76%       18.00 μs
recurse9f        52.07 K       19.21 μs    ±57.79%       19.00 μs
recurse2b        49.76 K       20.10 μs    ±52.32%       20.00 μs
recurse5b        48.40 K       20.66 μs    ±24.64%       20.00 μs
recurse5m        48.20 K       20.75 μs    ±69.18%       20.00 μs
recurse9b        44.33 K       22.56 μs    ±92.41%       22.00 μs
recurse9m        44.29 K       22.58 μs    ±60.78%       22.00 μs

Comparison: 
recurse1         97.01 K
recurse2fn       76.58 K - 1.27x slower
recurse2bn       73.59 K - 1.32x slower
recurse2f        58.18 K - 1.67x slower
recurse5f        54.10 K - 1.79x slower
recurse9f        52.07 K - 1.86x slower
recurse2b        49.76 K - 1.95x slower
recurse5b        48.40 K - 2.00x slower
recurse5m        48.20 K - 2.01x slower
recurse9b        44.33 K - 2.19x slower
recurse9m        44.29 K - 2.19x slower

##### With input 100000 #####
Name                 ips        average  deviation         median
recurse2fn        895.55        1.12 ms    ±15.37%        1.10 ms
recurse2bn        805.73        1.24 ms    ±27.33%        1.17 ms
recurse1          738.43        1.35 ms    ±44.65%        1.10 ms
recurse2f         647.77        1.54 ms     ±9.69%        1.51 ms
recurse5f         617.39        1.62 ms    ±11.58%        1.58 ms
recurse9f         580.50        1.72 ms    ±42.02%        1.55 ms
recurse9m         543.77        1.84 ms    ±13.20%        1.79 ms
recurse5b         541.94        1.85 ms    ±13.17%        1.81 ms
recurse5m         540.25        1.85 ms     ±7.65%        1.82 ms
recurse2b         475.47        2.10 ms    ±36.08%        1.81 ms
recurse9b         398.33        2.51 ms    ±60.55%        1.86 ms

Comparison: 
recurse2fn        895.55
recurse2bn        805.73 - 1.11x slower
recurse1          738.43 - 1.21x slower
recurse2f         647.77 - 1.38x slower
recurse5f         617.39 - 1.45x slower
recurse9f         580.50 - 1.54x slower
recurse9m         543.77 - 1.65x slower
recurse5b         541.94 - 1.65x slower
recurse5m         540.25 - 1.66x slower
recurse2b         475.47 - 1.88x slower
recurse9b         398.33 - 2.25x slower

##### With input 10000000 #####
Name                 ips        average  deviation         median
recurse1            7.14      139.99 ms    ±38.68%      130.54 ms
recurse2bn          6.82      146.61 ms    ±42.16%      135.12 ms
recurse2f           5.09      196.44 ms    ±36.61%      175.40 ms
recurse9f           5.04      198.54 ms    ±32.93%      181.58 ms
recurse2fn          5.01      199.69 ms    ±32.30%      188.76 ms
recurse2b           4.30      232.61 ms    ±35.59%      200.98 ms
recurse5f           3.95      253.09 ms    ±26.26%      233.55 ms
recurse9b           3.67      272.60 ms    ±27.00%      266.37 ms
recurse5m           3.58      279.27 ms    ±27.92%      287.48 ms
recurse9m           3.48      287.72 ms    ±30.91%      259.21 ms
recurse5b           3.42      292.59 ms    ±24.17%      298.01 ms

Comparison: 
recurse1            7.14 
recurse2bn          6.82 - 1.05x slower
recurse2f           5.09 - 1.40x slower
recurse9f           5.04 - 1.42x slower
recurse2fn          5.01 - 1.43x slower
recurse2b           4.30 - 1.66x slower
recurse5f           3.95 - 1.81x slower
recurse9b           3.67 - 1.95x slower
recurse5m           3.58 - 1.99x slower
recurse9m           3.48 - 2.06x slower
recurse5b           3.42 - 2.09x slower

Follows the same pattern, though I’m not sure why unless the BEAM is able to throw-away and reuse argument positions that it used to not be able to do, also interesting… Maybe it’s some-how optimizing the 1+... bit that ‘should’ be preventing TCO…

2 Likes