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

Hi Everyone.
I prototype another scenario
in that i not used any wrapper over rust + tokio !!
just used internal features

scenario 1.
in this scenario i used message passing and a calculate operation
first spawn some worker ( number == my cpu core)
then sending request to each worker and await for result of each worker and it do simple sum over N counter and then response Result::Ok in rust in elixir :ok

Rust Client


use chrono::PreciseTime;


mod server;
use server::server::{ start, MReq, MResp, Request};
use tokio::{sync::oneshot::{self, Receiver}};


#[tokio::main]
async fn main() {

    let server1 = start().await;
    let server2 = start().await;
    let server3 = start().await;

    let start = PreciseTime::now();
    // ===================================================

    for n in 0..1000 {
        
        let (recv_resp1, req1) = request_factory(n);
        let (recv_resp2, req2) = request_factory(n);
        let (recv_resp3, req3) = request_factory(n);
        
        
        let _ = server1.send(req1).await;
        let _ = recv_resp1.await;

        let _ = server2.send(req2).await;
        let _ = recv_resp2.await;
        
        let _ = server3.send(req3).await;
        let _ = recv_resp3.await;



    }

    // ===================================================
    let end = PreciseTime::now();
    let tm = start.to(end).num_microseconds().unwrap();
    println!("==> {} ns (microseconds)", tm) 

}


fn request_factory(n: i32) -> (Receiver<MResp>, Request<MReq, MResp>) {
    let (resp, recv) = oneshot::channel::<MResp>();
    let req = Request::<MReq, MResp> {
        msg: MReq::Event(n),
        resp
    };

    (recv, req)
}

Rust Server

pub mod server {

    use tokio::sync::mpsc::{self};
    use tokio::sync::oneshot::{self};

    pub struct Request<MReq, MResp> {
        pub msg : MReq,
        pub resp: oneshot::Sender<MResp>
    }

    pub enum MReq {
        Event(i32)
    }
    pub enum MResp {
        Event(Result<(), ()>)
    }



    pub async fn start() -> mpsc::Sender<Request<MReq, MResp>> {
        let (client, mut server) = 
            mpsc::channel::<Request<MReq, MResp>>(16);

        tokio::spawn(async move {
            while let Some(req) = server.recv().await {
                let MReq::Event(n)  = req.msg;
                {
                    let mut temp = 1;
                    for num in 1..n {
                        temp += num;
                    }
                }
                let _ = req.resp.send(MResp::Event(Ok(())));
            }
        });
        
        client
    }
    
}

Elixir Server

defmodule Todo.Server do
  use GenServer

  def start(args) do
    GenServer.start(__MODULE__, args)
  end

  def init(init_arg) do
    {:ok, init_arg}
  end

  # ========================================

  def handle_call({:event, n}, _from, state) do
    execute(n, 1)
    {:reply, :ok, state}
  end





  def execute(1, _) do
    :ok
  end
  def execute(num, temp) do
    execute(num-1, temp + num)
  end

end


Elixir Client

defmodule Todo.Client do

  def start(n) do
    {:ok, server1} = Todo.Server.start([])
    {:ok, server2} = Todo.Server.start([])
    {:ok, server3} = Todo.Server.start([])
    {tm, _} = :timer.tc(fn ->
        Todo.Client.loop({n, server1, server2, server3})
    end)

    IO.write("#{tm} ns")
  end


  def loop({0, _, _, _}) do
    :done
  end
  def loop({n, server1, server2, server3}) do
    GenServer.call(server1, {:event, n})
    GenServer.call(server2, {:event, n})
    GenServer.call(server3, {:event, n})
    loop({n-1, server1, server2, server3})
  end


end

above i said N sum operation N is iteration in below

Result is Amazing :

Scenario over 1,000 iteration
Rust+Tokio : 144,792 microseconds
Elixir/Beam : 23,393 microseconds

Scenario over 10,000 iteration
Rust+Tokio : 5,503,831 microseconds
Elixir/Beam : 812,285 microseconds

even with arithmetic operation beam was winner

2 Likes