Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Elixir message delivery time proportional to message size?

I am finding that the time it takes to deliver a message in Elixir is proportional to the size of the message, when I would expect it to be relatively constant. Since data structures are immutable, the runtime should be able to pass large structures between processes by reference (in constant time). Consider the following test.

use Bitwise

defmodule PerfTask do
  def pack(s) do
    {millis, packed} = :timer.tc(fn -> Enum.to_list(s) end)
    IO.puts("packed in #{millis} millis")
    Task.async(fn -> packed end)    
  end

  def unpack(t) do
    {millis, unpacked} = :timer.tc(fn -> Task.await(t) end)
    IO.puts("unpacked in #{millis} millis")
    unpacked
  end

  def go(n) do
    IO.puts "n = #{n}"
    1..n |> pack |> unpack
  end
end

PerfTask.go(1 <<< 20)

With 2^20 elements in the list, this prints

n = 1048576
packed in 106481 millis
unpacked in 9916 millis

It takes roughly 10 times as long to build the list as it does to get it out of the Task. (Note that the list is built before the task starts. All the task has to do is return the already-built list.)

With 2^22 elements in the list, it prints

n = 4194304
packed in 397428 millis
unpacked in 38748 millis

The ratio is still approximately 10:1. The 4x longer list takes 4x as long to send between processes. What am I missing?

$ iex
Erlang/OTP 18 [erts-7.2] [source] [64-bit] [smp:8:8] [async-threads:10] [kernel-poll:false]

Interactive Elixir (1.2.0) - press Ctrl+C to exit (type h() ENTER for help)

(I have confirmed the problem is not specific to the Task module by replacing it with plain processes with similar results.)

like image 724
Peter Winton Avatar asked Feb 01 '16 16:02

Peter Winton


Video Answer


1 Answers

Per this answer from @rvirding your basic assumption is flawed. To quote Mr. Virding:

. . . current versions of Erlang basically copy everything except for larger binaries. In older pre-SMP times it was feasible to not copy but pass references. While this resulted in very fast message passing it created other problems in the implementation, primarily it made garbage collection more difficult and complicated implementation. I think that today passing references and having shared data could result in excessive locking and synchronisation which is, of course, not a Good Thing.

In the context of Elixir, "larger binaries" means very long strings--bigger than 64K.

like image 173
Onorio Catenacci Avatar answered Sep 28 '22 04:09

Onorio Catenacci