Blog Post
luk, kudos for being conscientious about performance. Toolbox has a template profiling feature ("Performance" tab), so it should be useful, especially if you move utility methods to separate templates and <#import> them where they are needed. However, Toolbox will only give you the min, max, and mean runtimes, so in a sandbox you can experiment with test data and measure using ".now?long", placed into an <!-- HTML comment --> or console.log (with ?js_string). To do your own real-world reporting, you might have to get clever with JS.
In general, even without using the "copyOf" method, there are often ways to avoid transforming sequences via iterative concatenation by using sequence built-ins instead. Here is a slightly-modified example of a real customer "splice" function (delete and/or insert some items) that we saw creating deep trees on every invocation and causing slow page loads:
<#function splice items index deletedLength=0 insertedItems=[]> <#local result = []> <#list items as item> <#if (deletedLength > 0)> <#if (item_index >= index) && (item_index < index + deletedLength)> <#if item_index == index> <#local result = result + insertedItems> </#if> <#else> <#local result = result + [item]> </#if> <#else> <#if (item_index == index)> <#local result = result + insertedItems> </#if> <#local result = result + [item]> </#if> </#list> <#return result /> </#function>
Often when the function returned a sequence, that sequence was then used to run through additional complex functions (like "splice" again) that created even deeper trees. I contributed this reimplementation:
<#function splice items index deletedLength=0 insertedItems=[]> <#if insertedItems?has_content> <#return items[0 ..! index] + insertedItems + items[(index + deletedLength) ..]> <#elseif deletedLength > 0> <#return items[0 ..! index] + items[(index + deletedLength) ..]> <#else> <#return items> </#if> </#function>
This algorithm creates between 0 and 2 new tree nodes instead of N. In all cases, it completes the task faster, uses less memory, and even without using "copyOf" afterwards, using the returned result will be faster. This kind of win isn't always possible, but I think we are open to improving FreeMarker performance with more utility methods when there's a clear need. You might have noticed the other sequence utility methods (unique, remove, removeAll); each was added when we noticed common patterns that were slowing down customizations.