Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Java > Expensive Recursion + Method Calls?

Reply
Thread Tools

Expensive Recursion + Method Calls?

 
 
Jason Cavett
Guest
Posts: n/a
 
      10-23-2008
In my original code, I implemented a method that searched through a
tree structure to find children of a certain type beneath a parent
node. This is demonstrated in the following method. Note that this
method is in DataModel (which is a node in the tree, and an abstract
class that has to be implemented by any node in the tree).

public <E extends DataModel> List<E> getAllDescendentsOfType(
Class<? extends E> classType, boolean getArchived) {
List<E> foundDescendents = new ArrayList<E>();

for (DataModel child : children) {
if (!child.isArchived() || getArchived) {
if (classType.isInstance(child)) {
foundDescendents.add(classType.cast(child));
}
}

foundDescendents.addAll(child.getAllDescendentsOfT ype(
classType, getArchived));
}

return foundDescendents;
}

This method is relatively quick. (See the next part of my post for
why I'm saying this.)
So, because I wanted to separate the traversal of the tree from what
I'm actually trying to do on the tree (the implementation of an
algorithm), I decided to implement a visitor pattern where the
developer can provide a visitor that will perform whatever task it is
I want performed on a node and the traversal happens in the DataModel
itself. So, something like this...

// In DataModel
public <E extends DataModel> List<E> getAllDescendentsOfType(
Class<E> classType, boolean getArchived) {
AllDescendantsOfTypeVisitor<E> visitor = new
AllDescendantsOfTypeVisitor<E>(
classType, getArchived);
this.visit(visitor);

return visitor.getDescendants();
}

// Also in DataModel
public final void visit(Visitor visitor) {
visitor.startVisit(this);

synchronized (childrenLock) {
// Recurse through the children.
for (DataModel child : children) {
child.visit(visitor);
}
}

visitor.stopVisit(this);
}

// A visitor to find all the descendants of a certain type.
public class AllDescendantsOfTypeVisitor<E extends DataModel>
implements
Visitor {

private Class<E> classType;
private List<E> descendants = new ArrayList<E>();
private boolean archived = false;

public AllDescendantsOfTypeVisitor(Class<E> classType, boolean
archived) {
this.classType = classType;
this.archived = archived;
}

public List<E> getDescendants() {
return descendants;
}

@Override
public void startVisit(DataModel model) {
if (!model.isArchived() || this.archived) {
if (classType.isInstance(model)) {
descendants.add(classType.cast(model));
}
}
}

@Override
public void stopVisit(DataModel model) {
}
}


