Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with:

Caching ColdFusion Component Methods Has Negligible Performance Improvements

By Ben Nadel on
Tags: ColdFusion

One thing that I find intriguing about inheritance in Javascript is that it is object-based. Meaning, you inherent from an actual object instance, not from a class. This is known as Prototypal inheritance, sometimes referred as differential inheritance, in which your class contains only that which differs from your prototype object. In this form of inheritance, methods are shared among the various class instances rather than being copied for each instance.

In ColdFusion, on the other hand, when you create a class instance, a new copy of each class method is instantiated; there is no sharing of method definitions, at least not so far as I know (I could be quite wrong). As such, I wondered if there would be any performance improvement to caching method definitions and simply appending them to a given object in order to fully "define" a given class.

To test this, I set up a crude demo in which I create a large number of instances of a given class using both the traditional approach and the caching approach:

  • <cffunction
  • name="cfc"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I create and return a CFC. The CFC I create is a Shell CFC to which methods are copied by refernce.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="className"
  • type="string"
  • required="true"
  • hint="I am the class being created."
  • />
  •  
  • <!--- Create the local scope. --->
  • <cfset var local = {} />
  •  
  • <!---
  • Create a CFC shell. This is a completely empty CFC
  • who's creation will be fast as there are no methods
  • (sub-classes) to create.
  • --->
  • <cfset local.instance = createObject( "component", "Shell" ) />
  •  
  • <!---
  • Now, let's append the cached CFC to the shell - this will
  • copy over all of the method pointers (without re-creating
  • the methods).
  • --->
  • <cfset structAppend(
  • local.instance,
  • request.cache[ arguments.className ]
  • ) />
  •  
  • <!--- Return the new instance. --->
  • <cfreturn local.instance />
  • </cffunction>
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Create a request-based cache of the CFCs that we are
  • going to create. Each component will be cached using its
  • class path.
  •  
  • This cache is going to be used by our CFC method defined
  • above.
  • --->
  • <cfset request.cache = {
  • Person = createObject( "component", "Person" )
  • } />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!--- Create CFCs using our "prototype" approach. --->
  • <cftimer type="outline" label="Cached Methods">
  •  
  • <cfloop
  • index="counter"
  • from="1"
  • to="1000"
  • step="1">
  •  
  • <!--- Get the Shell CFC with appended methods. --->
  • <cfset variables[ "person" & counter ] = cfc( "Person" ).init(
  • "Tricia",
  • "Girl",
  • "Brunette",
  • "Athletic",
  • randRange( 1, 10 )
  • ) />
  •  
  • </cfloop>
  •  
  • </cftimer>
  •  
  •  
  • <br />
  •  
  •  
  • <!--- Create CFCs using our traditional approach. --->
  • <cftimer type="outline" label="Non-Cached Methods">
  •  
  • <cfloop
  • index="counter"
  • from="1"
  • to="1000"
  • step="1">
  •  
  • <!--- Instantiate the CFC directly. --->
  • <cfset variables[ "person" & counter ] = createObject( "component", "Person" ).init(
  • "Tricia",
  • "Girl",
  • "Brunette",
  • "Athletic",
  • randRange( 1, 10 )
  • ) />
  •  
  • </cfloop>
  •  
  • </cftimer>

As you can see, the first CFLoop creates objects using the CFC() method and the second loop creates objects using the traditional CreateObject() method. When an object is created using the CFC() method, the actual target CFC is not created; rather, a shell object is created and all the class methods are copied to it (by reference) from a cached version of the target CFC. In this way, there is only minimal instantiation and maximal sharing of method references.

It was an interesting experiment, but it did not yield any interesting results. Even at a 1,000 ColdFusion component instances, there was little to no difference in instantiation speeds between the two different methods. In fact, the caching technique was often slower than the traditional CreateObject() technique. It looks like the instantiation algorithms that ColdFusion uses are pretty tight.

If you are interested, here is the shell object that I was creating:

Shell.cfc

  • <cfcomponent output="false">
  • <!--- I am a component shell. --->
  • </cfcomponent>

... and here is the original Person object that I was caching:

