💾 Archived View for ser1.net › post › my-shocking-erlang-discovery.gmi captured on 2022-07-16 at 13:27:30. Gemini links have been rewritten to link to archived content

View Raw

More Information

➡️ Next capture (2024-03-21)

-=-=-=-=-=-=-

Sending messages to PIDs is really, really slow (comparatively).

Consider the following code, containing two tests that do almost the same thing.  I say "almost" because there's a small bug causing the totals to be off-by-one, but they're performing the same amount of functional work.  One uses PID message passing to increment a value; the other uses lambda expressions.

-module(test).
-export([test/2]).
-export([one/1, two/1]).

test(F, N) ->
    A = F(init),
    {T,R} = timer:tc( lists,foldl, [ fun( _, Acc) -> F(Acc) end, A,
lists:seq(1, N) ]), 
    F({stop,R}),
    io:format("n~ps~n",[T/1000000]).

done(T) ->
    io:format("nDone ~p~n",[T]),
    io:format("Memory: ~p~n",[erlang:memory(processes_used)]).

one(init) ->
    spawn( fun loop/0 );
one({stop,X}) ->
    X ! exit;
one( X ) ->
    X ! go, X.

loop() -> loop(0,0).
loop( State, Ttl ) when State > 100000 ->
    io:format("."), 
    loop(0, Ttl + 1);
loop( State, Ttl ) ->
    receive
        go -> loop( State + 1, Ttl + 1 );
        exit -> done(Ttl)
    end.

two(init) ->
    fun(_) -> make_next( 0,0 ) end;
two({stop,R}) ->
    R(exit);
two( F ) ->
    F(0).

make_next(C,T) when C > 100000 ->
    io:format("."),
    make_next( 0, T+1);
make_next(C,T) ->
    fun(K) ->
            case K of
                exit -> done(T);
                _ -> make_next(C+1,T+1)
            end
    end.

`one()` retains state by spawning a process that loops with it's own state. `two()` is the interesting code; it retains state by passing back a function factory.  Would you have expected `one()` to be slower than `two()`?  I wouldn't have, at least not to this degree (I cleaned the output a little):

447)~ % erl +native -smp
[Erlang R14B01 (erts-5.8.2) [source] [smp:2:2] [rq:2]
[async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.8.2  (abort with ^G)
1> c(test).
{ok,test}
2> test:test( fun test:one/1, 10000000 ).
...................................................................................................
39.70179s
Done 10000099
ok
Memory: 111707084
3> test:test( fun test:two/1, 10000000 ).
...................................................................................................
Done 10000098
Memory: 111710500
2.410091s
ok
4> 

Using functions like this *sixteen times faster* than passing messages. That really surprises me, to be honest, and gives me something to think about before I go off and gen_server-ize all of my code.