Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[aspectj-users] Performance facts and tips

Below are heuristics for avoiding unnecessary performance
overhead in programs using our implementation of AspectJ 1.1.

- Are they right?

- Is anyone interested in writing better examples?
  Or in writing code to quantify the effects?

- Are there other heuristics?

Results from this discussion will go at least into the FAQ,
and perhaps into the programming guide or fixes for 1.1.X/1.2.

I also want to know what the standards are for a good AspectJ
program so that we can ask whether there are cases where the
performance of a good AspectJ program built with our
implementation is not on par with an equivalent solution written
by hand in Java or implemented with an alternative AOP framework.
Any experience reports on that point are also welcome!

Thanks -
Wes

-------- Heuristics
As with any performance heuristic, use these only if profiling
shows you have a problem and only if they don't change the
semantics of your program in bad ways.

---- writing pointcuts

o Prefer static to dynamic pointcuts

The dynamic pointcut designators are if(), this(), target(),
args(), cflow() and cflowbelow().

The compiler/weaver can resolve static pointcuts, so there is
no cost at runtime to determine whether advice should run.
The static pointcuts do mean something different, so they should
only be used when they're correct.

Examples:
  - within(..) v. this(..)
  - withincode(..) v. cflow(..)
  ...


o Restrict the scope of dynamic pointcuts with static pointcuts

You can limit the cost of determining whether advice should run
by restricting the points at which the dynamic test is run.

Examples:
  - call(void run()) && target(Foo)
  - within(com.company.app..*) && printingCalls()


o Restrict wildcarded pointcuts

Some wildcarded pointcut match a great many join points,
e.g., "call(* *(..))".  Used alone, this is rarely what's intended.


o Change the code to be more susceptible to pointcuts

Sometimes it's possible and easier to refactor the target code
to write a better pointcut; this can avoid dynamic tests.


---- writing advice

o Prefer thisJoinPointStaticPart (or neither) to thisJoinPoint

Using thisJoinPoint involves some runtime reflection.


o Prefer binding variables with target(), args() or this()
  to using thisJoinPoint.getArgs()

For the same reason as above...


o Prefer after returning to after

"after" advice is more costly than "after returning";
in most cases, users actually intend "after returning".


o Avoid {re}calculating things where possible

This is straight Java, but can have a lot of impact in advice
that runs frequently.

Examples:
  - deferring String concatenation until use
  - using lazy construction
  - caching results...


---- writing inter-type declarations

o Avoiding maps

Maps can be used to associate state with a particular instance
or thread.  AspectJ provides inter-type member declarations and
"per" aspects that can do (and optimize) the same associations,
avoiding the hashing or the downcasts in your code.

  o Prefer perthis or pertarget when only using per-instance
    state in one aspect with the one master pointcut

  o Prefer private inter-type declarations to mapping from targets

  o Prefer percflow to mapping to Thread or using ThreadLocal

---- writing aspects

o Prefer default (issingleton) aspects, other things being equal


---- comment

Most of these heuristics are not worth saying without
more compelling examples.

I'm actually not sure that maps are always worse, but I've
seen bad code that uses maps when the AspectJ form was faster
and clearer.  It may have just been unnecessary complexity.

The advice heuristics reduce the runtime work done by preferring
cheaper alternatives; they're mostly straight Java.

The pointcut heuristics should reduce runtime costs
(and make compiles faster) by more strictly specifying the
join point shadows specified (or not) by the static pointcut
designators.  The dynamic pointcut designators are if(),
this(), target(), args(), cflow() and cflowbelow().

However, the dynamic checks should be efficient:

- this(), target() and args() are implemented by 'instanceof'
  checks, which have been optimized in most modern VM's

- cflow() and cflowbelow() are comparable to using ThreadLocal's

- if() is arbitrarily expensive, depending on the code invoked.

There's one exception to prefering static forms.  Given Type,
a subclass of SuperType, and foo(), defined in SuperType:

 static form:  call(* Type.foo(..))
 dynamic form: target(Type) && call(* foo(..))

Some older compilers incorrectly replaced the static reference
type of a method invocation with the type of the nearest
superclass implementation.  In that case, when weaving
binaries generated by older compilers, the method invocation
would not be matched by the first/static pointcut, but would
be matched by the second/dynamic one.  If it's what the
user intended, it could be changed to use the static form

    call(* SuperType.foo(..))




Back to the top