Person.cfc

  • <!---
  • NOTE: This component has been intentionally left with
  • *extremely poor* markup since the definition of a ColdFusion
  • component is NOT the primary intent; rather, I just wanted
  • to put in a larger number of functions to see how it would
  • affect speed of creation.
  • --->
  •  
  • <cfcomponent
  • output="false"
  • hint="I am a person object.">
  •  
  •  
  • <cffunction name="init">
  • <cfargument name="name" default="" />
  • <cfargument name="gender" default="" />
  • <cfargument name="hair" default="" />
  • <cfargument name="bodyType" default="" />
  • <cfargument name="hotness" default="" />
  •  
  • <!--- Set internal variables. --->
  • <cfset this
  • .setName( arguments.name )
  • .setGender( arguments.gender )
  • .setHair( arguments.hair )
  • .setBodyType( arguments.bodyType )
  • .setHotness( arguments.hotness )
  • />
  •  
  • <!--- Return this object. --->
  • <cfreturn this />
  • </cffunction>
  •  
  • <cffunction name="getBodyType">
  • <cfreturn variables.bodyType />
  • </cffunction>
  •  
  • <cffunction name="getGender">
  • <cfreturn variables.gender />
  • </cffunction>
  •  
  • <cffunction name="getHair">
  • <cfreturn variables.hair />
  • </cffunction>
  •  
  • <cffunction name="getHotness">
  • <cfreturn variables.hotness />
  • </cffunction>
  •  
  • <cffunction name="getName">
  • <cfreturn variables.name />
  • </cffunction>
  •  
  • <cffunction name="setBodyType">
  • <cfset variables.bodyType = arguments[ 1 ] />
  • <cfreturn this />
  • </cffunction>
  •  
  • <cffunction name="setGender">
  • <cfset variables.gender = arguments[ 1 ] />
  • <cfreturn this />
  • </cffunction>
  •  
  • <cffunction name="setHair">
  • <cfset variables.hair = arguments[ 1 ] />
  • <cfreturn this />
  • </cffunction>
  •  
  • <cffunction name="setHotness">
  • <cfset variables.hotness = arguments[ 1 ] />
  • <cfreturn this />
  • </cffunction>
  •  
  • <cffunction name="setName">
  • <cfset variables.name = arguments[ 1 ] />
  • <cfreturn this />
  • </cffunction>
  •  
  • </cfcomponent>

I know that there's a lot more going on than I understand, as far as Java class caching and instantiation is concerned. And, it looks like the ColdFusion team did a great job of keeping that fast and efficient.



Reader Comments

I know it's old habit, but my understanding is in CF9, you don't have to do this "<cfset var local = {} />" anymore because 'local' scope is already var'ed? It's the one thing that I in beta that stood out at me and I immediately thought, "Hey, Ben will be happy to see that."

or am I wrong about this?

Reply to this Comment

@Todd,

You are correct regarding the local scope being an implicit scope in CF9; but, on my dev environment we are still running CF8 (in order to match our production environments). That's why I'm still using the var keyword.

Reply to this Comment

@Rafael,

With a web service, you probably won't be creating too many instances of it, since it's really a gateway to another piece of the application machinery.

I was exploring this technique more for transient objects that might end up creating a lot of.

Reply to this Comment

Ben, I believe you are wrong about ColdFusion not sharing method definitions. Methods and UDFs in ColdFusion are immutable so I see no reason for a CFML engine to not share method definitions across CFC instances.

I suspect the biggest difference that sharing method definitions would make is not in speed but in memory usage. If you really wanted to test this, you could take one CFC file that has a few long methods in it, make a few hundreds of copies of the file, create 1 object instance from each CFC file then look at the memory consumption. Then for the second part of the test, create the same number of object instances except create them all from a single CFC file and look at the memory consumption.

If the CFML engine does not share method definitions then the memory consumption for the two tests would be similar. However, if the CFML engine does share method definitions, then the memory consumption of the second test should be lower than that of the first.

Given how much of an issue CFC performance has become in the CFML community, I'm sure every CFML engine vendor has been looking for every opportunity to optimize their CFC performance, and sharing method instances is an optimization I'm confident that the all the vendors have already implemented by now.

Despite disagreeing with you on this one, I do enjoy reading about your empirical testing of CFML engine behavior: please keep it up!

Reply to this Comment

@Dennis,

I could easily be wrong. I assumed since CFC creation has been known to have poor performance, it was because methods were created newly for each instance. If they are, in fact, shared across CFC instances, then the number of methods wouldn't really affect the time to instantiate (much).

I tried doing this just now:

<cfset a = createObject( "component", "Person" ) />
<cfset b = createObject( "component", "Person" ) />

A.init: #a.init.hashCode()#<br />
B.init: #b.init.hashCode()#<br />
<br />
<br />

<cfset aInit = a.init />
<cfset aDupInit = duplicate( a.init ) />

init: #aInit.hashCode()#<br />
dupInit: #aDupInit.hashCode()#<br />

... this gives me the output:

