Pages

January 13, 2011

Rx.net ObserveOn performance

I been playing with benchmarking performance of pushing small messages via Rx where message producer and message consumer are on different threads. With one producer and possibly many consumers.

Rx has a useful operator called ObserveOn, which let's you process messages on the threadpool. However it seems to have a couple of shortcomings.

1. It does not process several messages concurrently, although it is using a thread pool, it will process messages serially. So if message post processing done by the consumer is time consuming, you may want fork and join type of semantics (spin off N tasks, where N is the size of the thread pool), and then assemble the results in the right order and forward (right order is very important for correct Rx operation).

2. Rx seems to be slow! I wrote a little benchmark that pushes small messages as fast as it can (messages are preallocated, so little memory allocator overhead in the test timing), then I implemented a synchronized queue (user ReaderWriterLockSlim and Queue) to do same produce/consume logic, and the results are dramatically different, tests we done on a 2 core, 3 Ghz machine.

With Rx I am getting around 220k msg/sec, with my hand coded queue, I am getting a wopping (compared to Rx), 4m msg/sec, that is 20x faster than Rx.

Source code for the benchmark is here (note to self, clean up):
https://gist.github.com/90983196d1a45e91d11e

January 3, 2011

Erlang Ring Benchmark Part 3

In Ring Benchmark Part 2 I presented a more compact solution to the ring benchmark. It's performance is about same as the original solution that was using pure recursion to setup the ring. Both part 1 and part 2 were waiting for a message to go around the ring completely, before injecting the next message. In this version we will inject messages 1 through M as soon as the ring is setup, this considerably improves throughput. Line 10 below is the part where we spawn off a process to inject M..0 messages into the ring.

Timings Across Part 1-3

  • Part 1: N=1000, M=1000, Run Time = 600 ms
  • Part 2: N=1000, M=1000, Run Time = 600 ms
  • Part 3: N=1000, M=1000, Run Time = 400 ms

Code


-module(ring3_2).
-include("debug.hrl").
-export([start/2]).
%% ring benchmark using lists methods
start(N, M) ->
    io:format("Starting ring benchmark on pid=~p, N = ~p, M = ~p.~n", [self(), N, M]),
    statistics(wall_clock),
    statistics(runtime),
    Last = lists:foldl(
    fun (X, Pid) -> 
        spawn(fun () -> loop(X, Pid) end) end, self(), lists:seq(N-1, 1, -1)),    
    spawn(fun () -> 
        lists:foreach(fun (X) -> Last ! X end, lists:seq(M-1, 0, -1)) end),
    wait(),
    {_, WC} = statistics(wall_clock),
    {_, RT} = statistics(runtime),
    io:format("Done running ring benchmark in Wall Clock = ~p, Runtime = ~p.~n", [WC, RT]).

wait() ->
    receive
 Xm when Xm =:= 0 -> 
     ?DEBUG("main node done waiting for x = ~p.~n", [Xm]),
     void;
 Xm -> ?DEBUG("main node done waiting for x = ~p.~n", [Xm]), wait()       
    end.

loop(Nth, Pid) ->
    receive
 X when X =:= 0 -> 
     ?DEBUG("~p dying.~n", [Nth]),
     Pid ! X;
 X -> 
     ?DEBUG("~p got message ~p.~n", [Nth, X]), 
     Pid ! X,
     loop(Nth, Pid)
    end.

Erlang Ring Benchmark Part 2

Using list comprehensions - results in more compact code.

-module(ring3).
-include("debug.hrl").
-export([start/2]).
%% ring benchmark using lists methods
start(N, M) ->
    io:format("Starting ring benchmark on pid=~p, N = ~p, M = ~p.~n", [self(), N, M]),
    statistics(wall_clock),
    statistics(runtime),
    Last = lists:foldl(fun (X, Pid) -> 
        spawn(fun () -> loop(X, Pid) end) end, self(), lists:seq(N-1, 1, -1)),    
    lists:foreach(fun (X) -> send(X, Last) end, lists:seq(M-1, 0, -1)),
    {_, WC} = statistics(wall_clock),
    {_, RT} = statistics(runtime),
    io:format("Done running ring benchmark in Wall Clock = ~p, Runtime = ~p.~n", [WC, RT]).

