1

Analyzing some pre-existing third-party Java code (which cannot be re-written) in an application, I discovered that it is almost entirely structured on long method chains, of the form

public Object process() {
  // Do some class-specific stuff
  return this.getChild().process();
}

where there is a base class defining the process() method, inherited by subclasses that redefine that method and the getChild() call is repeated at the end.

Multiple such chains are created (branched off) at runtime as a result of conditional blocks, some of which may return relatively early (e.g., after 10-20 "links" in the chain), but most commonly the "length" of the chain is much bigger and can even exceed 100 consecutive method calls, before returning the result.

The application uses this code to process large files, with results changing for every invocation of the top level method, usually once every different line of each file.

So, two questions:

  • What performance implications should be expected from such a design, compared to a "regular" one?
  • Is there any trick to notably improve performance of that code as-is (e.g., by changing some JVM parameter)?
PNS
  • 19,295
  • 32
  • 96
  • 143
  • Why do you think this is a performance problem? Do you have profiling data to back up your concern? What would be a "regular" architecture? It's not at all clear what you are asking here. – Jim Garrison Dec 07 '17 at 22:43
  • By "regular" I mean an architecture with short method chains. The question is about assessing any performance implications. If the consensus turns out to be "no performance hit is expected", so be it. – PNS Dec 07 '17 at 22:45
  • 1
    There's no "long method chain" vs "short method chain" distinction, you can get a deep call stack either way. Worrying about performance due to the call depth is probably a waste of time unless you've profiled the system and identified a bottleneck. – Jim Garrison Dec 07 '17 at 22:50

2 Answers2

2

Method calls are very fast (10s or 100s of millions per second), so make sure it's actually beneficial first by profiling the system.

Otherwise, you can use caching to improve efficiency, especially if your results are consistent (same input, same output).

Ex:

Object cache = null;

public Object process() {
   if (cache == null)
      cache = this.getChild().process();

   return cache;
}

If there's some kind of input or state involved, then you could use a Map for the cache which uses keys of the input value and stores values of the output value.

Dave Cousineau
  • 12,154
  • 8
  • 64
  • 80
  • Yes, that is an obvious (as well as clever) option, but not applicable in this case, since the code is used for processing large files and the result changes on every line. I clarified that in the question. Thanks and +1 anyway. :-) – PNS Dec 07 '17 at 22:35
  • @PNS needs more details. there must be something that you could cache. otherwise, method calls are very fast (10s of millions per second) so it's likely you don't need to worry about it. – Dave Cousineau Dec 07 '17 at 22:59
  • IMO "very fast" and 10s or 100s of millions per second are contradictions. Meanwhile normal things like adding two integers can be done several *billions* of times per second, over 10 billion if you have a nice CPU. – harold Dec 08 '17 at 16:41
  • @harold yes, speed is relative, but there's no reason to worry about 100 method calls when you can make a half million in a millisecond – Dave Cousineau Dec 08 '17 at 17:09
1

A statement of the form

return getChild().process();

is not different to

SomeType var = getChild();
return var.process();

In both cases, one method will be invoked after the other and the return value of getChild() will be used as receiver of the process() call.

If you are hunting for irrelevant minor technical details, the first form doesn’t need a local variable. But since in HotSpot, a fixed-size stack memory is pre-allocated on thread creation, it is impossible to perceive any performce difference between these two forms.

The choice between these two forms is purely a stylistic one.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • I think his concern is related to the fact that it's a *recursive* call, sometimes going 100 levels deep, not just that it calls two methods one after the other. – Dave Cousineau Dec 08 '17 at 17:02
  • @Holger unless yet again some JVM does something funny with chaining https://www.google.com/url?sa=t&source=web&rct=j&url=https://stackoverflow.com/questions/44334233/why-is-the-stringbuilder-chaining-pattern-sb-appendx-appendy-faster-than-reg&ved=0ahUKEwib8Z_X8vrXAhUIyRQKHSKlBXIQjjgIKjAD&usg=AOvVaw0MaL8em5mv7EFA0l0onMn1 :) – Eugene Dec 08 '17 at 17:06
  • @DaveCousineau: that’s the point of this answer, *there is no recursion*. These two chained invocations are not different to two invocations with an intermediate assignment. Once you understand that, you will understand that this applies to one hundred chained invocations as well. They still are not different to a sequence of unchained invocations. Writing it down with one hundred invocations would just clutter the answer. – Holger Dec 11 '17 at 07:12
  • @Eugene: but that’s not really related to chaining, but to optimizing the more common pattern. – Holger Dec 11 '17 at 07:15