OK, so let's talk object creation...

I'm making sort of a series out of poking holes in the anti-ColdFusion ramblings of an admittedly biased blogger. Pro-PHP blogger Phil Parsons blogged about how ColdFusion is tragically slower than PHP and is therefore something we should all stay away from if performance is any kind of concern. Unless we want to connect to MS Exchange... maybe.

So his second example was object instantiation performance. Arguably, one of ColdFusion's weak points... but not something entirely unworkable. People who complain about this generally only do so because they don't know how to best use CFC's and, most especially, how to write them properly. Nowhere is this more apparent than in Mr. Parsons' components. If this is how he writes CFCs it's no wonder his employer has "to constantly make decisions NOT to use OO features of Coldfusion based on [sic] it's poor performance." (That quote is from his comments below the article).

You can download the source he uses in the article, so I'm not going to post that here. I will say that the primary critiques I have against them are things like the ungodly amount of white space, the silly variable creation in the pseudo-constructor and the amazing number of unnecessary expressions.

What I am going to show you is my version, and then I'll share my results.

Colony.cfc

view plain print about
1<cfcomponent displayName="Colony" output="false">
2
3    <cffunction name="init" output="false" returntype="Colony">
4        <cfargument name="cn" type="String" default="colony">
5        <cfscript>
6            colonyName = arguments.cn;
7            drones = arrayNew(1);
8        
</cfscript>
9        <cfreturn this>
10    </cffunction>
11    
12    <cffunction name="addDrone" output="false" returntype="void">
13        <cfargument name="drone" type="Drone" required="true">
14        <cfset arrayAppend(drones, drone)>
15    </cffunction>
16    
17    <cffunction name="getDroneCount" output="false" returntype="numeric">
18        <cfreturn arrayLen(drones)>
19    </cffunction>
20
21</cfcomponent>

Drone.cfc:

view plain print about
1<cfcomponent displayname="Drone" output="false">
2    
3    <cffunction name="init" output="false" returntype="Drone">
4        <cfargument name="m" type="String" required="true" default="">
5        <cfset message = m>
6 <cfreturn this>
7 </cffunction>
8    
9    <cffunction name="getMessage" output="false" returntype="String">
10 <cfreturn message>
11 </cffunction>
12    
13    <cffunction name="setMessage" output="false" returntype="void">
14        <cfargument name="val" type="String" required="true">
15 <cfset message = val>
16 </cffunction>
17
18</cfcomponent>

colony.cfm:

view plain print about
1<cfscript>
2    Colony = new objects.Colony("colony1");
3    
4    start = getTickCount();
5    while (Colony.getDroneCount()
< 10000) {
6        Colony.addDrone(new objects.Drone());
7    }
8    end = getTickCount();
9    writeOutput("Adding 10,000 Drones to the Colony took " & end-start & " miliseconds.");
10</cfscript>

And now for the really bad news:

While I managed to blow his unquestionably inflated time of roughly 600ms or more right out of the water, ColdFusion continues to have a weak spot around object instantiation. The best I could do was slightly less than half of what Mr. Parsons got. I manged to get this test down to 250-300ms. Still substantially slower than the 42ms he reported for PHP. So while Mr. Parsons was grossly exaggerating the problem, the problem yet persists. Even with the drastic improvements the ColdFusion team managed to pack into ColdFusion 9.0.1 over previous versions.

The question you have to ask yourself, at that point, is whether it really makes any difference. CFCs (and OO in general, really) are about maintenance, and there are ways you should use them and ways you shouldn't. Not knowing those rules, or worse knowing them and deliberately refusing to use them, and then presenting yourself as someone with something worthwhile to say on the subject is just plain dishonest. It's been years since I've worked on an application that was written without CFCs, and even the ones that were poorly done performed adequately. The ones that were written with CFC best practices in mind were actually extremely performant.

In other words, there's no reason for anyone's employer to refuse to use them due to performance or any other reasons but ignorance, poor design and (worst of all) hyperbole. It's no different than knowing you should be using arrays instead of lists, or that structures are passed by reference and arrays by value. Go ahead, it's OK. Use CFCs in your applications... just do it intelligently, keeping in mind what works and (most important) what doesn't. And use ColdFusion's built-in data structures to their maximum advantage: structures, queries, arrays, oh my! Use the scopes, too: server (far too often overlooked), application, session, request, variables, this, super, caller, etc. The list goes on and on.

Programming is a craft. Craft your applications wisely and you'll never go wrong (unless you're looking to trash-talk something using bad science and worse code).

Laterz!

Related Blog Entries

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
Did you turn on Template Cache and Component Cache in the cf admin? I know that your other blog post mentioned that there wasn't any tuning, but those two settings are something that should definitely be used in production. If you tweak that, do you get better times?
# Posted By Daniel Short | 1/30/11 2:08 AM
Dan,

I've tried 2 different garbage collectors. Right now I have 1536MB of heap space, 256MB of heap size for young gen, and 512MB for perm gen. I have stripped every single bit of excess out of the CFCs and I have tuned the CF admin settings. In other words I am choking every single bit of performance out of this that I can.

I have managed to get the timing down to 106ms for 10,000 objects... with a caveat. I hate caveats, but there it is. The caveat is this: CF generates a bazillion Java classes for each CFC. One class per method, and a class for the object itself, plus various inner classes for different things. It's a messy way to build objects... and heavy. They take up a lot of memory, that's just how it is.

And generating lots and lots of heavy objects means frequent garbage collection, which means a regular performance hit. If I run the code that adds 10k Drones to the Hive, I can get it as low as ~100ms. BUT, if I add another loop around that, run it 20 times in a row, capture the timing info for each of those 20 and average them, the average climbs, quite a bit (factor of ~4). Why? Because every time I run those 20 iterations there's 2 GCs in there. So out of those 20, 2 of them will take ~1.5-3 SECONDS to run.

