Beware ColdFusion duplicate() function makes deep copies

Author: Steven Neiland
Published:

Warning: This blog entry was written two or more years ago. Therefore, it may contain broken links, out-dated or misleading content, or information that is just plain wrong. Please read on with caution.

Recently I had cause to look at speeding up a part of a new application which was creating an array of cfc objects using D/I1 and then populating them from a query. I noted that the time to call getBean() to get a new copy of each cfc was aprox 0.2 seconds which is not terrible except when you are doing it 66 times resulting in a load time of aprox 13 seconds before the beans are even populated with data.

The solution I came up with was to use DI/1 to get a single instance of the bean, then in a loop copy the original and populate the copy. This sped of the load time significantly, however I shortly discovered that the app was consuming a huge amount of memory and eventually would crash.

The problem turned out to be that in my end of years fatigue I had used the "duplicate()" function in my code.

duplicate() deep copies!

After going back and looking at the documentation again I did a facepalm when I read that the function does a deep copy of the object passed to it.

What that means for those of you who don't know is that every object referenced by that object gets duplicated recursively. Since the object I was duplicating was referencing multiple other singleton objects and each in turn were referencing other singleton objects and eventually the main framework object I was duplicating the entire application multiple times in memory "ouch!".

Solution

The solution of course was to use structCopy() instead, which despite its name also copies cfc objects only with structcopy it preserves references instead of duplicating the referenced object.

Testing

To demonstrate this I threw together this small test code. All we are doing here is create an instance of a counter component and then passing a reference into another component "main" which we then copy using both duplicate() and structCopy() methods.

I then call the original counter objects increment value and compare the before and after values of the counter when referenced through the different copies on the main component object.

Index.cfm

<cfset counter = createObject("component","counter").init()>

<cfset main = createObject("component","main").init(counter)>
<cfset maincopy = structCopy(main)>
<cfset mainduplicated = duplicate(main)>

<cfoutput>
      <h2>Counter Values Before Increment</h2>
      Original counter object: #counter.getCount()#<br/>
      Counter object referenced from main object: #main.getCounter().getCount()#<br/>
      Counter object referenced from copied main object: #maincopy.getCounter().getCount()#<br/>
      Counter object referenced from duplicated main object: #mainduplicated.getCounter().getCount()#

      <cfset counter.increment()>

      <h2>Counter Values After Increment</h2>
      Original counter object: #counter.getCount()#<br/>
      Counter object referenced from main object: #main.getCounter().getCount()#<br/>
      Counter object referenced from copied main object: #maincopy.getCounter().getCount()#<br/>
      Counter object referenced from duplicated main object: #mainduplicated.getCounter().getCount()#
</cfoutput>

Main.cfc

component {

      variables.counter = "";

      public any function init(counter){
            variables.counter = arguments.counter;
            return this;
      }

      public function getCounter(){
            return variables.counter;
      }
}

Counter.cfc

component {

      variables.counter = 0;

      public any function init(){
            return this;
      }

      public void function increment(){
            variables.counter += 1;
      }

      public numeric function getCount(){
            return variables.counter;
      }
}

Result

As you can see from the below output the counter value when referenced from the duplicated main component does not match the others demonstrating that the counter object it references is not the same one referenced everywhere else now.

Counter Values Before Increment
Original counter object: 0
Counter object referenced from main object: 0
Counter object referenced from copied main object: 0
Counter object referenced from duplicated main object: 0

Counter Values After Increment
Original counter object: 1
Counter object referenced from main object: 1
Counter object referenced from copied main object: 1
Counter object referenced from duplicated main object: 0

Reader Comments

  • Please keep comments on-topic.
  • Please do not post unrelated questions or large chunks of code.
  • Please do not engage in flaming/abusive behaviour.
  • Comments that contain advertisments or appear to be created for the purpose of link building, will not be published.

Archives Blog Listing