Cache Evaluation Aspect
In the previous section we reviewed how to add a cache to our Fibonacci class. Now that we have the cache, a good question that may arise is: how effective is the cache?. In this section we will se how to define a new aspect: a cache evaluation aspect; that will monitor the execution of another aspect: the cache aspect.
First, we need to define how we are going to measure the cache efectiveness. The alternative we will take is to compare how many times the get method was called and how many times the cache aspect called proceed (this represents a cache miss). Then, we can calculate the cache efectiveness with the following formula:
cache efectiveness (%) = (theCalls - theMisses) / theCalls) * 100
That represents the percentage of cache hits ever the total get invocations.
As we have already developed a cache aspect, we will reuse its code (the version we chose is the one that uses the Fibonacci instances to hold the cached values):
SMetaobject theSMO = new CacheElementsSMO(); SLink theSLink = API.links().createSLink( new NameCS("reflex.examples.fibonacci.app.Fibonacci"), theSMO ); Hookset theHookset = new PrimitiveHookset( MsgReceive.class, new NameCS("reflex.examples.fibonacci.app.Fibonacci"), new NameOS("get") ); MODefinition theMO = new MODefinition.SharedMO(new GlobalCacheHandler()); BLink theCacheLink = Links.get(theHookset, theMO); theCacheLink.setControl(Control.AROUND); theCacheLink.setCall(Control.AROUND, GlobalCacheHandler.class.getName(), "lookCache", new Parameter[]{ Parameter.CONTEXT_OBJECT, new StdParameters.IndexedArgument(0), Parameter.CLOSURE });
(A detailled description of this code can be found in the previous section)
The metaobject we will use to record the statistics is defined this way:
public static class EvalCacheMO{ private int itsCalls = 0; private int itsCacheMisses = 0; public void enterGet(){ //record that the get method was called } public Object cacheMiss(IExecutionPointClosure aClosure){ //record that the cache failed and proceed } public int getCacheMisses(){ return itsCacheMisses; } public int getCalls(){ return itsCalls; } }
As you can see, it only holds the two values representing the number of calls to the get method and the number of cache misses. We will review now the how the methods enterGet and cacheMiss are invoked and what they do in their bodies.
First, the metaobject definition:
EvalCacheMO theEvalCacheMO = new EvalCacheMO(); MODefinition theEvalMO = new MODefinition.SharedMO(theEvalCacheMO);
Now, to make the enterGet method be called, we put a link with BEFORE
at the get method:
BLink theEnterGetLink = Links.get(theHookset, theEvalMO); theEnterGetLink.setControl(Control.BEFORE); theEnterGetLink.setCall(Control.BEFORE, EvalCacheMO.class.getName(), "enterGet");
This code is self-explanatory: It declares a link, with BEFORE
control, using the hookset that matches the get method, that calls the enterGet
method is called without arguments.
Now, and this is the difficult part, we need to make the cacheMiss
method be called:
BLink theCacheMissLink = Links.get(theHookset, theEvalMO); theCacheMissLink.setControl(Control.AROUND); theCacheMissLink.setCall( Control.AROUND, EvalCacheMO.class.getName(), "cacheMiss", Parameter.CLOSURE) );
Here we create a link, with AROUND
control: it will replace the original operation, in our case, the get method. And when the hookset is reached, the cacheMiss method is called with a closure as parameter. To understand this, let us make a resume of the behavioral links we have at the moment:
- theCacheLink:
AROUND
control - theEnterGetLink:
BEFORE
control - theCacheMissLink:
AROUND
control
The first two links can be composed without problems: first behavior assosiated with theEnterGetLink
is executed, then the behavior assosiated with theCacheLink
.
But in this case, wo have two links with AROUND
control, then, composition rules must be specified by the developer.
In our case we want the cacheMiss
method to be invoked when the cache aspect invokes proceed
over its closure. This is exactly what the Wrap rule does: it creates a chain in the execution flow conditioned to the execution of proceed by the previous metaobject in the chain.
Then, there is only one configuration instruction remaining:
API.rules().addRule(new Wrap(theCacheLink, theCacheMissLink));
{info}
The code above specifies that the theCacheLink
will wrap theCacheMissLink
. {info}
Finally, we add a shutdown hook at the virtual machine level to print the cache statistics right before the end of the program (in a real application, the EvalCacheMO instance can be stored in a way that statistics can be printed at any time):
Runtime.getRuntime().addShutdownHook(new Thread(){ public void run(){ int theCalls = theEvalCacheMO.getCalls(); int theMisses = theEvalCacheMO.getCacheMisses(); System.out.println("------ Cache Evaluation ---------"); System.out.println(" Method invocations : " + theCalls); System.out.println(" Cache misses : " + theMisses); System.out.println(" Cache effectiveness: " + ((theCalls - theMisses) * 100.0 / theCalls) + "%"); System.out.println("---------------------------------"); } });
The complete code of this example is here. To run it, the command is:
Windows %java -classpath "lib\javassist.jar;build\reflex-core.jar;build\reflex-examples.jar" reflex.examples.tutorial.CacheEvaluationAspect Linux %java -classpath "lib/javassist.jar:build/reflex-core.jar:build/reflex-examples.jar" reflex.examples.tutorial.CacheEvaluationAspect
If everithing went ok, the output should be something like:
The fibonacci[5] is 5 The fibonacci[6] is 8 ------ Cache Evaluation --------- Method invocations : 20 Cache misses : 13 Cache effectiveness: 35.0% ---------------------------------
You can see here that the effectiveness of our cache strategy is 35%. Not so bad .