Sporkmonger

purveyor of fabulously ambiguous eating utensils

A Question

Posted by sporkmonger
Written October 22nd, 2006

Lately, this has become something of a design pattern for me: A base class describes a type of behavior, and rather than subclasses inheriting the base class’s behavior (which tend to just raise NotImplementedErrors), the subclasses override the base class’s methods to do their own thing. The catch, of course, is that some of the base class’s methods are not, in fact, just stubs. Some of them actually dispatch the message just received to each of the subclasses. So if you send GentleCMS::Cache the :clear message, that message will actually get relayed to GentleCMS::ResponseCache, GentleCMS::ResourceCache, and GentleCMS::RouteCache, thus clearing out all caching systems within GentleCMS. This allows for either selective or indiscriminate clearing of the cache. Very useful.

The problem is that my method for finding subclasses is slow, and I was hoping that you, Dear Readers, might have some suggestions for how I might improve the performance of this method:

1
2
3
4
5
6
7
8
9
10
11
12
13

class Module #:nodoc:
  # Returns a list of modules and classes that descend from this module.
  def descendants
    descendant_modules = []
    ObjectSpace.each_object do |object|
      next if !object.kind_of? Module
      next if object == self
      descendant_modules << object if object.ancestors.include? self
    end
    return descendant_modules
  end
end

I tend to cache the results of calling this method, so it doesn’t have a huge performance hit during normal operation, but startup times have become rather surprisingly long.

Update: I discovered that ObjectSpace.each_object could take an optional type parameter. The code runs much faster now:

1
2
3
4
5
6
7
8
9
10
11
12

class Module #:nodoc:
  # Returns a list of modules and classes that descend from this module.
  def descendants
    descendant_modules = []
    ObjectSpace.each_object(Module) do |object|
      next if object == self
      descendant_modules << object if object.ancestors.include? self
    end
    return descendant_modules
  end
end

Update: Cool, that took the worst-case startup times down from 12 seconds to a worst-case startup time of 3 seconds. Not bad, a 400% overall performance improvement from changing two lines of code. I can live with that.

  1. Brian Brian :
    Written October 24th, 2006 at 10:57 PM

    I have the same issue for STI subclasses on ActiveRecord objects. I added @@descendents to the SuperClass with an accessor add_descendent. Then each subclass calls superclass.add_descendent(self). Looking for a better technique still.

  2. Written October 25th, 2006 at 02:34 AM

    Yeah, that technique works, but there’s no doubt but that it’s ugly. I suppose it’s not as bad if add_descendent is a protected method, but even then…

    I certainly wouldn’t mind it if someone figured out an even more efficient way of doing it than what I’m using now.

  3. Written October 25th, 2006 at 01:13 PM

    You could create an inherited method in the superclass. It will be called automatically when you create the subclass.

    Your comment spam protection won’t seem to let me post any code.

Leave a Response

NOTE: I'm afraid Javascript needs to be on in order to comment.

Comments should be formatted using Textile.

Ruby code should be enclosed within a <macro:code lang="ruby"> element. Other languages are supported. For output you can simply omit the lang attribute.