My shocking Erlang discovery
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.