Metaprogramming.apply(Aspects)

Posted by Mathew Abonyi Wed, 10 Oct 2007 01:51:00 GMT

I’m just briefly breaking my silence (which must continue a bit longer) to raise awareness about an impressive and very useful gem which has just come onto the scene: Aquarium.

Although I don’t follow Aspect Oriented Programming very closely, I know about its benefits and use it when necessary. All Rails developers, for example, employ its principles without even knowing it: before, after and around filters, proxies to alter execution, callbacks and the like. If you don’t know about AOP yet, basically it’s the practice of separating components which appear across the various classes of an application to provide system or class-agnostic functionality—for example, logging, authentication or object persistence.

Having these components intrude on, for example, calculating a bank balance really gets annoying after a while. Imagine every moderately sized script you write needing lots of diagnostic messages, custom event handlers, and the like. It gets very tedious because you know there’s the ideal of reuse just around the corner.

Clean, Ruby-esque AOP… a Pipe Dream?

Imagine methods without calls to a logger, without a litter of ‘if/then/else’ for authentication or authorisation, without calls to save an object and checking if it returns true or false… What you’ll get is the raw functionality of your class or module, which… umm, is as it should be. Wrapping a method (using cuts, wrapping or aspects from AOP) lets you extract common functionality which cuts across objects.

Until now, there’s only been the Facets libraries for AOP (‘advice’, ‘aop’ and ‘cut’ in Facets 2.0.0) and AspectR. The former is buggy at best and the latter is like returning to Java. So I wasn’t very keen on AOP in Ruby at all. Metaprogramming seemed easy enough and didn’t need the structure of AOP to follow.

Enter Aquarium. It’s a glass of clean water in a desert. Ruby-style. The most important thing it offers is a ‘Join Point Model’ which will make you wet. In other words, it is very easy to intercept method calls to wrap them and introduce functionality before or after the original definition.

Sadly, I don’t have the time for an extensive example from my own coding, so I’ll have to rip off the example from Aquarium’s own README which I think illustrates its power best (with a few modifications to show how I use it at the moment): method tracing.

Example: Method Tracing


require 'rubygems'
require 'aquarium'
require 'aquarium/aspects/dsl/object_dsl' # makes the Aspects DSL (before/after/around) system-wide

module Example
  class Foo
    def initialize *args
      p "Inside:   Foo#initialize: args = #{args.inspect}"
    end
    def do_it *args
      p "Inside:   Foo#do_it: args = #{args.inspect}"
    end
  end

  module BarModule
    def initialize *args
      p "Inside:   BarModule#initialize: args = #{args.inspect}"
    end
    def do_something_else *args
      p "Inside:   BarModule#do_something_else: args = #{args.inspect}"
    end
  end

  class Bar
    include BarModule
  end
end

p "Before advising the methods:"
foo1 = Example::Foo.new :a1, :a2
foo1.do_it :b1, :b2

bar1 = Example::Bar.new :a3, :a4
bar1.do_something_else :b3, :b4

# EXAMPLE 1: Wrapping methods on multiple classes using the powerful 'types' matcher.
# It can take constants or regular expressions. Also demonstrates method_options, which
# can specify public, protected, private, inherited or local methods.

module Example
  include Aquarium::Aspects::DSL::AspectDSL
  around :types => [Foo, Bar], :methods => :all, 
  :method_options => :suppress_ancestor_methods do |jp, *args|
    begin
      p "Ex. 1: Entering: #{jp.target_type.name}##{jp.method_name}: args = #{args.inspect}"
      jp.proceed
    ensure
      p "Ex. 1: Leaving:  #{jp.target_type.name}##{jp.method_name}: args = #{args.inspect}"
    end
  end
end

# EXAMPLE 2: Wrapping one or more methods very simply.
module Example
  class Bar
    after :do_something_else do |jp, *args|
      p "Ex. 2: Simply after: #{jp.target_type.name}##{jp.method_name}: args = #{args.inspect}"
    end
  end
end

# EXAMPLE 3: Using the Aspect class without the DSL

include Aquarium::Aspects
Aspect.new :before, :method => :do_it, :type => Example::Foo do |jp, *args|
  p "Ex. 3: Simply before (using Aspect.new): #{jp.target_type.name}##{jp.method_name}: args = #{args.inspect}"
end

# EXAMPLE 4: Contain aspects within a namespace for clarity. These are executed
# upon loading the source.

module Example
  module AllAspects
    include Aquarium::Aspects::DSL::AspectDSL
    before :do_it, :type => Example::Foo do |jp, *args|
      p "Ex. 4: Simply before (using namespaces): #{jp.target_type.name}##{jp.method_name}: args = #{args.inspect}"
    end
  end
end

# And now see the result...
puts
p "After advising the methods. Notice that #intialize isn't advised:"
foo2 = Example::Foo.new :a5, :a6
foo2.do_it :b5, :b6

bar1 = Example::Bar.new :a7, :a8
bar1.do_something_else :b7, :b8

You could argue that the namespace for Aquarium needs a little work and the availability of Aspect.new system wide should be a given, but adding that oneself until Aquarium has it is no big deal. I also didn’t demonstrate pointcuts, another option to around, before and after. With a pointcut, you can encapsulate the type, scope and method names—perhaps as a constant, class variable or class method to reduce dependencies.

Overview

All in all, the DSL is already miles better than anything else out there and Aquarium was only released to the public months ago. To summarise, I think this DSL is killer because it is…

  • clear about what is affected
  • easy to separate from what is affected
  • rich with selectors (type, scope, name, pointcut)
  • able to choose whether to alter method execution

So, I would definitely suggest giving it a go and seeing how it can modularise some of your ‘cross cutting concerns’. I also suggest the Aquarium README and the reading recommended on its site, http://aquarium.rubyforge.org/.

