Blog Post
Hi luk, very technical answer about the "copyOf" helper method ahead:
FreeMarker, being a templating language, was primarily designed to convert prepared data into a view, rather than work heavily with data itself, so to be more efficient with memory and computation, while reducing stateful bugs, all the data structures are immutable, and combining them reuses each portion instead of copying (persistent data structure). This is not a common practice in other languages, so it can present some performance gotchas when trying to use FreeMarker like a programming language.
What all that means here is that internally in memory, a single sequence might actually be implemented as a tree. Each time you do "seq = seq + [x]" you are creating a new tree, with "seq" (which itself might be a tree) as the left child and a new 1-element sequence containing "x" as the right child. So if this is done in a loop, at the end you will have a tall tree of very tiny sequences. Such a structure was faster for FreeMarker to create, since no data needed to be copied around, and it means that the old sequences are still intact (safe to use). This is usually called structural sharing and can be good, but there is a tradeoff: most reads or changes you make will pay extra costs to traverse the tree. These costs can really add up—multiply, actually—so we found this to be a common performance hotspot, especially in those libraries that have general methods like "splice", "unique", "sort", etc. After all, FreeMarker isn't built to casually manipulate arrays like JavaScript or C#. (Side note: some languages like Scala present both types of data structures and expect the programmer to choose between them.)
Nutshell: I recommend that if you build a sequence that you plan to use more than twice, you can use "copyOf" one time after you have built it to make sure things stay fast. This will fully traverse the sequence and copy all its values into a fresh sequence backed by an array. All operations on the new sequence will be optimal, and if you choose to concatenate it into a new sequence or cut out a subsequence, those new derived sequences will also see some benefits. Our "lab tests" have produced very good performance results using "copyOf" inside of some of the more complicated operations we see in some customizations.
There is some additional info in the FreeMarker FAQ.