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.