Why is the chain.exs example of creating 1m processes slower on my faster laptop?

In chapter 15 of Dave T. book programming elixir, there is an example chain.exs that basically creates many processes, each one incrementing the value and passing value as a message to another process.

Anyhow Dave mentions that he runs this code on his 2011 macbook air 2.13GHz core 2 dua with only 4GB of ram.

I ran the same code on my macbook pro (2016) and for some reason my code is running slow than his. Mine is a Macbook pro 15" 2.6Ghz i7, 16GB ram.

elixir --erl “+P 1000000” -r chain.exs -e “Chain.run(1_000_000)”

{10799103, “Result is 1000000”}

His results were:

{5135238, “Result is 1000000}” # page # 193 in his book

Any ideas on why it would be running slower on my laptop? Its 10 seconds compared to his 5 seconds.

The code that I am running is:

defmodule Chain do
  def counter(next_pid) do
    receive do
      n -> send next_pid, n + 1
    end
  end

  def create_processes(n) do
    last = Enum.reduce 1..n, self(), fn (_, send_to) -> spawn(Chain, :counter, [send_to]) end
    send last, 0

    receive do
      final_answer when is_integer(final_answer) ->
        "Result is #{inspect(final_answer)}"
    end
  end

  def run(n) do
    IO.puts inspect :timer.tc(Chain, :create_processes, [n])
  end
end
  Model Name:	MacBook Pro
  Model Identifier:	MacBookPro13,3
  Processor Name:	Intel Core i7
  Processor Speed:	2.9 GHz
  Number of Processors:	1
  Total Number of Cores:	4
  L2 Cache (per Core):	256 KB
  L3 Cache:	8 MB
  Memory:	16 GB
  System Version:	macOS 10.13.5 (17F77)
  Kernel Version:	Darwin 17.6.0
$ elixir -v
Erlang/OTP 21 [erts-10.1.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Elixir 1.7.4 (compiled with Erlang/OTP 21)
# 8 schedulers (online) - 262144 processes
$ elixir -r chain.exs -e "Chain.run(10)"
{30, "Result is 10"}
# vs. {4015, "Result is 10"}
$ elixir -r chain.exs -e "Chain.run(100)"
{1013, "Result is 100"}
# vs. {4562, "Result is 100"}
$ elixir -r chain.exs -e "Chain.run(1000)"
{7418, "Result is 1000"}
# vs. {8458, "Result is 1000"}
$ elixir -r chain.exs -e "Chain.run(10_000)"
{88654, "Result is 10000"}
# vs. {66769, "Result is 10000"}

i.e. it is faster if you stay under a 1000 :expressionless:

Tada!

$ elixir --erl "+S 1 +P 1000000" -r chain.exs -e "Chain.run(1_000_000)"
{3251305, "Result is 1000000"}

It is faster by forcing it to a single scheduler.

3 Likes

How did you get that 8 schedulers online output?

I didn’t - that was a comment - but I was running this code:

defmodule Chain do
  def counter(next_pid) do
    receive do
      n -> send next_pid, n + 1
    end
  end

  defp reducer(_, send_to),
    do: spawn(Chain, :counter, [send_to])

  def create_processes(n) do
    last = Enum.reduce(1..n, self(), &reducer/2)
    send last, 0

    receive do
      final_answer when is_integer(final_answer) ->
        "Result is #{inspect(final_answer)}"
    end
  end

  def run(n) do
    IO.inspect :erlang.system_info(:schedulers)
    IO.inspect :erlang.system_info(:schedulers_online)
    IO.inspect :erlang.system_info(:process_limit)
    IO.puts inspect :timer.tc(Chain, :create_processes, [n])
  end
end
1 Like