Pages

Saturday, November 28, 2009

Simple Mutable Variables using Erlang NIF's

I wrote a simple mutable variable data store to experiment with the new Erlang native implemented function (NIF) interface. Like Erlang itself, the NIF interface is simple, well thought out and robust.

I should mention that I like the immutable variables in Erlang. I only find them to be an annoyance when I want to write a quick program. In this sense they are sort of like static types in other languages; they force you to design your program before you start coding. The sort of complaints I've read about non-destructive variable updates (e.g., writing tests) seem to reinforce this view.

A quick example of using wat: you want to benchmark various checksum functions and prove the results are evenly distributed for a given set of inputs. We can benchmark a version using arrays and another using NIF's.

The array version:
-module(csum).
-export([start/2]).
-export([loop/3]).

start(Hash,File) ->
    {ok, Fh} = file:open(File, [
            binary,
            read,
            read_ahead,
            raw
        ]),
    {MicroSec, L} = timer:tc(
        ?MODULE, loop,
        [Fh, Hash, array:new([3, {default,0}])]
    ),
    T = lists:sum(L),
    P = [ N / T * 100 || N <- L ],
    {{elapsed_time, MicroSec / 1000000},
        {result, L}, {total, T}, {perc, P}}.

loop(Fh, Hash, Count) ->
    case file:read_line(Fh) of
        eof ->
            array:to_list(Count);
        {error, Error} ->
            io:format("error:~p~n", [Error]);
        {ok, Line} ->
            N = erlang:Hash(Line) rem
            array:size(Count),
            V = array:get(N, Count),
            loop(Fh, Hash, array:set(N, V+1, Count))
    end.

The NIF version:
-module(scum).
-export([start/2]).
-export([loop/3]).

start(Hash, File) ->
    wat:init(),
    {ok, Fh} = file:open(File, [
            binary,
            read,
            read_ahead,
            raw
        ]),
    {MS, A} = timer:tc(
        ?MODULE, loop,
        [Fh, Hash, 3]),
    T = lists:sum(A),
    P = [ N / T * 100 || N <- A ],
    {{elapsed_time, MS / 1000000}, {result, A}, {total, T}, {perc, P}}.

loop(Fh, Hash, Count) ->
    case file:read_line(Fh) of
        eof ->
            [ wat:get(N) ||
                N <- lists:seq(0,Count-1) ];
        {error, Error} ->
            io:format("error:~p~n", [Error]);
        {ok, Line} ->
            N = erlang:Hash(Line) rem Count,
            wat:add(N, 1), loop(Fh, Hash, Count)
    end.

Running csum/scum on a MacBook Pro with 2 cores and SMP enabled:

4> csum:start(crc32,"test.txt").
{{elapsed_time,0.668354},
{result,[49001,48963,48743]},
{total,146707},
{perc,[33.40058756569216,33.37468559782423,
33.224726836483605]}}
5> scum:start(crc32,"test.txt").
{{elapsed_time,0.642855},
{result,[49001,48963,48743]},
{total,146707},
{perc,[33.40058756569216,33.37468559782423,
33.224726836483605]}}

Using "+A 10":
5> csum:start(crc32,"test.txt").
{{elapsed_time,3.597195},
{result,[49001,48963,48743]},
{total,146707},
{perc,[33.40058756569216,33.37468559782423,
33.224726836483605]}}
6> scum:start(crc32,"test.txt").
{{elapsed_time,2.839503},
{result,[49001,48963,48743]},
{total,146707},
{perc,[33.40058756569216,33.37468559782423,
33.224726836483605]}}

No comments:

Post a Comment