Elixir/Erlang is Faster than Optimized Rust(tokio) in Message Passing

Yet another benchmark :sweat_smile:

to prove to myself and anyone because this is a very strange topic
but it is REAL

Scenario
in benchmark, spawn 3 very simple worker and those
generate (key, value) and
( send to channel in rust ),
( send to a mailbox in elixir )
then a task/process store those to (Hashmap in rust) (ETS in Elixir)

Again Beam Winner

i think this was a dream for joe armstrong but come to true …

Rust


use std::collections::{HashMap};
use chrono::PreciseTime;
use tokio::sync::mpsc;


#[tokio::main]
async fn main() {
    let mut kv = HashMap::<i32, String>::new();
    let (sender, mut recv) = 
        tokio::sync::mpsc::channel::<(i32, String)>(100);

    
    let start = PreciseTime::now();
    // ===================================================
    worker_factory(1000, sender.clone());
    worker_factory(1000, sender.clone());
    worker_factory(1000, sender);


    while let Some((key, val)) = recv.recv().await {
        kv.insert(key, val);
    }
    // ===================================================
    let end = PreciseTime::now();
    let tm = start.to(end).num_microseconds().unwrap();
    println!("==> {} ns (microseconds)", tm) 

}


fn worker_factory(counter: i32, chan: mpsc::Sender<(i32, String)>) {
    tokio::spawn(async move {
        for elem in 0..counter {
            let _ = chan.send((elem, elem.to_string())).await;
        }
    });
}

Elixir

defmodule Todo.Main do

  def start(counter) do
    tid = :ets.new(__MODULE__, [])

    :timer.tc(fn ->
      worker_factory(counter, self())
      worker_factory(counter, self())
      worker_factory(counter, self())
      receiver(0, tid)

    end)
  end

  def receiver(finished, tid) do
    receive do
      {key, val} ->
        :ets.insert(tid, {key, val})
        receiver(finished, tid)
      finish ->
        finished = finish + finished
        case finished do
          3 ->
            :done
          _ ->
            receiver(finished, tid)
        end
    end
  end

  def worker_factory(counter, kvserver) do
    Task.start(fn() ->
      Todo.Main.loop(counter, kvserver)
    end)
  end


  def loop(0, kvserver) do
    send(kvserver, 1)
  end
  def loop(n, kvserver) do
    send(kvserver, {n, "#{n}"})
    loop(n-1, kvserver)
  end


end

**Result was Amazing **

Scenario over 100 Iteration:
Rust + Tokio : 837ns ~ 1,300ns
Elixir/Beam : 270ns ~ 1,100ns

Scenario over 1,000 Iteration:
Rust + Tokio : 9,769ns ~ 14,300
Elixir/Beam : 2,202ns ~ 11,200

but this is not really real usage because in real world
for storing we almost always need read-heavy or write heavy
if take benchmark for it
i PROMISE beam is winner because it and (ETS) are very full features

1 Like