💾 Archived View for ser1.net › post › my-shocking-erlang-discovery.gmi captured on 2023-05-24 at 17:55:03. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2022-07-16)
-=-=-=-=-=-=-
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.