virzen

virzen

Body recursion faster than tail recursion in factorial

Hi, I’m new here. I’m finally trying out Elixir after hearing and reading about it for quite some time.

I’m implementing simple algorithms first, and already at factorial I found something perplexing. I’ve implemented it in several versions, one of them being tail recursive. I expected that one to be the fastest, but to my surprise it wasn’t - body recursion was.

I’ve read Erlang's Tail Recursion is Not a Silver Bullet and Erlang -- The Seven Myths of Erlang Performance but these only talk about lists; when calculating simple value, like factorial does, tail recursion should be faster, as far as I understand.

I’m doing the benchmark using Benchee, in a script run with mix run benchmark.exs. Project is clean project created using mix new. Below is the code for the benchmark and the functions themselves.

Am I missing something? Are my implementations wrong? Is the benchmark wrong? Am I running it wrong? Is this “idiomatic code being optimized”?

# benchmark.exs
n = 10000
Benchee.run(%{
  "body" => fn -> Factorial.body(n) end,
  "tail" => fn -> Factorial.tail(n) end
})
# factorial.ex
defmodule Factorial do
  def body(n) do
    case n do
      0 -> 1
      1 -> 1
      n -> n * body(n - 1)
    end
  end

  def tail(n) do
    tail_internal(1, n)
  end
  defp tail_internal(acc, 0), do: acc
  defp tail_internal(acc, n) do
    tail_internal(acc * n, n - 1)
  end
end

One output is below, but the situation with tail recursion being 5 - 15% slower repeats consistently and goes down as the input goes up, presumably because there is less and less calls to the function (only 1 when n is 1 000 000).

Operating System: Linux
CPU Information: AMD Ryzen 5 3600 6-Core Processor
Number of Available Cores: 12
Available memory: 12.44 GB
Elixir 1.12.2
Erlang 24.0.5

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 5 s
memory time: 0 ns
parallel: 1
inputs: none specified
Estimated total run time: 14 s

Benchmarking body...
Benchmarking tail...

Name           ips        average  deviation         median         99th %
body        3.20 K      312.96 μs     ±7.01%      309.20 μs      383.20 μs
tail        2.79 K      359.05 μs     ±6.48%         355 μs      441.43 μs

Comparison: 
body        3.20 K
tail        2.79 K - 1.15x slower +46.09 μs

Most Liked

LostKobrakai

LostKobrakai

It might be that cleaning up the stack between each individual call might be slower than just doing all the calculation while accumulating stacks and freeing them up in one batch at the end.

Tail call optimization is much more a thing of “allow programs to run, which cannot be run without it” and less “it’s better than body recursion”.

derek-zhou

derek-zhou

10000! is a pretty big number. Large integer calculation is slow, and will dominate the run time. The small saving in function calling overhead is insignificant.

If you change the multiply to add you can clearly see that tail recursion is faster.

derek-zhou

derek-zhou

No. This is related to the fact that infinite accurate integer arithmetic is slow.

dimitarvp

dimitarvp

derek-zhou

derek-zhou

Remember, elixir automatically promotes integers to infinite precision on a as needed basis. Changing to addition eliminates all large numbers, so everything fit inside standard word size, (64 bit in most computers)

Where Next?

Popular in Questions Top

srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
gonzofish
I’m currently trying to understand how to join three tables using Ecto. All the examples I’ve seen use 2, so maybe I’m just missing somet...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New

Other popular topics Top

Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
sergio
I couldn’t find any guides that worked well with Phoenix 1.6.0 and esbuild. I hope this helps people test the waters and eases you into t...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New

We're in Beta

About us Mission Statement