String Concatenation performance test
August 19, 2008
I've always tried to avoid using the underlying Java if I can do the same thing with CFML. This is because (as far as I know) it is undocumented so you may have issues if you try to run your code on one of the other CFML engines.
Anyway, I recently needed to do some string concatenation with large strings, which is notoriously slow in ColdFusion, so I thought I run some tests to compare performance of various ways to do it. Here is my test code.
<cfset iterations = 1000 />
<cfset sampletext = RepeatString("Lorem ipsum dolor sit amet, consectetuer adipiscing elit.", 10) />
<!--- concatenate with coldfusion --->
<cfset startTime = getTickCount() />
<cfset cfstring = "" />
<cfloop from="1" to="#iterations#" index="i">
<cfset cfstring = cfstring & sampletext />
</cfloop>
<cfset endTime = getTickCount() />
<cfoutput>
concatenate with coldfusion : #endTime-startTime#ms<br />
</cfoutput>
<!--- listappend coldfusion --->
<cfset startTime = getTickCount() />
<cfset cflist = "" />
<cfloop from="1" to="#iterations#" index="i">
<cfset cflist = ListAppend( cflist, sampletext, Chr(1) ) />
</cfloop>
<cfset cfstring = ListChangeDelims( cflist, "", Chr(1) ) />
<cfset endTime = getTickCount() />
<cfoutput>
listappend with coldfusion : #endTime-startTime#ms<br />
</cfoutput>
<!--- arrayappend coldfusion --->
<cfset startTime = getTickCount() />
<cfset cfarray = ArrayNew(1) />
<cfloop from="1" to="#iterations#" index="i">
<cfset ArrayAppend( cfarray, sampletext ) />
</cfloop>
<cfset cfstring = ArrayToList( cfarray, "" ) />
<cfset endTime = getTickCount() />
<cfoutput>
arrayappend with coldfusion : #endTime-startTime#ms<br />
</cfoutput>
<!--- concatenate with cfsavecontent --->
<cfset startTime = getTickCount() />
<cfsavecontent variable="cfstring"><cfoutput><cfloop from="1" to="#iterations#" index="i">#sampletext#</cfloop></cfoutput></cfsavecontent>
<cfset endTime = getTickCount() />
<cfoutput>
coldfusion with cfsavecontent: #endTime-startTime#ms<br />
</cfoutput>
<!--- concatenate with java stringbuffer --->
<cfset startTime = getTickCount() />
<cfset oStringBuffer = createObject( "java", "java.lang.StringBuffer" ).init() />
<cfloop from="1" to="#iterations#" index="i">
<cfset oStringBuffer.append( sampletext ) />
</cfloop>
<cfset cfstring = oStringBuffer.toString() />
<cfset endTime = getTickCount() />
<cfoutput>
concatenate with java stringbuffer : #endTime-startTime#ms<br />
</cfoutput>
<!--- concatenate with java StringBuilder --->
<cfset startTime = getTickCount() />
<cfset oBuilder = createObject( "java", "java.lang.StringBuilder" ).init() />
<cfloop from="1" to="#iterations#" index="i">
<cfset oBuilder.append( sampletext ) />
</cfloop>
<cfset cfstring = oBuilder.toString() />
<cfset endTime = getTickCount() />
<cfoutput>
concatenate with java stringbuilder : #endTime-startTime#ms<br />
</cfoutput>
I ran this on ColdFusion 8.01 Developer Edition (which is the equivalent of ColdFusion 8.01 Enterprise). The results are really interesting.
concatenate with coldfusion : 6813ms
listappend with coldfusion : 21609ms
arrayappend with coldfusion : 47ms
coldfusion with save content: 47ms
concatenate with java stringbuffer : 63ms
concatenate with java StringBuilder : 62ms
I'm really surprised by this as ColdFusion is built of Java but as you can see, the fastest way to concatenate strings in ColdFusion is to use ArrayAppend and not Java Strings. It also shows that you should avoid using ListAppend.
- Posted in:
- ColdFusion
24 comments
Leave a comment
If you found this post useful, interesting or just plain wrong, let me know - I like feedback :)





