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.)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With