I am taking a stab at profiling Velocity using Jinsight (from IBM alphaWorks).
For a test case, I basically turned Jon's speed test servlet into a
standalone program by liberally cutting and pasting from
Example.java. Output goes to a StringWriter. I call the whole mess
twice so the second time Velocity.init() should be fast and all the
templates should be cached. I am focusing on the second-time
performance.
I have some preliminary results and some questions.
- The big news is that nothing jumps out as a major time sink. This
is good because it means there are no major bloopers, but bad because
there is nothing to cherry pick.
- ASTReference.render() is called a lot (I guess that's no surprise).
It builds its output by using String concatenation, for example
writer.write(NodeUtils.specialText(getFirstToken()) + prefix +
value.toString());
This creates a temp StringBuffer, appends three strings to it and
converts using toString().
Changing that one line to the three separate writes
writer.write(NodeUtils.specialText(getFirstToken()));
writer.write(prefix);
writer.write(value.toString());
saves about 17% of the time spent in this method. All the other uses
of + in this method can be changed too.
This assumes that writes are fast, for StringWriter they are,
probably for BufferedWriter too.
Some minor improvements:
- In Parse.render(), the check of the template stack depth is pretty
expensive. It takes about 20% of the time of the whole method! The
time is split between
InternalContextAdapterImpl.getTemplateNameStack() and
Runtime.getInt(). I rewrote Configuration.getInteger() to avoid
creating an Integer and that helps a little.
- ResourceManager.getResource() calls
globalCache.containsKey(resourceName) followed by globalCache.get()
if the containsKey succeeds. I replaced these with just a get().
OK, those are the easy ones. Here are some other possibilities:
- About 40% of the time is spent in ASTReference.execute(), any
improvements here could have a big effect. One thing that stands out
here is the overhead of layering through InternalContextAdapter ->
AbstractContext -> VelocityContext -> HashMap to actually get a value
or a method.
- I am seeing a few places where caching a value in a node could be
beneficial. For example, ASTNumberLiteral.value() parses its integer
value on each call. This is a relatively expensive operation. Another
example is ASTSetDirective.render() which calls String.substring() to
get the identifier name.
I know that caching can be problematic in multi-threaded code, so I'm
not comfortable caching a value in a node at render time. I was
wondering if there is any time during parsing where the cached value
can be computed? It would have to be after the child nodes have been
added.
ASTNumberLiteral could cache the integer value, that at least avoids
the issues with cached references. Can anyone tell me if this code is
thread-safe? Or how to make it thread-safe?
private boolean cached = false;
private int cachedValue;
public Object value( InternalContextAdapter context)
{
if (!cached) {
cachedValue = Integer.parseInt(getFirstToken().image);
cached = true;
}
return new Integer(cachedValue);
}
Finally, here are diffs for ASTReferenceNode.java, Configuration.java
and ResourceManager.java.
Kent Johnson Transparent Language, Inc.
[EMAIL PROTECTED] http://www.transparent.com
--- d:/dev/jakarta-velocity/old/ASTReference.java Wed Apr 18 12:03:15 2001
+++ ASTReference.java Wed Apr 18 14:51:45 2001
@@ -215,10 +215,19 @@
if ( escaped )
{
if ( value == null )
- writer.write( NodeUtils.specialText(getFirstToken())
+ prefix + "\\" + nullString );
+ {
+ writer.write( NodeUtils.specialText(getFirstToken()) );
+ writer.write( prefix );
+ writer.write( "\\" );
+ writer.write( nullString );
+ }
else
- writer.write( NodeUtils.specialText(getFirstToken())
+ prefix + nullString );
-
+ {
+ writer.write( NodeUtils.specialText(getFirstToken()) );
+ writer.write( prefix );
+ writer.write( nullString );
+ }
+
return true;
}
@@ -232,14 +241,19 @@
* write prefix twice, because it's shmoo, so the \
don't escape each other...
*/
- writer.write(NodeUtils.specialText(getFirstToken()) +
prefix + prefix + nullString);
+ writer.write( NodeUtils.specialText(getFirstToken()) );
+ writer.write( prefix );
+ writer.write( prefix );
+ writer.write( nullString );
if (referenceType != QUIET_REFERENCE &&
Runtime.getBoolean(
RuntimeConstants.RUNTIME_LOG_REFERENCE_LOG_INVALID, true) )
Runtime.warn(new ReferenceException("reference :
template = " + context.getCurrentTemplateName(), this));
}
else
{
- writer.write(NodeUtils.specialText(getFirstToken()) +
prefix + value.toString());
+ writer.write(NodeUtils.specialText(getFirstToken()));
+ writer.write(prefix);
+ writer.write(value.toString());
}
return true;
--- d:/dev/jakarta-velocity/old/Configuration.java Wed Apr 18 13:52:53 2001
+++ Configuration.java Wed Apr 18 13:58:05 2001
@@ -1485,7 +1485,10 @@
public int getInteger(String key,
int defaultValue)
{
- return getInteger(key, new Integer(defaultValue)).intValue();
+ Integer i = getInteger(key, null);
+ if (i == null)
+ return defaultValue;
+ return i.intValue();
}
/**
--- d:/dev/jakarta-velocity/old/ResourceManager.java Wed Apr 18
14:19:48 2001
+++ ResourceManager.java Wed Apr 18 14:23:07 2001
@@ -218,8 +218,6 @@
public static Resource getResource(String resourceName, int resourceType)
throws ResourceNotFoundException, ParseErrorException, Exception
{
- Resource resource = null;
-
/*
* Check to see if the resource was placed in the cache.
* If it was placed in the cache then we will use
@@ -227,10 +225,9 @@
* will load it.
*/
- if (globalCache.containsKey(resourceName))
+ Resource resource = (Resource) globalCache.get(resourceName);
+ if (resource != null)
{
- resource = (Resource) globalCache.get(resourceName);
-
/*
* The resource knows whether it needs to be checked
* or not, and the resource's loader can check to