We have a lot of FTL code that performs hash concatenation like this:
<#local event_data = event_data + {
'subaction_name': subactionName
} />
However, these constructs cause some overhead due to the following code (the freemarker.core.AddConcatExpression class’ _getAsTemplateModel(Environment env) method):
try {
String s1 = getStringValue(leftModel, left, env);
if(s1 == null) s1 = "null";
String s2 = getStringValue(rightModel, right, env);
if(s2 == null) s2 = "null";
return new SimpleScalar(s1.concat(s2));
} catch (NonStringException e) {
if (leftModel instanceof TemplateHashModel && rightModel instanceof TemplateHashModel) {
if (leftModel instanceof TemplateHashModelEx && rightModel instanceof TemplateHashModelEx) {
TemplateHashModelEx leftModelEx = (TemplateHashModelEx)leftModel;
TemplateHashModelEx rightModelEx = (TemplateHashModelEx)rightModel;
if (leftModelEx.size() == 0) {
return rightModelEx;
} else if (rightModelEx.size() == 0) {
return leftModelEx;
} else {
return new ConcatenatedHashEx(leftModelEx, rightModelEx);
}
} else {
return new ConcatenatedHash((TemplateHashModel)leftModel,
(TemplateHashModel)rightModel);
}
} else {
throw e;
}
}
because the getStringValue method throws NonStringExceptions in such cases. The NonStringException, in its turn, inherits the following constructor logic from the TemplateException:
super(getDescription(description, cause));
causeException = cause;
this.env = env;
if(env != null)
{
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
env.outputInstructionStack(pw);
pw.flush();
ftlInstructionStack = sw.toString();
}
else
{
ftlInstructionStack = "";
}
which fetches a full instruction stack each time regardless further handling.
This leads to up to 50 ms of execution time spent on the exception constructors per page request in our case, which is quite critical for the overall throughput.
Could anyone give some advices on how to avoid these exceptions without touching the FTL code, please? Or, maybe, it’s possible to get some kind of a patch, which would move the TemplateModelHash instanceof check before the catch block in the AddConcatExpression._getAsTemplateModel method?
EDIT: freemarker version 2.3.19
Try to use the latest stable FreeMarker release (2.3.25 at the moment), and see if the speed will be acceptable. A commit in 2013-06-13 says:
This reduces TemplateException creation resource usage further (as far as it will be silently handled), as the final message from the blamed expression, tips and so on is only assembled when the message is indeed needed.
2.3.19 was released earlier than that, on 2012-02-29.
Update: I have committed into 2.3.26-nightly so that falling back to hash addition doesn't rely on exception throwing/catching at all. Not sure how dominant this was in your performance problem, but it was inefficient for sure.