in reply to Encapsulation without methods

I wrote a benchmark to compare tied scalar with get-and-set processing, and with mutation (passing a code ref to apply to the variable). Results are somewhat disappointing, though not necessarily prohibitive:
Rate tie mutate mutate2 getnset naked tie 77.4/s -- -27% -36% -54% -93% mutate 107/s 38% -- -11% -37% -91% mutate2 120/s 55% 13% -- -29% -90% getnset 169/s 118% 58% 40% -- -86% naked 1181/s 1426% 1007% 882% 601% --
naked
is direct access to the variable. This is included for a performance upper bound.
getnset
is a classic two-method-call way of modifying the member
mutate
is a one-method-call way to get, set, or modify (via code ref) the member
mutate2
uses only the get and set forms of the mutate method (to illustrate the overhead of simply having the extra test)
tie
uses a tied scalar whose STORE and FETCH are essentially identical to the get and set methods
The overhead for simply having the encapsulation (the difference between naked and getnset) is about a factor of 6. The overhead for using tie adds another factor of 2+. The more complex the behind-the-scenes machinations of storing and fetching, the less important that overhead will be, but it is significant.

One note about my benchmark program: if you pass it a non-zero argument, it will pass that on to cmpthese. If you don't, it will just sample-run each method to verify that the output is sensible. Use a negative arg for seconds, as positive one for iterations.

#! perl use strict; package Ring; use Carp; sub TIESCALAR { my $class = shift; my $arg; bless \$arg, $class; } sub FETCH { my $self = shift; confess "wrong type" unless ref $self; croak "usage error" if @_; return $$self; } sub STORE { my $self = shift; confess "wrong type" unless ref $self; my $newval = shift; croak "usage error" if @_; $$self = $newval % 12; } package MyOb; use Carp; sub new { my $class = shift; my $struct = { tie => 0, getnset => 0, mutate => 0, naked => 0 }; tie $struct->{'tie'}, 'Ring'; bless $struct, $class; } sub get { my $self = shift; confess "wrong type" unless ref $self; croak "usage error" if @_; return $self->{'getnset'}; } sub set { my $self = shift; confess "wrong type" unless ref $self; my $newval = shift; croak "usage error" if @_; $self->{'getnset'} = $newval % 12; } use UNIVERSAL 'isa'; sub mutate { my $self = shift; confess "wrong type" unless ref $self; if (@_) { my $set = shift; if (isa($set, 'CODE')) { $set->() for $self->{'mutate'}; } else { $self->{'mutate'} = $set } $self->{'mutate'} %= 12; } $self->{'mutate'}; } package Main; use Benchmark ':all'; my $arg = shift; my $ob = MyOb->new; if ($arg) { cmpthese($arg, { 'naked' => sub { $ob->{'naked'}++, $ob->{'naked'} %= 12 for 1..1 +00 }, 'tie' => sub { ++$ob->{'tie'} for 1..100 }, 'mutate' => sub { $ob->mutate(sub{++$_}) for 1..100 }, 'getnset' => sub { $ob->set($ob->get + 1) for 1..100 }, 'mutate2' => sub { $ob->mutate($ob->mutate + 1) for 1..100 }, }); } else { print 'Naked: '; &{sub { $ob->{'naked'}++, $ob->{'naked'} %= 12 for 1..100 }}; print $ob->{'naked'},"\n"; print 'Tie: '; &{sub { ++$ob->{'tie'} for 1..100 }}; print $ob->{'tie'}, "\n"; print 'Mutate: '; &{sub { $ob->mutate(sub{++$_}) for 1..100 }}; print $ob->mutate, "\n"; print 'GetNSet: '; &{sub { $ob->set($ob->get + 1) for 1..100 }}; print $ob->get, "\n"; $ob->mutate(0); print 'Mutate: '; &{sub { $ob->mutate($ob->mutate + 1) for 1..100 }}; print $ob->mutate, "\n"; }

We're not really tightening our belts, it just feels that way because we're getting fatter.