Basically, I pass in an instance of the visitor to
DataModel.visit(Visitor) and when the visit is completed, this
specific visitor will have the children I am looking for. (And I can
do any task on the tree - searching, preparing for writing, copying,
etc. It doesn't really matter.)

When I switched to this method, the application began running
noticeably slower. After profiling, there is seconds difference
between the first method that I implemented and the visitor method.
Why would this be happening? Are Java method calls so expensive that
I would notice this. (Please note that I do this search a lot since
I'm doing a lot of tree traversal to find things. We're talking
millions of traversals, so if a method is expensive, I would notice
it.)

Or...am I just doing something stupid that makes this more expensive
than it should be? Any help would be appreciated, because this is a
bit exasperating.


Thanks!
 
Reply With Quote
 
 
 
 
Jason Cavett
Guest
Posts: n/a
 
      10-23-2008
On Oct 23, 12:00*pm, Jason Cavett <jason.cav...@gmail.com> wrote:
> In my original code, I implemented a method that searched through a
> tree structure to find children of a certain type beneath a parent
> node. *This is demonstrated in the following method. *Note that this
> method is in DataModel (which is a node in the tree, and an abstract
> class that has to be implemented by any node in the tree).
>
> public <E extends DataModel> List<E> getAllDescendentsOfType(
> * * * * * * Class<? extends E> classType, boolean getArchived) {
> * * * * List<E> foundDescendents = new ArrayList<E>();
>
> * * * * for (DataModel child : children) {
> * * * * * * if (!child.isArchived() || getArchived) {
> * * * * * * * * if (classType.isInstance(child)) {
> * * * * * * * * * * foundDescendents.add(classType.cast(child));
> * * * * * * * * }
> * * * * * * }
>
> * * * * * * foundDescendents.addAll(child.getAllDescendentsOfT ype(
> * * * * * * * * classType, getArchived));
> * * * * }
>
> * * return foundDescendents;
>
> }
>
> This method is relatively quick. *(See the next part of my post for
> why I'm saying this.)
> So, because I wanted to separate the traversal of the tree from what
> I'm actually trying to do on the tree (the implementation of an
> algorithm), I decided to implement a visitor pattern where the
> developer can provide a visitor that will perform whatever task it is
> I want performed on a node and the traversal happens in the DataModel
> itself. *So, something like this...
>
> // In DataModel
> * * public <E extends DataModel> List<E> getAllDescendentsOfType(
> * * * * * * Class<E> classType, boolean getArchived) {
> * * * * AllDescendantsOfTypeVisitor<E> visitor = new
> AllDescendantsOfTypeVisitor<E>(
> * * * * * * * * classType, getArchived);
> * * * * this.visit(visitor);
>
> * * * * return visitor.getDescendants();
> * * }
>
> // Also in DataModel
> * * public final void visit(Visitor visitor) {
> * * * * visitor.startVisit(this);
>
> * * * * synchronized (childrenLock) {
> * * * * * * // Recurse through the children.
> * * * * * * for (DataModel child : children) {
> * * * * * * * * child.visit(visitor);
> * * * * * * }
> * * * * }
>
> * * * * visitor.stopVisit(this);
> * * }
>
> // A visitor to find all the descendants of a certain type.
> public class AllDescendantsOfTypeVisitor<E extends DataModel>
> implements
> * * * * Visitor {
>
> * * private Class<E> classType;
> * * private List<E> descendants = new ArrayList<E>();
> * * private boolean archived = false;
>
> * * public AllDescendantsOfTypeVisitor(Class<E> classType, boolean
> archived) {
> * * * * this.classType = classType;
> * * * * this.archived = archived;
> * * }
>
> * * public List<E> getDescendants() {
> * * * * return descendants;
> * * }
>
> * * @Override
> * * public void startVisit(DataModel model) {
> * * * * if (!model.isArchived() || this.archived) {
> * * * * * * if (classType.isInstance(model)) {
> * * * * * * * * descendants.add(classType.cast(model));
> * * * * * * }
> * * * * }
> * * }
>
> * * @Override
> * * public void stopVisit(DataModel model) {
> * * }
>
> }
>
> Basically, I pass in an instance of the visitor to
> DataModel.visit(Visitor) and when the visit is completed, this
> specific visitor will have the children I am looking for. *(And I can
> do any task on the tree - searching, preparing for writing, copying,
> etc. *It doesn't really matter.)
>
> When I switched to this method, the application began running
> noticeably slower. *After profiling, there is seconds difference
> between the first method that I implemented and the visitor method.
> Why would this be happening? *Are Java method calls so expensive that
> I would notice this. *(Please note that I do this search a lot since
> I'm doing a lot of tree traversal to find things. *We're talking
> millions of traversals, so if a method is expensive, I would notice
> it.)
>
> Or...am I just doing something stupid that makes this more expensive
> than it should be? *Any help would be appreciated, because this is a
> bit exasperating.
>
> Thanks!


Just for the record, I profiled both methods, and the visitor pattern
takes approx. 3 times longer to perform the same task. My guess is
method overhead.
 
Reply With Quote
 
 
 
 
John B. Matthews
Guest
Posts: n/a
 
      10-23-2008
In article
<83663190-bae3-4a6b-8921->,
Jason Cavett <> wrote:

> On Oct 23, 12:00*pm, Jason Cavett <jason.cav...@gmail.com> wrote:
> > In my original code, I implemented a method that searched through a
> > tree structure to find children of a certain type beneath a parent
> > node. *This is demonstrated in the following method. *Note that
> > this method is in DataModel (which is a node in the tree, and an
> > abstract class that has to be implemented by any node in the tree).

[...]
> > * * * * synchronized (childrenLock) {

[...]
> Just for the record, I profiled both methods, and the visitor pattern
> takes approx. 3 times longer to perform the same task. My guess is
> method overhead.


Was the original synchronized, also?

--
John B. Matthews
trashgod at gmail dot com
http://home.roadrunner.com/~jbmatthews/
 
Reply With Quote
 
Jason Cavett
Guest
Posts: n/a
 
      10-23-2008
On Oct 23, 2:39*pm, "John B. Matthews" <nos...@nospam.invalid> wrote:
> In article
> <83663190-bae3-4a6b-8921-d3a0dc60a...@26g2000hsk.googlegroups.com>,
> *Jason Cavett <jason.cav...@gmail.com> wrote:
>
>
>
> > On Oct 23, 12:00*pm, Jason Cavett <jason.cav...@gmail.com> wrote:
> > > In my original code, I implemented a method that searched through a
> > > tree structure to find children of a certain type beneath a parent
> > > node. *This is demonstrated in the following method. *Note that
> > > this method is in DataModel (which is a node in the tree, and an
> > > abstract class that has to be implemented by any node in the tree).

> [...]
> > > * * * * synchronized (childrenLock) {

> [...]
> > Just for the record, I profiled both methods, and the visitor pattern
> > takes approx. 3 times longer to perform the same task. *My guess is
> > method overhead.

>
> Was the original synchronized, also?
>
> --
> John B. Matthews
> trashgod at gmail dot comhttp://home.roadrunner.com/~jbmatthews/


Ah. Yes it was. I forgot to put that in the second method when I was
typing into the newsgroup. Sorry about that. (And, for the record, I
tried to take out the synchronization block just to see what would
happen, and there were no speed improvements.)
 
Reply With Quote
 
Jason Cavett
Guest
Posts: n/a
 
      10-23-2008
On Oct 23, 2:45*pm, Jason Cavett <jason.cav...@gmail.com> wrote:
> On Oct 23, 2:39*pm, "John B. Matthews" <nos...@nospam.invalid> wrote:
>
>
>
> > In article
> > <83663190-bae3-4a6b-8921-d3a0dc60a...@26g2000hsk.googlegroups.com>,
> > *Jason Cavett <jason.cav...@gmail.com> wrote:

>
> > > On Oct 23, 12:00*pm, Jason Cavett <jason.cav...@gmail.com> wrote:
> > > > In my original code, I implemented a method that searched through a
> > > > tree structure to find children of a certain type beneath a parent
> > > > node. *This is demonstrated in the following method. *Note that
> > > > this method is in DataModel (which is a node in the tree, and an
> > > > abstract class that has to be implemented by any node in the tree).

> > [...]
> > > > * * * * synchronized (childrenLock) {

> > [...]
> > > Just for the record, I profiled both methods, and the visitor pattern
> > > takes approx. 3 times longer to perform the same task. *My guess is
> > > method overhead.

>
> > Was the original synchronized, also?

>
> > --
> > John B. Matthews
> > trashgod at gmail dot comhttp://home.roadrunner.com/~jbmatthews/

>
> Ah. *Yes it was. *I forgot to put that in the second method when I was
> typing into the newsgroup. *Sorry about that. *(And, for the record, I
> tried to take out the synchronization block just to see what would
> happen, and there were no speed improvements.)


So, I've now also tried to use a stack to implement recursion and came
up with this.

public final void visit(Visitor visitor) {
Deque<DataModel> stack = new ArrayDeque<DataModel>();
stack.push(this);

while (stack.size() > 0) {
DataModel current = stack.pop();
visitor.startVisit(current);

Enumeration<DataModel> children = current.children();
while (children.hasMoreElements()) {
stack.push(children.nextElement());
}
}
}

However, although it's slightly faster (via profiling), there's no
real noticeable change. The only method being called here is
startVisit (I don't use stopVisit right now, so I decided to remove it
for the moment). This removes the need for synchronization which is
nice, but that's about it.

I can't figure out why this is THAT much slower.
 
Reply With Quote
 
John B. Matthews
Guest
Posts: n/a
 
      10-23-2008
In article
<05699536-8456-4c5f-9463->,
Jason Cavett <> wrote:
> On Oct 23, 2:45*pm, Jason Cavett <jason.cav...@gmail.com> wrote:
> > On Oct 23, 2:39*pm, "John B. Matthews" <nos...@nospam.invalid> wrote:
> > > In article
> > > <83663190-bae3-4a6b-8921-d3a0dc60a...@26g2000hsk.googlegroups.com>,
> > > *Jason Cavett <jason.cav...@gmail.com> wrote:

> >
> > > > On Oct 23, 12:00*pm, Jason Cavett <jason.cav...@gmail.com> wrote:
> > > > > In my original code, I implemented a method that searched through a
> > > > > tree structure to find children of a certain type beneath a parent
> > > > > node. *This is demonstrated in the following method. *Note that
> > > > > this method is in DataModel (which is a node in the tree, and an
> > > > > abstract class that has to be implemented by any node in the tree).
> > > [...]
> > > > > * * * * synchronized (childrenLock) {
> > > [...]
> > > > Just for the record, I profiled both methods, and the visitor pattern
> > > > takes approx. 3 times longer to perform the same task. *My guess is
> > > > method overhead.

> >
> > > Was the original synchronized, also?

[...]
> > Ah. *Yes it was. *I forgot to put that in the second method when I was
> > typing into the newsgroup. *Sorry about that. *(And, for the record, I
> > tried to take out the synchronization block just to see what would
> > happen, and there were no speed improvements.)

>
> So, I've now also tried to use a stack to implement recursion and came
> up with this.
>
> public final void visit(Visitor visitor) {
> Deque<DataModel> stack = new ArrayDeque<DataModel>();
> stack.push(this);
>
> while (stack.size() > 0) {
> DataModel current = stack.pop();
> visitor.startVisit(current);
>
> Enumeration<DataModel> children = current.children();
> while (children.hasMoreElements()) {
> stack.push(children.nextElement());
> }
> }
> }
>
> However, although it's slightly faster (via profiling), there's no
> real noticeable change. The only method being called here is
> startVisit (I don't use stopVisit right now, so I decided to remove it
> for the moment). This removes the need for synchronization which is
> nice, but that's about it.
>
> I can't figure out why this is THAT much slower.


I would expect the double dispatch to be only fractionally slower.
Presumably, your original getAllDescendentsOfType() traversed the data
model recursively, examining each node only once. Is it possible that
your visitor is visiting some of the same nodes repeatedly?

--
John B. Matthews
trashgod at gmail dot com
http://home.roadrunner.com/~jbmatthews/
 
Reply With Quote
 
Mark Space
Guest
Posts: n/a
 
      10-23-2008
Jason Cavett wrote:

> However, although it's slightly faster (via profiling), there's no
> real noticeable change. The only method being called here is
> startVisit (I don't use stopVisit right now, so I decided to remove it
> for the moment). This removes the need for synchronization which is
> nice, but that's about it.
>
> I can't figure out why this is THAT much slower.



I'm interested too, but I'm feeling too lazy right now to implent the
code for profiling. Could you post a full working version with both
algoritms? Please include a method to fill the tree to a reasonable
size with random data. I could be motivated to profile that myself and
then take time to try some optimizations.
 
Reply With Quote
 
Jason Cavett
Guest
Posts: n/a
 
      10-24-2008
On Oct 23, 5:37*pm, Mark Space <marksp...@sbcglobal.net> wrote:
> Jason Cavett wrote:
> > However, although it's slightly faster (via profiling), there's no
> > real noticeable change. *The only method being called here is
> > startVisit (I don't use stopVisit right now, so I decided to remove it
> > for the moment). *This removes the need for synchronization which is
> > nice, but that's about it.

>
> > I can't figure out why this is THAT much slower.

>
> I'm interested too, but I'm feeling too lazy right now to implent the
> code for profiling. *Could you post a full working version with both
> algoritms? *Please include a method to fill the tree to a reasonable
> size with random data. *I could be motivated to profile that myself and
> then take time to try some optimizations.


So, it turns out that the problem was related to a locking issue -
nothing even to do with this visitor. I figured it out and fixed
locking and now everything works at a good speed again.

Thanks anyway!
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
va_arg... recursion: changing arguments and the using recursion jononanon@googlemail.com C Programming 8 04-26-2012 08:37 PM
Recursion in a class method Iain Barnett Ruby 2 10-13-2010 12:30 PM
method def in method vs method def in block Kyung won Cheon Ruby 0 11-21-2008 08:48 AM
Recursion Method Problem Mike ASP .Net 5 12-14-2006 10:36 PM
Wrapped method causing infinite recursion in rcov Pedro Côrte-Real Ruby 8 08-07-2006 05:38 PM



Advertisments
 



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57