Showing posts with label visitor. Show all posts
Showing posts with label visitor. Show all posts

Tuesday, March 25, 2008

Optimizing Reflective Visitor with Runtime Compilation

Last night I had the pleasure of doing some optimizations I'd been wanting to do for some time to the core visitor model relationship in webseer. Basically, all of webseer runs on the visitor pattern and a specially magical reflective visitor (see previous blog on this) that does runtime method lookup and invocation. As a quick reminder, I want to be able to write

public class MyVisitor extends ReflectiveSuperVisitor {
public void visit(HTMLTag tag) {
// do something
}
}

pass this to any object that accepts visitors and have it just run that code on anything that matches a HTMLTag. It's just a generic visitor pattern with the added coolness that there is no compile-time linking so there is no need to implement a particular visitor interface or have the model know about what visitors will visit it. In my mind, this is how the visitor pattern should be. Otherwise, it becomes too tedious for its own good in large systems.

I cache the runtime method lookups (essentially the same lookup that Java does at compile time to link to the right method) so that part isn't that intensive over many calls, but you still have to deal with a reflective method invocation. Because this is so core to all the transformations and feature extraction in webseer, I knew this had to be fundamentally faster for anyone to take it seriously.

A while back I read this great article that describes how to convert reflective calls into runtime compiled method calls. It fit perfectly and with about 15 lines of code of Javassist I was able to dramatically cut down the time of method invocation. I didn't get quite as dramatic speedups as his results (he was doing reflective lookups and several method invocations and it's possible that JVMs are faster at this now), but in my simple test it more than doubled the speed of the calls. I also changed the method invocations to take both the visitor and the acceptor so there can be one for each class in a static cache as opposed to per visitor like it was before - this cuts down both on memory overhead as well as GC time. Essentially I now dynamically generate classes that look like this:

public class MyVisitorvisitHTMLTag implements MethodCaller {

public void callFor(ReflectiveSuperVisitor visitor, Acceptor tag) {
((MyVisitor)visitor).visit((HTMLTag)tag);
}

}


Now when you call visitor.visit(this); in an accept(SuperVisitor) method, after the first lookup, it performs the following steps:
  1. invoke ReflectiveSuperVisitor.visit(Acceptor) - SUPER FAST
  2. get hashcode of runtime visitor class - SUPER FAST
  3. lookup MethodCaller in hashtable - FAST
  4. invoke MethodCaller - SUPER FAST
  5. invoke correct visit method - SUPER FAST
Now obviously, this is still slower than:
  1. invoke correct visit method - SUPER FAST
but compared to the reflective invocation which replaced steps 4 and 5 with a reflective call, it's much more acceptable.

Friday, July 13, 2007

Reflection-based Visitor

In WebSeer, the program that does my web analysis, the main architecture is based on the concept of visitors analyzing models. So you go to a URL, convert the data stream into a local, easy-to-use object, and then run some analysis depending on the application. If you don't understand what the visitor pattern is, I advise you Google it and come back. I've been using the pattern for five years very heavily so I sort of take it for granted in my writing.

My existing problem is that I have several different types of visitors that are used for different purposes. So let's create an example - say I have a graph based model of something, with types Graph, Node, and Edge. Then I have a GraphVisitor that has three methods: visit(Graph), visit(Node), and visit(Edge). The accept methods in Graph, Node, and Edge take care of the default traversal. Now conveniently, I can print out all the edges in the graph or something else without having to care about the actual graph structure in my visitor code. Great...simple visitor pattern.

Now in my library, I have several different types of models, lets say I have another one that's Tree. Both Tree and Graph inherit the Model interface. Now I want to create a visitor that visits a list of Models without caring what type they are. We sort of have a problem. I can create a ModelVisitor, add an accept(ModelVisitor) method in the Model interface, and then check the type of the ModelVisitor and if it's also a GraphVisitor for instance, then do the component traversal. But that's messy. For the last long while, that's what I've been doing. However, in more interesting cases, it gets silly. You have to write accept methods for every type of visitor that could visit the node. Being as this is a visitor-centric architecture, this gets ridiculous even with inheritance. For instance, my TextNodeImpl class recently looked like:

public class TextNodeImpl implements TextNode {

...

public void accept(TextVisitor visitor) {
visitor.visit(this);
}

public void accept(DataSourceVisitor visitor) {
visitor.visit(this);
}

public void accept(TreeVisitor visitor) {
visitor.visit(this);
}

public void accept(MutableTextVisitor visitor) {
visitor.visit(this);
}

}

Kinda crazy, right?

A while back I played around with reflection-based visitors, but I think I took it way too far (I also made the accepting objects reflection-based) and it got hard to understand because there was very little type-checking. However, I revisited it last night and I think I found a good balance.

All the visitors implement the SuperVisitor interface. You can either use a more type-safe generics version:

public interface SuperVisitor {

public boolean canVisit(Class<?> dataClass);

public <T> DataSourceVisitor<T>
getVisitor(Class<T> dataClass);

}

where DataSourceVisitor just has one "visit(T object) " method.

or the more handy and yet not as clean way:

public interface SuperVisitor {

public void visit(Object object);

}

where the visitor will attempt to visit any object and just fail quietly if it can't.

Then in AbstractSuperVisitor which takes care of the reflection, you initialize a Map with a mapping of requested Class to DataSourceVisitor. It initially puts in all the visit methods that are found in the runtime class. Then when a visitor for a particular runtime class is requested, you walk up the class hierarchy tree to find the appropriate visit method(s) that has already been initialized. Then you insert that new mapping into the tree. This caching is necessary to cut down lookup time, since reflection is pretty slow. And while this does create an extra object for each visit method, plus a Map for every Visitor pattern, you rarely have so many visitors that this would matter too much. So the primary cost is the cost of reflection based invocation. If this cost is important, I advise you look into articles that discuss how to replace reflection with byte code generation, which would work perfectly here.

So what this allows is that I can now create a Visitor that can visit any type of object on the fly. Let's say I want to print out everything this visitor comes across. Just put in a visit(Object o) method with a print line in the method and all the visit calls will be resolved to this method.

In addition, I now only need a single accept method in all my model classes, which calls the generic visit on itself and then passes the visitor to its components.

public class Graph {

...

public void accept(SuperVisitor visitor) {
visitor.visit(this);

for (Edge edge : edges) {
edge.accept(visitor);
}
}

}

The design loss is in the loss of cohesion between the visitor and the things it's visiting. However, this can be mitigated by still using particular visitor interfaces that are sort of fake visitor guidelines for methods it should implement. So I can still have a GraphVisitor which enforces some methods on the visitor implementation, however, the Graph object will only see it as a SuperVisitor.

Overall, I think this is a neat solution to a model-based architecture where you have a lot of visitors interesting in different aspects of complex models. I also added a preVisit and a postVisit which gives me more control over post-children processing for instance. Hopefully this will be a good enough model for WebSeer.