So it's an improvement over previous versions, but there's still room for the CF team to improve performance. And again, OO is about long-term maintenance, not about performance... so looking at CFCs correctly is more important than anything.
# Posted By Jared Rypka-Hauer | 1/30/11 4:30 AM
OK the pseudo-constructor is not necessary, agreed but whitespace? Come on your clutching at straws with that insult.

It appears in my test that using cftimer is having a large impact on the performance and I am redoing the tests with this removed and utilising getTickCount instead.

I'll update the results when I am done.
# Posted By Phil | 1/30/11 6:13 AM
Jared did you try making your drone as a strict CFC?
# Posted By João Fernandes | 1/30/11 9:43 AM
Glad to see you're revising your "tests", Phil. That's cool.

You may have noticed that I managed to get this down to as little as ~100ms, although the timer values are very inconsistent for reasons I illustrate in a previous comment. Averages for test groups larger than a single test run tend to be closer to ~400ms.

I concede the point, in any case. And regardless of the whitespace (which affects the compiler phase of the process), the pseudo-constructor and the poorly written methods made it look like you were TRYING to make CF fail this test. It doesn't need any help with this one, really.
# Posted By Jared Rypka-Hauer | 1/30/11 2:43 PM
João, I'm not sure I follow the question...

Can you clarify?
# Posted By Jared Rypka-Hauer | 1/30/11 2:44 PM
Jared if your drone.cfc uses cfcomponent strict="true" and use <cfproperty name="message"/> instead of cffunction get/set I'm pretty sure there is a boost in performance. Didn't try but I remember that I had converted every single ValueObject for performance reason when I use to combine CF with LCDS.
# Posted By João Fernandes | 1/30/11 4:26 PM
So I ran the code you put above on a default CF9 install and it runs in around 800ms on my home desktop pc, but then I wanted to see what Railo does... wouldn't you know it, it will do 80ms about every time. Crazy, right? I just pulled down the latest Railo Express w/Jetty download off their web site, v3.2.1. It seems if you develop a large object-heavy app, you owe it to yourself to see if it will be significantly faster in Railo. I'm not a fanboy at all, and I hope future versions of ACF catch up, but railo's performance is shocking to me.
# Posted By Nathan Strutz | 1/30/11 8:22 PM
@João

I don't think strict="true" still does anything. It was a feature of early CF9 beta releases that doesn't seem to be documented anywhere in shipping CF9.
# Posted By Elliott Sprehn | 1/31/11 2:46 AM
Something I forgot to mention before:

Tag-based CFCs performed better than all-script CFCs. I have no idea why, or how, but they did. Not by a huge margin, either, but by a consistent few milliseconds each.

And I expected the complete opposite result. Grrr...
# Posted By Jared Rypka-Hauer | 2/1/11 2:38 AM
I know this may sound weird but I got it down to 0ms ...I did cheat though...

I had the objects created in Java. Ran in CF 8.01 --- the highest time was 5ms ....most of the averages were 0!!!

<cfset maxloops=4>
   <cfset maxdrones=10000>
   <cfset times = []>
    <cfset start2 = getTickCount()>
<cfloop from="1" to="#maxloops#" index="i">
    <cfset start = getTickCount()>
      <cfobject type="Java" class="Colony" name="colony">
      <cfset colony.init("My Cf Colony#i#",maxdrones)>
      <cfset colony.popdrones()>
       <cfset arrayAppend(times, getTickcount() - start)>         
</cfloop>
   <cfset    timetaken   =getTickcount() - start2>
   <cfoutput>
   <p>Took a total time of #timetaken# ms to run over 10000 iterations</p>
   
   #colony.colonyName# drone count=<b>#colony.getDroneCount()#</b>   max=#colony.maxdrones#
   <br>
    last drone: <b>#colony.getDrone(maxdrones-100).GetMessage()#</b> update str=#colony.iamupdated()#
   <p>Object creation test took an average time of #arrayAvg(times)# ms to run over #maxdrones# drones with #maxloops# iterations</p>
   </cfoutput>
   <cfdump var=#times#>
   <cfdump var="#colony.getDrone(maxdrones-1)#">


public class Colony {
   public Drone[] drones;
   public String colonyName;
   public int dronecount=0;
   public int maxdrones=0;
   public Colony(String cn,int setmaxdrones){
      colonyName=cn;
      drones=new Drone[setmaxdrones];   
      maxdrones=setmaxdrones;    
   }
   public void addDrone(Drone adrone) {
      if (dronecount < maxdrones) {
         drones[dronecount]=adrone;
         dronecount++;
      }   
   }
   public int getDroneCount() {
      return dronecount;
    }

   public String iamupdated() {
      String retstr="I am updated on 3/15/2013 9:14:am";
      return retstr;
}    
public void popdrones() {
   for (int x=0; x<maxdrones; x++) {
      String droneinitstr="I am drone " + (x +1);
      drones[x]=new Drone(droneinitstr);
      dronecount++;
   }   
}
public Drone getDrone(int x) {
   if (x<maxdrones)
      return drones[x];
   else
      return new Drone("Drone " + x + " cannot be found");
}
}


public class Drone {
   public String message;
   public Drone(String m) {
      message=m;
   }

   public String GetMessage() {
return message + " <i>changed on 3/15/2013 2:41pm </i>";
}

public void SetMessage(String m) {
message = m;
}
}
# Posted By Charles Test | 3/15/13 11:31 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.7. Contact Blog Owner