Showing posts with label performance. Show all posts
Showing posts with label performance. Show all posts

Tuesday, October 6, 2009

Closure vs. Object in Perl

I was curios how closures performs in comparison with objects in Perl. I have tested it on counter as the simplest possible abstraction with state.

use Benchmark qw(cmpthese);

sub make_counter {
    my $counter = shift;
    return sub { $counter++ };
}

package counter;

sub new {
    my ( $class, $counter ) = @_;
    return bless \$counter, $class;
}

sub inc { ${ shift() }++ }

package main;

our $inc = make_counter(1);

our $counter = counter->new(1);

cmpthese(
    -5,
    {   closure_make => q{make_counter(1)},
        object_make  => q{counter->new(1)}
    }
);

cmpthese(
    -5,
    {   closure => q{$main::inc->()},
        method  => q{$main::counter->inc()}
    }
);

And results are:

                 Rate closure_make  object_make
closure_make 420045/s           --         -35%
object_make  643969/s          53%           --
             Rate  method closure
method  2397172/s      --    -35%
closure 3697681/s     54%      --

Well, it is not simple. Both approaches have their benefits but if you want best performance and want little bit abstraction you should pass closure into your hot loop instead objects. But if you want create closure inside loop, than let you try avoid it or change to object. But you should not need it in hot loop anyway. I'm little bit surprised that closure construction is so expensive in perl. I have known that bless is not cheap operation, but closure?

Sunday, February 15, 2009

How fast or slow is Moose?

Moose is a postmodern object system for Perl 5 that takes the tedium out of writing object-oriented Perl. It borrows all the best features from Perl 6, CLOS (LISP), Smalltalk, Java, BETA, OCaml, Ruby and more, while still keeping true to its Perl 5 roots. It is very powerful and I was curious how fast is in current state. I have read that Moose is slow but all articles what I have found is about two years old. For me is important mostly runtime speed but compile time. So my benchmark is focused only to runtime. I also hate getter/setter combined accessors thus my tests are also only about separated getter and setter. Benchmark code follows.

#!/usr/bin/env perl

use strict;
use warnings;
use Data::Dumper;
use Benchmark qw(:all :hireswallclock);

{

 package MooseClassMutable;
 use Moose;

 has var => (
  is       => 'ro',
  reader   => 'get_var',
  writer   => 'set_var',
  required => 1
 );

}
{

 package MooseClassImmutable;
 use Moose;

 has var => (
  is       => 'ro',
  reader   => 'get_var',
  writer   => 'set_var',
  required => 1
 );

 no Moose;
 __PACKAGE__->meta->make_immutable;
}
{

 package PerlClass;

 sub new {
  my ( $class, %args ) = @_;
  die 'var value must be set' unless exists $args{var};
  return bless \%args, $class;
 }

 sub get_var { shift()->{var} }

 sub set_var { $_[0]->{var} = $_[1] }
}

{

 package MooseClassFast;
 use Moose;
 with 'MooseX::Emulate::Class::Accessor::Fast';

 has var => (
  is       => 'ro',
  required => 1
 );

 __PACKAGE__->follow_best_practice;
 __PACKAGE__->mk_accessors('var');

 no Moose;
 __PACKAGE__->meta->make_immutable;
}

cmpthese(
 -5,
 {   map {
   my $class = $_;
   "new $class" => sub { $class->new( var => 1 ) for 1 .. 1000 }
   } qw(MooseClassMutable MooseClassImmutable PerlClass MooseClassFast)
 }
);
my %objs = ( map { $_ => $_->new( var => 1 ) }
  qw(MooseClassMutable MooseClassImmutable PerlClass MooseClassFast) );
cmpthese(
 -5,
 {   map {
   my $class = $_;
   my $obj   = $objs{$class};
   "get $class" => sub { $obj->get_var() for 1 .. 1000 }
   } qw(MooseClassMutable MooseClassImmutable PerlClass MooseClassFast)
 }
);
cmpthese(
 -5,
 {   map {
   my $class = $_;
   my $obj   = $objs{$class};
   "set $class" => sub { $obj->set_var(1) for 1 .. 1000 }
   } qw(MooseClassMutable MooseClassImmutable PerlClass MooseClassFast)
 }
);

I have tested Moose versions 0.54 and 0.68 and just for curiosity also Class::Accessor::Fast emulation which works only with Moose version 0.68. Notice that rate is measured in thousands. Moose 0.54 results comes first.

                          Rate new MooseClassMutable new MooseClassImmutable new PerlClass
