The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
Singing the Praises of Benchmark

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Jamis Buck

Posts: 184
Nickname: minam
Registered: Oct, 2004

Jamis Buck is a C/Java software developer for BYU, and hacks in Ruby for fun.
Singing the Praises of Benchmark Posted: Jun 10, 2005 8:33 PM
Reply to this message Reply

This post originated from an RSS feed registered with Ruby Buzz by Jamis Buck.
Original Post: Singing the Praises of Benchmark
Feed Title: the buckblogs here
Feed URL: http://weblog.jamisbuck.org/blog.cgi/programming/index.rss
Feed Description: Jamis Buck's corner of the blogging universe. Mostly about ruby, but includes ramblings on a variety of topics.
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by Jamis Buck
Latest Posts From the buckblogs here

Advertisement

So, I wanted a way to know whether the contents of a String had been modified. Something like:

  s = "hello world" 
  assert !s.dirty?
  s << " and goodbye!" 
  assert s.dirty?

A few ideas came to mind, and I found myself gravitating towards one of them, but I began to wonder which one would perform the best. Again, I had my suspicions, but I wrote up a benchmark anyway. And I am certainly glad I did because my intuition was way off.

One trick I’d used before, where I had an object instance that I wanted to enhance, was to put the enhancement code in a module and then use Object#extend to add the module to the instance in question. Something like:

  module DirtyDetector
    METHODS = [:<<, :"[]=", :capitalize!, :chomp!, :chop!, :concat, :delete!,
      :downcase!, :gsub!, :insert, :lstrip!, :next!, :replace, :reverse!,
      :rstrip!, :slice!, :squeeze!, :strip!, :sub!, :succ!, :swapcase!, :tr!,
      :tr_s!, :upcase!]

    def self.extend_object(base)
      me = class <<base; self; end
      METHODS.each do |method|
        me.class_eval { alias_method :"unprotected_#{method}", :"#{method}" }
      end
      super
    end

    def dirty?
      @dirty == true
    end

    METHODS.each do |method|
      define_method(method) do |*args|
        @dirty = true
        send("unprotected_#{method}", *args)
      end
    end
  end

  s = "hello world" 
  s.extend DirtyDetector
  s << " and goodbye!" 
  assert s.dirty?

This works (more or less—I haven’t actually tested the above code as I’m giving it to you). However, the whole “class_eval” thing in #extend_object made a little uneasy—seemed like a lot of invocations. I wondered if it would be more efficient to collect the alias_method calls into a big string and then eval them all at once:

  ALIASES = METHODS.inject("") do |aliases, method|
    aliases << "alias_method :\"unprotected_#{method}\", :\"#{method}\"\n" 
  end

  def self.extend_object(base)
    me = class <<base; self; end
    me.class_eval ALIASES
    super
  end

This was actually 50% slower than the other! It was obvious, once I saw the results, though—each eval has to parse the string again, which is not going to be an inexpensive operation. So, I wondered, could I find a way to pre-compile the string?

  ALIASER = eval "Proc.new do\n#{ALIASES}\nend" 

  def self.extend_object(base)
    me = class <<base; self; end
    me.class_eval &ALIASER
    super
  end

That was much better, roughly twice as fast—and even faster than the first version. But it was still an order of magnitude slower than doing without the DirtyDetector. Having to alias each of those methods and add the new functionality to the instance is just not a very practical way to go about this. But what other option was there?

Perhaps you saw the solution before I even started into this article. Bear with me, sometimes it takes a while before I can see the obvious, but I’ll get there in the end.

I considered using Delegator, but I wanted the new instance to still be identifiable as a String (that is to say, String === s should still be true). The solution was to simply extend String and override the methods in question in the subclass:

  class DirtyDetectorString < String
    METHODS = [:<<, :"[]=", :capitalize!, :chomp!, :chop!, :concat, :delete!,
      :downcase!, :gsub!, :insert, :lstrip!, :next!, :replace, :reverse!,
      :rstrip!, :slice!, :squeeze!, :strip!, :sub!, :succ!, :swapcase!, :tr!,
      :tr_s!, :upcase!]

    METHODS.each do |method|
      define_method(method) do |*args|
        @dirty = true
        super
      end
    end

    def dirty?
      @dirty == true
    end
  end

  s = DirtyDetectorString.new("hello world")
  assert !s.dirty?
  s << " and goodbye!" 
  assert s.dirty?

And the best news? It performed as fast as the native string operations! Thank goodness for the benchmark library.

Read: Singing the Praises of Benchmark

Topic: First test results: ADODB Adapter for Rails Previous Topic   Next Topic Topic: Improving ActiveRecord Part 2

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use