ColdFusion already has all the objects initialized with it, so when you call a list Append for example, you're just invoking the method, but the object is already there.
Try to invoke the objects first outside of the counter and then just invoke the methods inside of the counter. That should really change things.
Cheers
Comment by Marcos Placona – August 19, 2008
That could explain the difference in performance. However, I think it is fair to include the time it takes to initialize the Java String object as you would have to do this in your code if you wanted to do it that way.
Comment by John Whish – August 19, 2008
concatenate with coldfusion : 6875ms
listappend with coldfusion : 24328ms
arrayappend with coldfusion : 47ms
concatenate with java stringbuffer : 109ms
concatenate with java StringBuilder : 63ms
I then ran it again and also excluded the .toString() method which made the biggest difference. The results are:
concatenate with coldfusion : 7375ms
listappend with coldfusion : 24360ms
arrayappend with coldfusion : 47ms
concatenate with java stringbuffer : 46ms
concatenate with java StringBuilder : 47ms
ArrayAppend still holds up really well.
Comment by John Whish – August 19, 2008
Comment by Marcos Placona – August 19, 2008
concatenate with coldfusion : 421ms
listappend with coldfusion : 1579ms
arrayappend with coldfusion : 0ms
concatenate with java stringbuffer : 0ms
concatenate with java StringBuilder : 15ms
and here are the results for same code but 10,000 iterations:
concatenate with coldfusion : 87250ms
listappend with coldfusion : 453312ms
arrayappend with coldfusion : 78ms
concatenate with java stringbuffer : 125ms
concatenate with java StringBuilder : 110ms
Comment by Ed – August 19, 2008
I think I'll try on CF8.01 Standard to see if there is a difference.
Comment by John Whish – August 19, 2008
Comment by Ed – August 19, 2008
coldfusion with save content: 16ms
<cfset startTime = getTickCount() />
<cfsaveContent variable="sampleText">
<cfoutput>
<cfloop from="1" to="#iterations#" index="i">
#sampletext#
</cfloop>
</cfoutput>
</cfsaveContent>
<cfset endTime = getTickCount() />
<cfoutput>
coldfusion with save content: #endTime-startTime#ms<br />
</cfoutput>
This is with CFMX 7.02 on an WinXP (sp2) box with 2 gig of ram.
Generally string buffer and string builder are very similar, although string builder is optimized for performance. However there is a risk in using it since it is not thread safe.
Here's a discussion of of these issues recently on CF-Talk. www.houseoffusion.com/groups/cf-talk/thread.cfm/threadid:56583
/>
regards,
larry
Comment by larry c. lyons – August 19, 2008
Thanks for the comment. Would you mind posting the results for all the tests to see if there is any difference between CF8.01 and CF7.02.
I've seen people use cfsavecontent but heard that it can cause Java Heap errors, so didn't include that one, but you're right I should add it. I've updated my post (I removed the carriage returns from your code so that it returns the same string as the other tests.)
For subscribers here are my results:
concatenate with coldfusion : 6797ms
listappend with coldfusion : 21344ms
arrayappend with coldfusion : 47ms
coldfusion with save content: 47ms
concatenate with java stringbuffer : 62ms
concatenate with java StringBuilder : 63ms
Comment by John Whish – August 20, 2008
<cfset cfstring = cfstring & sampletext />
to the new CF syntax of:
<cfset cfstring &= sampletext />
The results are:
concatenate with coldfusion : 6813ms
concatenate with coldfusion &= : 6265ms
Comment by John Whish – August 20, 2008
Pentium 4 3.00GHz
1GB Ram
concatenate with coldfusion : 641ms
listappend with coldfusion : 2047ms
arrayappend with coldfusion : 15ms
coldfusion with cfsavecontent: 0ms
concatenate with java stringbuffer : 32ms
concatenate with java stringbuilder : 31ms
Comment by Tim Rubel – September 04, 2008
Would you mind posting the results for 10,000 iterations? My tests were done on my dev box which is Windows 2000 SP4, CF8.01, 1GB RAM, 2 x 1Ghz Pentium III.
Comment by John Whish – September 04, 2008
Comment by Tim Rubel – September 04, 2008
Comment by John Whish – September 05, 2008
Comment by Tim Rubel – September 05, 2008
<cfset runtime = CreateObject("java", "java.lang.Runtime").getRuntime() />
<cfset runtime.gc() />
I should point out that (as I understand it) it is the garbage collection that makes the server run slowly.
Comment by John Whish – September 05, 2008
Comment by Tim Rubel – September 05, 2008
Hope that helps.
Comment by John Whish – September 05, 2008
Comment by Tim Rubel – September 05, 2008
concatenate with coldfusion : 74640ms
listappend with coldfusion : 349782ms
arrayappend with coldfusion : 94ms
coldfusion with cfsavecontent: 422ms
concatenate with java stringbuffer : 515ms
concatenate with java stringbuilder : 578ms
concatenate with coldfusion : 302687ms
listappend with coldfusion : 626172ms
arrayappend with coldfusion : 141ms
coldfusion with cfsavecontent: 109ms
concatenate with java stringbuffer : 172ms
concatenate with java stringbuilder : 203ms
Comment by Tim Rubel – September 05, 2008
Comment by John Whish – September 08, 2008
(changed the string size / repitition)
All values are in ms.
All test were repeated 500 times in random order to calculate the averages.
ColdFusion service was restarted before each test.
My machine:
(2x xeon 3ghz 5160, 4gb ecc, cf8.01 developer, sata raid0, vista business)
concatenate below is using &= method.
------------------------------
Test 1 - small strings, large repitition:
Added 6 characters to a string 10,000 times.
-- average:
concatenate : 351.83
listAppend : 346.90
arrayAppend : 5.56
cfsavecontent: 2.54
java.lang.StringBuffer : 31.84
java.lang.StringBuilder : 27.12
-- average (less slowest and fastest 15%):
concatenate : 344.91
listAppend : 344.72
arrayAppend : 5.32
cfsavecontent: 2.45
java.lang.StringBuffer : 27.02
java.lang.StringBuilder : 26.92
-- slowest:
concatenate : 779
listAppend : 410
arrayAppend : 13
cfsavecontent: 11
java.lang.StringBuffer : 396
java.lang.StringBuilder : 34
-- fastest:
concatenate : 334
listAppend : 333
arrayAppend : 5
cfsavecontent: 2
java.lang.StringBuffer : 26
java.lang.StringBuilder : 25
------------------------------
Test 2 - medium strings, medium repitition:
Added 12 characters to a string 5,000 times.
-- average:
concatenate : 174.27
listAppend : 174.44
arrayAppend : 2.70
cfsavecontent: 1.59
java.lang.StringBuffer : 14.20
java.lang.StringBuilder : 14.02
-- average (less slowest and fastest 15%):
concatenate : 173.23
listAppend : 173.18
arrayAppend : 2.67
cfsavecontent: 1.47
java.lang.StringBuffer : 13.97
java.lang.StringBuilder : 13.88
-- slowest:
concatenate : 224
listAppend : 209
arrayAppend : 9
cfsavecontent: 8
java.lang.StringBuffer : 21
java.lang.StringBuilder : 21
-- fastest:
concatenate : 166
listAppend : 168
arrayAppend : 2
cfsavecontent: 1
java.lang.StringBuffer : 13
java.lang.StringBuilder : 13
------------------------------
Test 3 - large strings, small repitition:
Added 24 characters to a string 2,500 times.
-- average:
concatenate : 88.21
listAppend : 88.55
arrayAppend : 1.47
cfsavecontent: 1.04
java.lang.StringBuffer : 7.32
java.lang.StringBuilder : 7.30
-- average (less slowest and fastest 15%):
concatenate : 87.22
listAppend : 87.32
arrayAppend : 1.46
cfsavecontent: 1.02
java.lang.StringBuffer : 7.17
java.lang.StringBuilder : 7.05
-- slowest:
concatenate : 118
listAppend : 147
arrayAppend : 2
cfsavecontent: 2
java.lang.StringBuffer : 15
java.lang.StringBuilder : 22
-- fastest:
concatenate : 82
listAppend : 83
arrayAppend : 1
cfsavecontent: 0
java.lang.StringBuffer : 6
java.lang.StringBuilder : 6
It seems cfsavecontent is the winner!
Comment by Mike Causer – January 09, 2009
Here's the code I used:
<!--- Vector append --->
<cfset startTime = getTickCount() />
<CFSet SampleArray = [SampleText]><!--- The AddAll function requires an array (a collection actually). --->
<cfset objVector = createObject( "java", "java.util.Vector" ) /><!--- Include object creation time... negligible. --->
<cfloop from="1" to="#iterations#" index="i">
<CFSet objVector.AddAll(SampleArray)>
</cfloop>
<cfset cfstring = ArrayToList(objVector.toArray(), "") /><!--- If you use toString() then you end up with extra characters in the resulting string. This way works (can be improved on I'd guess). --->
<cfset endTime = getTickCount() />
<cfoutput>
Vector Append : #endTime-startTime#ms (#Len(cfstring)#) <br />
</cfoutput>
In order to rank them I scrapped the idea of measuring the time to do each of the tests because the ListAppend was so slow and memory hogging that I couldn't get the iterations high enough to create a significant difference between the faster methods. i.e. When the ArrayAppend takes 141ms and the CFSaveContent takes 109ms I don't consider that significant enough as the server could have been doing something else at the time. To solve that you can run the tests multiple times, OR you can increase the iterations significantly. I ended up with 900,000 iterations to get the times up high enough to notice a significant difference.
In order to go that high I had to disable the slower tests, because they simply would never finish!
So, I found that the ArrayAppend and the CFSaveContent were the fastest, with the CFSaveContent being generally faster. They did 900,000 iterations in about 4000ms each, creating a string of 513 million characters each. However, from execution to execution they would actually switch position as to who was faster (it's one of my production boxes so I'm sure there is some other stuff happening). Either way, it's good enough for me to continue using ArrayAppend over CFSaveContent because of the possible JVM heap concerns about CFSaveContent (which gave a heap error at 1 million iterations, and ArrayAppend kept going until 1.6 million iterations and survived 850 million characters), and besides, it isn't significantly faster anyway - at least, it isn't significantly faster with under a billion characters.
BTW this CF8 instance has 6GB allocated to it.
Steven
Comment by Steven Van Gemert – May 06, 2009
Comment by John Whish – May 06, 2009