Monday 31 December 2012

Don't optimise your code (annexe)

G'day (again):
In the previous article I alluded to the fact I had more to say on the approach I had to creating my test strings for those experiments. This'll just be short, as it's a variation on the same theme, and I just thought I'd share this with you.

For the previous tests I saved a million-char randomly generated string to test on. Whilst writing the code for this, I noticed some significant performance differences in various different techniques I was using to build the string.  And these are perhaps worth remembering, as far as potential optimisations go.


Here's a simplification of the code I used to generate these strings. I've take the randomness out as it's irrelevant to what I'm showing here, plus I just create the string, I don't save it to disk:


This is similar to the other code, in that it allows some options:
  • magnitude of string size (a power of ten)
  • a method to create the string. One of:
    • String
    • StringBuffer
    • cfsavecontent
And the three handlers are as follows:

// string.cfm
startTime = getTickCount();
s = "";
for (i=1; i <= stringLength; i++){
    s &= "X";
}
endTime = getTickCount();

// stringBuffer.cfm
startTime = getTickCount();
sb = createObject("java", "java.lang.StringBuffer").init(stringLength);
for (i=1; i <= stringLength; i++){
    sb.append("X");
}
s = sb.ToString();
endTime = getTickCount();

<!---cfsavecontent.cfm --->
<cfset startTime = getTickCount()>
<cfset i=1>
<cfsetting enablecfoutputonly="true">
<cfsavecontent variable="s">
    <cfloop condition="i LE stringLength"><!--- doing it this way to best emulate the for() loops in the other examples --->
        <cfoutput>X</cfoutput>
        <cfset i=i+1>
    </cfloop>
</cfsavecontent>
<cfsetting enablecfoutputonly="false">
<cfset endTime = getTickCount()>

Generating a million-char string using the string.cfm method was not viable, so I instead tested with a magnitude of "5", creating 100k char strings. The results were as follows:

Test Mean (ms)
CF Railo
string63006600
stringbuffer48782
cfsavecontent26348

So this is interesting in a coupla ways, isn't it?

  1. Don't use string concatenation for this sort of thing;
  2. Railo craps all over ColdFusion on the other methods;
  3. Surprisingly(?) <cfsavecontent> is the best approach here (if one is to consider performance alone, which one should not).
But don't get too excited by this, and decide to abandon string concatentation in favour of <cfsavecontent> for everything. Even a 100k character string is very large for a string being contructed in CFML. What about if I ratchet that back a coupla orders of magnitude and see what we get. This is with a magnitude of 3 (so 1000 chars):

Test Mean (ms)
CF Railo
string82
stringbuffer134
cfsavecontent82

Again, Railo is the better performer, but now the overhead of creating the StringBuffer and then coverting it to a string seems to be taking up a significant amount of the total time, so it's not a practical approach any more. <cfsavecontent> still competes with string concatenation though. I think, really, the conciseness of the code using the concatenation operator is more of a consideration than using <cfsavecontent> for string ops though.

Obviously with smaller magnitudes, the differences become even more inconsequential.

And that's me for the day. Enjoy your New Year's Eve if it's looming, or I hope your hangover is not so bad if you've already done it.

Cheers.

--
Adam