Posted in ,  | Tags , , , , ,  | 2 comments | 1 trackback

Surprise 10min Benchmark: eval, class_eval, instance_eval, define_method, bind

Posted by Mathew Abonyi Thu, 11 Jan 2007 08:42:37 GMT

What is the fastest way to dynamically define a method? define_method? class_eval? instance_eval? With strings or procs? Maybe we can pull out some arcane magic and use instance_method and bind (depending on what we want to do).

We all need to use some kind of eval at some point and, while it will not usually effect performance when used sparingly, sometimes it just has to make a serious appearance. When I saw how slow RSpec is in comparison to the venerable Test::Unit, I discovered the way it handles its contexts: each one is individually instance_eval’d into an empty ContextEvalModule. It isn’t the only reason for being slower, but it is one and one which can really mount up on big projects.

So, moving on from RSpec, Test::Unit and my baseline, MicroTest (see previous post), I became perversely interested in benchmarking all the ways of doing it and found some surprises.

Results (Ruby 1.8.4)

                            user     system      total        real
eval/str              112.540000   0.060000 112.600000 (112.615607)
instance_eval/str      99.510000   0.040000  99.550000 ( 99.564661)
class_eval/str        112.750000   0.060000 112.810000 (112.824374)
instance_eval/proc     56.750000   0.040000  56.790000 ( 56.802483)
class_eval/proc        56.440000   0.020000  56.460000 ( 56.464818)
define_method/class   112.420000   0.060000 112.480000 (112.489430)
define_method/send    111.540000   0.050000 111.590000 (111.610502)
def/unbind/bind        73.750000   0.060000  73.810000 ( 73.875800)
def                    52.670000   0.030000  52.700000 ( 52.705886)
method/bind            13.520000   0.010000  13.530000 ( 13.523194)

Analysis

I put it roughly in the order which I originally expected, with the slowest at the top.

The first thing that stunned me was the speed of define_method. It’s as slow as a snail. It’s practically eval, despite using a proc. I’ve been using it in preference before, thinking it was faster than any of the evals (proc or string). Apparently not.

The second revelation was how proc-based evals are about 50% faster than a def/unbind/bind cycle, which only has def as the evaluating part. That any eval is close to def is simply surprising. The instance_eval variety is the one used by RSpec, so I guess they picked the best one already.

The third shock was how slow def is! Granted, this is a lot of iterations, but it should be quicker than that, surely?

So it seems pretty plain that method unbinding as a tuning technique is rubbish, despite not evaluating anything, whereas a proc-based instance_eval or simple def are the clear winners.

But wait… what about Ruby 1.8.5?

Seeing the decrepitude of define_method made me wonder about the latest release of Ruby. I downloaded it, compiled it, got it working and ran the same bench:

Results (Ruby 1.8.5)

                            user     system      total        real
instance_eval/str     109.620000   0.050000 109.670000 (109.685393)
class_eval/str        113.840000   0.060000 113.900000 (113.910749)
eval/str              114.520000   0.060000 114.580000 (114.596238)
define_method/class    70.220000   0.040000  70.260000 ( 70.264506)
define_method/send     68.030000   0.040000  68.070000 ( 68.066688)
def/unbind/bind        73.690000   0.030000  73.720000 ( 73.741549)
instance_eval/proc     59.050000   0.030000  59.080000 ( 59.084445)
class_eval/proc        59.530000   0.030000  59.560000 ( 59.566885)
def                    52.870000   0.030000  52.900000 ( 52.895184)
method/bind            12.470000   0.010000  12.480000 ( 12.484069)

Anaylsis

I guess they fixed the problem and more besides. define_method is now marginally faster than def/unbind/bind (which has not budged an inch). I think I’ll continue to take the performance hit on define_method with 1.8.4, knowing I’ll want to upgrade later in the future and not change techniques.

The old recommendations hold true

  • instance_eval with a proc for class, instance or singleton evaluation
  • instance_eval for changing proc bindings
  • define_method for converting procs into methods
  • def for everything else
  • eval when you really know you need it

Note: These findings are only for def meth; true; end and may vary in complex cases.

Benchmark Code

require 'benchmark'

class A; def meth1; true; end; end

# defining using eval/class/instance
str = "def meth2; true; end"
a_binding = A.send(:binding)

# proc for class/instance
proc1 = Proc.new { def meth2; true; end }

# proc for define_method
$proc2 = Proc.new { true }

# unbound method for bind
um = A.instance_method(:meth1)

# number of iterations
n = 12000 * 600

Benchmark.bm(22) do |x|

  x.report('instance_eval/str') do
    for i in 1..n; A.instance_eval(str); end
  end

  x.report('class_eval/str') do
    for i in 1..n; A.class_eval(str); end
  end

  x.report('eval/str') do
    for i in 1..n; eval(str, a_binding); end
  end

  x.report('define_method/class') do
    for i in 1..n; class A; define_method(:meth2, &$proc2); end; end
  end

  x.report('define_method/send') do
    for i in 1..n; A.send(:define_method, :meth2, &$proc2); end
  end

  x.report('def/unbind/bind') do
    for i in 1..n
      class A; def meth2; true; end; end
      A.instance_method(:meth2).bind(A.new)
    end
  end

  x.report('instance_eval/proc') do
    for i in 1..n; A.instance_eval(&proc1); end
  end

  x.report('class_eval/proc') do
    for i in 1..n; A.class_eval(&proc1); end
  end

  x.report('def') do
    for i in 1..n; class A; def meth2; true; end; end; end
  end

  x.report('method/bind') do
    for i in 1..n; um.bind(A.new); end
  end

end

Posted in ,  | no comments | no trackbacks

Older posts: 1 2 3 ... 5