Chris Peters: Web Developer

Performance loss in CFCs is not as significant as I once thought

Filed under: Programming — Chris Peters, November 25, 2005

The clock is ticking. Your users don't want to wait on your cool code to load.I’m glad that there are a wealth of ColdFusion resources out within the CF community on the Web. I had lost faith in using CFCs for data encapsulation until I loaded Google and started searching on the topic. It turns out that I was experiencing extreme performance loss because of my CFML code.

Why I tried beans in the first place

When I first read up on beans (aka JavaBeans), I didn’t quite get the value in leveraging them.

When I recently had to go through someone else’s code, I finally understood the value in beans. The code I was going through made no sense. Even though the author did a decent job of documenting her code, I had the hardest time figuring out the meaning behind what some of the keys to structs she defined.

A big difference between structs and bean-like CFCs is that the beans are self-documenting (provided that the developer uses the hint attribute properly within <cffunction> and <cfargument>). They give the data they encapsulate meaning. I can run the CFC from the browser and get nicely-formatted documentation on what all of the methods do, what their parameters represent, and what the data represents.

“My Way” of writing beans (read: the not-so-effective way)

When I first started developing beans, I decided to shortcut writing all of the “set” methods and just initialize the beans’ data members in the constructors (init()). For instance, look at this piece of code:


<cffunction name="init" access="public" returntype="Address" output="false" hint="Initializes component.">
<cfargument name="line1" type="string" required="no" hint="Line 1 of street address.">
<cfargument name="line2" type="string" required="no" hint="Line 2 of street address.">
<cfargument name="city" type="string" required="no" hint="City.">
<cfargument name="state" type="string" required="no" hint="State.">
<cfargument name="zip" type="string" required="no" hint="Zip code.">

<cfset variables.line1="arguments.line1">
<cfset variables.line2="arguments.line2">
<!--- Rest of variables scope set --->

<cfreturn>

By doing this, I would omit the need to write tons of “set” methods.

As it turns out, if you loop through a query and try to set the beans using the init() method on the same CFC instance, the changes do not take. I’m not sure why, but they don’t. So the only way to clear the values would be to create a whole new instance of the CFC with CreateObject() and call init() afterward. No big deal, right? Only one extra operation per loop cycle.

Using this method, I wrote a page that outputs data based on a dataset whose model contained rather large objects composed of other large objects. To say the least, the response time on the page was rather slow. I did what most smart developers would do and started blaming the database calls. As it turns out, the major queries were only taking 10 ms to be called from and returned to ColdFusion.

When I analyzed the code that took the query data and populated the beans, I found my bottleneck. It was taking thousands of miliseconds to append 10 CFCs to an array.

Back to the setter methods

I knew there was something wrong. I knew that there were people out there who saw the benefits in data encapsulation regardless of the criticism it draws. I started looking up beans in Google and decided to try it the “standard” way.

I saw major performance gains after implementing a component using setters instead of the method I mentioned above.

In my test, I implemented two CFCs; TicketUpdate.cfc used setters and TicketUpdate2.cfc initialized its data members in the constructor. I then created two test cfm pages (servlets, if you will).

The code for test.cfm is as follows:

<cfset tick = GetTickCount()>

<cfset update = CreateObject('component', 'TicketUpdate').init()>

<!--- Build an array of 1000 updates --->
<cfset updates = ArrayNew(1) />
<cfloop from="1" to="1000" index="i">
<cfset update.setID(i)>
<cfset update.setMessage('This is a relatively long message because messages may tend to be relatively long. Relatively. What if I start adding a bunch of HTML into this? What are you going to do then? What are you going to do then?‘)>
<cfset update.setDateCreated(Now())>
<cfset update.setUser(CreateObject(’component’, ‘User’))>
<cfset update.setMimeType(’application/pdf’)>
<cfset update.setMinsWorked(15)>
<cfset ArrayAppend(updates, update)>
</cfloop>

<cfset tick = GetTickCount() - tick>
<cfoutput>#tick#</cfoutput>

The code of test2.cfm is as follows:

<cfset tick = GetTickCount()>

<!--- Build an array of 1000 updates --->
<cfset updates = ArrayNew(1)>
<cfloop from="1" to="1000" index="i">
<cfset
update = CreateObject('component', 'TicketUpdate2').init(
i,
'This is a relatively long message because messages may tend to be relatively long. Relatively. What if I start adding a bunch of HTML into this? What are you going to do then? <strong>What are you going to do then?</strong>',
Now(),
CreateObject('component', 'User'),
'application/pdf',
15
)
>
<cfset ArrayAppend(updates, update)>
</cfloop>

<cfset tick = GetTickCount() - tick>
<cfoutput>#tick#</cfoutput>

As you can see, both pages output the length of time it takes to load 1,000 instances of virtually the same CFC into an array. The only difference is that test.cfm reuses the same instance of TicketUpdate while test2.cfm creates a new instance of TicketUpdate2 for each iteration through the loop.

I ran both files under the same conditions on ColdFusion MX 7 on my Mac OS X and got these results for 5 page loads each:

Script Avg. Time to complete task (ms)
test.cfm 744.5
test2.cfm 3314.8

I’m hoping that there’d never be a case where I’d have to create 1,000 instances of a bean for one page load, but I found this test to be pretty effective in showing the effects of loading the CreateObject() function for each loop iteration.

Back to the drawing board

I know this was a long post, but my findings confirmed a lot for me. I had already gone back to using structs in a current project, but now I can consider going back to CFCs. Maybe my code will be clear after all. Only more testing will tell…

3 Comments »

  1. Ok, so the plot thickens. I redid a few CFCs using setters and getters. Within each loop iteration, I didn’t reinstantiate the CFC I was adding to an array. But the setter values aren’t holding once the CFC instance is appended to the array. For example, this code doesn’t work:





    Comment by Chris Peters — November 26, 2005 @ 10:17 pm

  2. I finally figured out that the reason why my bean changes would not get passed back correctly in an array was a result of the behavior of the ArrayAppend() function.

    As it turns out, ArrayAppend() passes CFC instances by reference. That means if you add an instance to an array and change the instance’s value outside of the array, the instance’s state is affected inside of the array as well.

    Generally, ColdFusion passes by value, so this is pretty inconsistent and pretty confusing. Looks like calling CreateObject() in each loop iteration is necessary after all.

    Back to the debugging drawing board…

    Thanks to these sites for help:

    Creating and Accessing Collection Files
    ColdFusion Developer’s Journal
    http://cfdj.sys-con.com/read/41549.htm

    Arrays are passed by value, no by reference, no by value
    Compound Theory
    http://www.compoundtheory.com/?action=displayPost&ID=65

    Comment by Chris Peters — November 27, 2005 @ 4:56 pm

  3. Lastly, I should mention that another advantage of using setters in beans is that you don’t necessarily have to set every data member. This means that queries that feed into beans don’t need to select every single piece of data if the calling page doesn’t require it.

    Comment by Chris Peters — November 28, 2005 @ 9:10 am

RSS feed for comments on this post. TrackBack URL

Leave a comment