A.init: 1664155
B.init: 1664155

init: 1664155
dupInit: 1664155

So, they all have the same Hash Code. But, not sure what that tells us. I think two strings of the same value also have the same hash code, but are not the same object. Anyway to tell if two objects take up the same memory space?

Reply to this Comment

@Ben,

Nice work there! That was a good idea to try to find some unique ID of the method object at the Java level.

You are correct in that equal values returned by hashCode() for two object references does not guarantee they are the same object, but it does make it very likely. The official Sun javadocs contains the following about the topic:

"As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects."

I tried to go deeper but it started to make my head hurt, so I'll stop here and let any JVM experts listening to chime in with an answer if they have one.

As far as why CFC instantiation is so slow, I've read quite a few blog entries that suggest that the slowest part of instantiating a CFC is figuring out where the heck the CFC file actually is! The CFML rules for searching for a CFC are quite complex, and involve the current directory, ColdFusion mappings, custom tag folders, and Web server mappings. For all CFML engines, CFC instantiation seems to be the fastest when the component path is unqualified and the CFC file exists in the current directory. I don't have any links handy, but you can try Googling for more information.

Reply to this Comment

@Dennis,

Ok cool. I'll certainly be the first to admit when I was off-base about something and it seems like I was in this case.

That's comforting - it's nice to know that instances of a component share instances of the methods; just seems like a good practice.

Reply to this Comment

I'm pretty sure the only way that CF would be able to have class based/shared methods is if the underlying java object representing the CFC (when compiled) has it's methods declared static. Static methods are shared amongst all instances of the class, they also have no concept of state but can operate other static properties. (http://java.sun.com/docs/books/tutorial/java/javaOO/classvars.html)

I guess it could be possible that CF is smart enough to recognize CFC methods that don't access 'variables' or 'this' scope and make them static. It would be interesting to decompile a compiled CFC having methods that both access something in an instance scope or not at all.

Reply to this Comment

In using ecplise's class viewer (no decompilation), it looks like each method on the cfc is being turned into an inner class (of type UDFMethod) of the component that's being compiled. Those inner classes are then being referenced as static properties. If I understand it correctly, it means that cfc methods are in fact being shared amongst all instances of the same cfc, but in the form of a class that represents the method(?).

Reply to this Comment

Awesome find @JAlpino! You've confirmed my theory about shared method instances. I knew that each CFC method is translated into a separate Java class, but I did not know that they were translated into inner classes with a class representing the component. I learned something new today!

I know that UDFs are very similar to CFCs and UDF objects seem to be bound to the CFML templates they were defined in, so I suspect that a similar inner-class/outer-class relationship exists between UDFs and CFML templates.

Let me return to @Ben's very first point. The primary mechanism of reusing methods in Javascript is prototypal inheritance, while the primary mechanism of reusing methods in ColdFusion is class-based inheritance. However both languages also support method reuse via the dynamic injection of methods into objects at run-time. Ben's demo shows just one of the ways to do this in CFML.

Method injection is a very flexible form of method reuse, but can easily create problems if used incorrectly. One of the very few places I've seen it used well in ColdFusion is the Transfer ORM. In Transfer, every ORM object (regardless of the class it is mapped to) is an instance of the same CFC, TransferObject. When a TransferObject instance needs to be mapped to a specific ORM class, Transfer injects the ORM class methods into the TransferObject instance. This works well, but can be very confusing if you don't understand method injection. A common Transfer newbie question is "Where's the CFC for my Transfer object?".

I'd be interested to hear from anyone else who have found good uses for method injection in ColdFusion.

Reply to this Comment

@JAlpino, @Dennis,

Very nice detective work fellas. Thanks for clearing that up for me. But the broader question then remains - if the methods are static, what aspect of CFC creation takes so much processing; and, why are engines like Railo faster at it? I understand what Dennis was saying before about the number of places that ColdFusion has to search.

... or, perhaps it is a fallacy that other ColdFusion engines are, in fact, faster at creating CFCs?

Reply to this Comment

Without claiming to know the inner workings of object creation, I would have to image that instantiation time is partially consumed due to the size of the underlying object. Each component appears to be a concrete instance of coldfusion.runtime.CFComponent which probably has it's own inheritance chain and considering that each method becomes its own class with again, potentially it's own inheritance chain, an object could get relatively large.

I haven't delved too deep into Railo source, but it would be cool to look into their parser/compiler and perhaps interpreter to understand how they handle object creation and instantiation.

@Ben - were you in Maryland yesterday? =) I think you might have me confused with someone else.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.