new MooseClassMutable   6.85/s                    --                    -96%          -98%
new MooseClassImmutable  192/s                 2697%                      --          -53%
new PerlClass            403/s                 5790%                    111%            --
                          Rate get MooseClassMutable get MooseClassImmutable get PerlClass
get MooseClassMutable   1716/s                    --                     -2%          -25%
get MooseClassImmutable 1754/s                    2%                      --          -23%
get PerlClass           2273/s                   32%                     30%            --
                          Rate set MooseClassImmutable set MooseClassMutable set PerlClass
set MooseClassImmutable 1611/s                      --                   -2%          -16%
set MooseClassMutable   1643/s                      2%                    --          -14%
set PerlClass           1916/s                     19%                   17%            --

Moose version 0.68 follows.

                          Rate new MooseClassMutable new MooseClassFast new MooseClassImmutable new PerlClass
new MooseClassMutable   15.4/s                    --               -84%                    -92%          -96%
new MooseClassFast      98.7/s                  541%                 --                    -48%          -76%
new MooseClassImmutable  190/s                 1138%                93%                      --          -54%
new PerlClass            412/s                 2579%               318%                    116%            --
                          Rate get MooseClassFast get MooseClassImmutable get MooseClassMutable get PerlClass
get MooseClassFast      1716/s                 --                     -2%                   -2%          -24%
get MooseClassImmutable 1743/s                 2%                      --                   -1%          -23%
get MooseClassMutable   1754/s                 2%                      1%                    --          -22%
get PerlClass           2261/s                32%                     30%                   29%            --
                          Rate set MooseClassFast set MooseClassMutable set MooseClassImmutable set PerlClass
set MooseClassFast      78.6/s                 --                  -95%                    -95%          -96%
set MooseClassMutable   1659/s              2011%                    --                     -1%          -15%
set MooseClassImmutable 1680/s              2038%                    1%                      --          -14%
set PerlClass           1950/s              2381%                   18%                     16%            --

Moose seems fast enough for me. If I realize how powerful Moose is results are great. I can persist class definition using make_immutable in most of cases and 190 thousand object constructions per second is enough. There is also big improvement in mutable version between 0.54 and 0.68 and 15 thousand per second is not terrible. Moose accessors are really fast and make_immutable have not any impact here. 1.7 million reads and 1.6 million writes per second is enough and my ugly handcrafted accessors can't make big difference here (2.2Mr/s and 1.9Mw/s). There is strange Class::Accessor:Fast setter result and I'm curious why. Anyway Moose itself performs well and there is not reason using it.

Sunday, October 7, 2007

Is bfile faster than old erlang file?

Steve Vinoski is using klacke’s bfile module in his Wide Finder Project work, but I don't know why bfile should be faster than erlang OTP file. Well, then I measured. I tried Steve's read test and my test on my old home desktop (model name : AMD Athlon(tm) processor, stepping : 2, cpu MHz : 1199.805, cache size : 256 KB).
-module(readold).
-export([start/1, start/2]).
-compile([native]).

scan_file(F, Readsize, Total) ->
   Rd = file:read(F, Readsize),
   case Rd of
       {ok, Bin} -> scan_file(F, Readsize, size(Bin)+Total);
       eof -> Total
   end.
scan_file(F, Readsize) -> scan_file(F, Readsize, 0).

start(File, Readsize) ->
   {ok, F} = file:open(File, [raw, binary, read]),
   T = scan_file(F, Readsize),
   io:format("read ~p bytes~n", [T]),
   file:close(F).
start(File) ->
   start(File, 512*1024).
And there are results here:
2> timer:tc(readold,start,["o1M.ap"]).
read 200995500 bytes
{1041306,ok}
3> timer:tc(readold,start,["o1M.ap"]).
read 200995500 bytes
{836876,ok}
4> c(readold).
{ok,readold}
5> timer:tc(readold,start,["o1M.ap"]).
read 200995500 bytes
{837501,ok}
6> timer:tc(read,start,["o1M.ap"]).  
read 200995500 bytes
{1353678,true}
7> timer:tc(read,start,["o1M.ap"]).
read 200995500 bytes
{1237174,true}
8> timer:tc(read,start,["o1M.ap"]).
read 200995500 bytes
{1318029,true}
9> timer:tc(readold,start,["o1M.ap"]).
read 200995500 bytes
{856662,ok}
In generally, I don't know why erlang's file should be slower. I don't know why bfile is 45% slower than file on my old home desktop, but why should be faster anywhere? I tested it on Linux, may be bfile using BSD file implementation is faster on Darwin aka BSD clone? The file implementation is fast enough on my Erlang/OTP R11B-5.