send(X, Pid) ->
    ?DEBUG("sending X=~p to ring.~n", [X]),
    Pid ! X,
    receive
 Xm -> ?DEBUG("main node done waiting for x = ~p.~n", [Xm])
    end.

loop(Nth, Pid) ->
    receive
 X when X =:= 0 -> 
     ?DEBUG("~p dying.~n", [Nth]),
     Pid ! X;
 X -> 
     ?DEBUG("~p got message.~n", [Nth]), 
     Pid ! X,
     loop(Nth, Pid)
    end.
           

December 28, 2010

Erlang Ring Benchmark

I am reading Joe Armstrong's Programming Erlang: Software for a Concurrent World, in chapter 8 he poses a little challenge to create a ring benchmark:
Write a ring benchmark. Create N processes in a ring. Send a mes- sage round the ring M times so that a total of N * M messages get sent. Time how long this takes for different values of N and M.
My solution is below... It was fun.

Usage from erlang command line

1> c(ring2).
2> ring2:start(100, 1000). % ring size = 100, send msg 1000 times round the ring
... sample below ...
42> ring2:start(300, 2000).
Starting ring benchmark on pid=<0.30.0>.
Done initializing nodes.
Got X(600000) >= M(600000) in pid=<0.30.0>.
Done running ring benchmark in Wall Clock = 437, Runtime = 391.
ok

The results above were obtained on a Core 2 Duo E8400 @ 3.00GHz

ring2.erl


-module(ring2).
-include("debug.hrl").
-export([start/2]).

%% ring benchmark

start(N, M) ->
    io:format("Starting ring benchmark on pid=~p.~n", [self()]),
    statistics(wall_clock),
    statistics(runtime),
    NextPid = node(N-1, N*M, self(), self()),
    NextPid ! 1,
    io:format("Done initializing nodes.~n"),
    wait(N*M, NextPid, self()),
    NextPid ! die,
    {_, WC} = statistics(wall_clock),
    {_, RT} = statistics(runtime),
    io:format("Done running ring benchmark in Wall Clock = ~p, Runtime = ~p.~n", [WC, RT]).
     
node(1, M, NextPid, DonePid) ->
    Pid = spawn(fun () -> wait(M, NextPid, DonePid) end),
    ?DEBUG("Creating final node: ~p.~n", [Pid]),
    Pid;
node(N, M, NextPid, DonePid) -> 
    Pid = spawn(fun () -> wait(M, NextPid, DonePid) end),
    ?DEBUG("Creating node ~p = ~p.~n", [N, Pid]),
    node(N-1, M, Pid, DonePid).

wait(M, NextPid, DonePid) ->
    receive
 die ->
     ?DEBUG("~p dying.~n", [self()]),
     if NextPid =/= DonePid -> NextPid ! die;
        true -> void
     end,
     void;
 X when X >= M -> 
     io:format("Got X(~p) >= M(~p) in pid=~p.~n", [X, M, self()]),
     DonePid ! die,
     wait(M, NextPid, DonePid);
 X   ->
     ?DEBUG("Got X = ~p in pid=~p, fwd to ~p.~n", [X, self(), NextPid]),
     NextPid ! X+1,
     wait(M, NextPid, DonePid)
    end.     


I am using a little debugging macro to turn logging on/off. At erlang shell: c(ring2, {d, debug}). to turn debugging trace on.

debug.hrl


-ifdef(debug).
-define(DEBUG(Format, Args), io:format("~s.~w: DEBUG: " ++ Format, [ ?MODULE, ?LINE | Args])).
-else.
-define(DEBUG(Format, Args), true).
-endif.

here are some solutions from other people: http://goo.gl/kE42w

December 27, 2010

Book Review: Beautiful Architecture


Beautiful Architecture: Leading Thinkers Reveal the Hidden Beauty in Software Design by Diomidis Spinellis


I got a chance to review one of the books in OReilly's "Beautiful" series, in this case it was Beautiful Architecture.
One of the attractions of the books in this series, is the premise that you get to hear first-hand accounts, in an essay format, from the people behind some of the more interesting projects out there. So the book can be viewed as an opportunity to ask a bunch of innovative technologists the question: how did you build it?

On the back cover the book claims that the contributors cover notable and innovative software architectures. At first glance the table of contents looked pretty promising, for example “The architecture of the Facebook platform”. However, after spending some time with the book and reading through 5 random chapters, it feels like there is just not enough substance for the book to achieve what most people are probably hoping to get out of the book: concrete blueprints or solid design patterns to build systems demonstrating desired characteristics.

For the first 45 or pages of the book, the essays tended to meander around the topics of general definition of architecture vs software design etc, and general experience of people working on some non-trivial projects. But somehow the conversation always feels more academic than practical. So you kind of feel like you are sitting on a plane waiting for it to disengage from the gate and move towards the tarmac. And given the fact that this book is supposed to be about “beautiful” architectures, I am not at all sure why we need to spend time reading about “The Messy Metropolis”. Given the title and general back cover description of the book I was expecting the narratives to be more specific and more parallels drawn with real-world systems that people interested in this book could be building.

The chapter “Architecting for Scale” by Jim Waldo, was also a bit underwhelming. We basically get a pretty high level overview of a system that was never really tested in the field, was not thoroughly bench-marked, and is addressing a fairly narrow problem domain of online gaming. Although some interesting ideas are presented, you have a hard time agreeing that this is necessarily beautiful because it does not seem that the system is proven to successfully solve their scalability goals. The chapter about facebook platform also failed to describe the thinking process and design trade-offs, you get a description of general building blocks but you don't really get any aha moments.

I did enjoy “Software Architecture: Object-Oriented versus Functional” by Bertrand Meyer, however one might argue that the book might have been better off focusing on systems architecture and concrete examples of real-world projects as opposed to discussion of type systems and other programming language esoterica, especially when the Meyer makes it clear that he has not actually worked in finance nor released production systems dealing with financial contracts.

I think this book is decent read on your e-book reader, but if you have limited bookshelf space and want to keep it filled with true classics, this book is not it.

3/5 stars.

December 18, 2010

Big Cities Make People More Efficient

From a NY Times article: A Physicist Solves the City
The first data set they analyzed was on the economic productivity of American cities, and it quickly became clear that their working hypothesis — like elephants, cities become more efficient as they get bigger — was profoundly incomplete. According to the data, whenever a city doubles in size, every measure of economic activity, from construction spending to the amount of bank deposits, increases by approximately 15 percent per capita. It doesn’t matter how big the city is; the law remains the same. “This remarkable equation is why people move to the big city,” West says. “Because you can take the same person, and if you just move them to a city that’s twice as big, then all of a sudden they’ll do 15 percent more of everything that we can measure.” While Jacobs could only speculate on the value of our urban interactions, West insists that he has found a way to “scientifically confirm” her conjectures. “One of my favorite compliments is when people come up to me and say, ‘You have done what Jane Jacobs would have done, if only she could do mathematics,’ ” West says. “What the data clearly shows, and what she was clever enough to anticipate, is that when people come together, they become much more productive.”

December 16, 2010

Rx.NET Subject useful for connecting decoupled components

Rx has a nifty abstraction called Subject shipped as part of the System.Reactive assembly.

It's basically a buffer with channel like capabilities rolling up an Obsever/Observable pattern into a single class.

In cases where you have two or more classes that don't really know about each other but sometimes need to be connected (ie output of one should go into input of another), you can can inject an instance of a Subject class into both components, and then have them send messages. Subject provides a standard Rx.NET friendly API to push/pull messages, this makes it quite powerful as you can use the full expressive power of Rx.NET to consume message from Subjects in different scenarios (one-to-one, one-to-many, filtering, Join, etc) .