StructKeyExists vs. IsDefined when checking 2+ scopes

There has been a movement away from using the IsDefined() function to using the StructKeyExists() function. The reasoning is that when IsDefined() is used to find an unscoped variable, it follows a specific order of scope evaluation that goes through over half a dozen scopes (9 actually) in order until it finds the variable or fails. StructKeyExists() on the other hand requires a scope and limits the search to that scope.

Note: There is a thought in the community that StructKeyExists() is more efficient than searching for a scoped variable using IsDefined(). I can't comment on this as the difference is probably tiny.

Note: I say over half a dozen rather than 9 as some of the scopes 'exist' but are empty unless specifically used (cffile, url, form, client) or only exist in certain places like the arguments or the 'unnamed local scope' in a UDF.

Now all of this is fine and good, but what happens when a variable may exist in one of two scopes, such as Url or Form? In such a case, most people move back to an unscoped IsDefined() search. I suggest instead to simply use two StructKeyExists() functions and trust in ColdFusion's short circuited boolean evaluation to keep things efficient. All you have to do is make sure you check the scope where you expect the variable to occur most often first.

For example, this CFIF is expecting an email passed to it from an URL or Form. The URL is the most common way in which the email will be sent so I check the URL scope first.

If the email variable is passed on the url, then the second StructKeyExists() will never be processed and the end result is a single function use. In the worst case all you are doing is running 2 functions against 2 (probably small) variable scopes rather than running through half a dozen, at least one of which (CGI) is rather large.

Of course the use of 2 functions may be less cost effective and I await the word of someone like Sean who knows the inside of ColdFusion better than I.

Those who a simple explanation of short circuited boolean evaluation can read this Fusion Authority article from 2000 which details it rather simply: ColdFusion with Style

Related Blog Entries

Comments
Barney's Gravatar One problem with structKeyExists in multiple scopes is that it doesn't tell you WHERE the variable exists. So if you check URL and FORM scopes and it's there, how do you know how to reference the variable (i.e. in which scope)? At least for this case (which is a very common one) a better solution would be to combine the two scopes into a new psuedoscope. Fusebox has used 'attributes' for this since forever. Mach-II uses an event object. Model-Glue does something similar.
# Posted By Barney | 11/21/06 3:47 PM
Dan Wilson's Gravatar Michael,

Your link to the 9 scopes does not work.

http://www.houseoffusion.com/documentation/htmldoc...

dw
# Posted By Dan Wilson | 11/21/06 3:58 PM
Michael Dinowitz's Gravatar I was against that when we first created Fusebox because:
1. If both the url and form have the same variable, which takes precedence?
2. As a fast test will show, the new 'scope' is a full copy of both the url and form, which means more memory overhead (which does not matter to many but can have an effect).
3. It obfuscates where the variable is from just as much or more so than multiple StructKeyExists functions.
Bottom line is that it was a hack to solve a problem that could have been solved with a little more code and less 'games'. But that's just my perspective. :)
# Posted By Michael Dinowitz | 11/21/06 4:13 PM
Michael Dinowitz's Gravatar Urls are fixed.
# Posted By Michael Dinowitz | 11/21/06 4:15 PM
todd sharp's Gravatar Interesting post. I just threw this sample together really quick to test, what do you think of having a udf like this? Good/Bad - too much overhead?

I know it uses the hated "eval()" function, but hey...

<!--- Fake some vars --->
<cfparam name="url.email" default="you@somewhere.com">
<cfparam name="form.email" default="you@somewhere.com">
<cfset s = structNew()>
<cfparam name="s.nestedStruct.username" default="joeboo">

<cffunction name="structKeyInList" access="public" hint="i look for a struct key in a list of scopes and return true if any of them exist" output="true" returntype="boolean">
   <cfargument name="keyList" hint="a comma seperated list of structures to search (for example: url,form)" required="true" type="string">
   <cfargument name="value" hint="i am the key to look for" required="true" type="string">
   <cfset var exists = false>
   <cfset var i = 1>
   <cfset var curStruct = "">

   <cfloop condition="exists neq true and i lte listLen(arguments.keyList)">
      <cfset curStruct = listGetAt(arguments.keyList, i, ",")>
      <cfif structKeyExists(evaluate(curStruct), arguments.value)>
         <cfset exists = true>
      <cfelse>
         <cfset i = i+1>
      </cfif>
   </cfloop>
   <cfreturn exists />
</cffunction>
<cfoutput>
   structKeyInList("url,form", "email"): #structKeyInList("url,form", "email")#<br/>
   structKeyInList("url,form,s.nestedStruct", "username"): #structKeyInList("url,form,s.nestedStruct", "username")#<br/>
</cfoutput>
# Posted By todd sharp | 11/21/06 4:28 PM
Barney's Gravatar Michael,

1) why, whichever you configure, of course.
2) the base CF memory footprint is measured in hundreds of MB. Page parameters totalling even a few KB are atypical. If that's significant to your infrastructure's hardware, I'd say CF is probably the wrong technology.
3) no it removes an irrelevant distinction, it doesn't obfuscate. With structKeyExists, you have to check whether it exists, and then still know where it came from in order to use it. Combining the scopes simplifies both pieces, and is only a problem if you actually care whether the variable came from the URL or a form post (which is almost always an irrelevant distinction). The Servlet spec doesn't differentiate between the two, and ASP.NET provides combined and separate views (last I checked), so I think there's a strong precedence for not wanting to care.
# Posted By Barney | 11/21/06 5:02 PM
Toby Reiter's Gravatar Michael,
For me, one of the most compelling arguments FOR an attributes scope is that it doesn't matter where your incoming variables are coming from.

This means a task triggered manually by posting a form (form scope) can also be triggered automatically using a scheduled task (URL scope), and embedded within another process as a custom tag style call (attributes scope). In my current programming, seeing "Form" or "Url" scoped variables in my code is a hint to me that I haven't finalized the controller component of my application.

The same principle exists in object oriented programming -- a called function doesn't care where it's arguments come from, just that they reflect the correct types (if specified). A fuse in Fusebox is expected to behave the same way, generally.

In any case, for those wishing to not create a pseudo-scope in their apps, your solution seems to be a good one.

Thanks,
Toby
# Posted By Toby Reiter | 11/21/06 7:54 PM
Michael Dinowitz's Gravatar This does not prove your point as doing a dual structkeyexists to make sure it does exist and then a usage with no scope has the same effect. You don't care where the var exists as long as it does. And the dual structkeyexists is still a savings over copying the form and url scopes to a new attributes scope.

3) no it removes an irrelevant distinction, it doesn't obfuscate. With structKeyExists, you have to check whether it exists, and then still know where it came from in order to use it. Combining the scopes simplifies both pieces, and is only a problem if you actually care whether the variable came from the URL or a form post (which is almost always an irrelevant distinction). The Servlet spec doesn't differentiate between the two, and ASP.NET provides combined and separate views (last I checked), so I think there's a strong precedence for not wanting to care.
# Posted By Michael Dinowitz | 11/22/06 12:47 AM
Matt's Gravatar Michael, you're right that there is probably more overhead putting the url and form scopes into a common scope vs. doing the dual StructKeyExists() check. But the more of those url or form variables I need to use, at some point there would be a break even point. The overhead for combining those scopes would be a one time hit. And although I can't imagine any code ever actually doing so, but if you were to do the dual StructKeyExists for every variable, that amount of overhead goes up each time you do so.

Fusebox, Mach II and Model Glue all have a setting for which scope takes priority. I suppose there could be some rare use case where you wanted per request control over this, making the issue more complicated. I have yet to see such a conflict though as why would you have url parameters when submitting a form (other than the fuseaction or event parameters for the framework itself)?

My two cents.
# Posted By Matt | 11/22/06 8:55 AM
William Broadhead's Gravatar hum...okay, played around with that a bit more as our dev group is trying to decide what REALLY is best practise isDefined vs. structKeyExists...

Short answer: structKeyExists is SLOWER and has no advantage other than forcing coder to use scoping, which s/he should do anyway...

Long Answer:

whereas for the examples below url.pageid is a real url variable

STRUCTKEYEXISTS:
structKeyExists is pretty much the same speed no matter what you throw at it, keys can be real or not (url, 'pageid' and url, 'pageadfasdf' are about the same, no big diff one way or the other)
and if you throw a scope at it that does not exist, it throws, so it forces you to be careful about scoping.

ISDEFINED:
isDefined is super fast when the scope exists and you pass in a scope (url.pageid),
however, it is extremely extremely mega super slow when you pass in a non-existant scope (urlss.pageid).
it is also super super mega slow if you pass in a non-scoped non-existant variable (pagelksjdljsdf)
it is quite fast, on average 10 to 20% faster than structKeyExists, when you pass in a SCOPED variable, existant or not (url.pageid or url.aoiausldkjf)

So what does that mean in real life?
Well, as long as you are asking for a SCOPED variable that is extant, and you don't mispell the scope (url for instance), isDefined will be faster on average....
If you are looking for a NON-SCOPED variable, you have no choice but to live with isDefined.... hum....which can be very very slow when the variable does not exist.
If you want to make sure you scope all your vars, use structKeyExists and live with a small speed hit, but be assured it is scoped to a real scope (or throw an error).
So for me, in real life use, that means isDefined is probably the better choice as it is overall going to be faster as long as i can spell my scope correctly.
It also is the only choice to use when looking for multiple scope variables such as FORM.ID vs URL.ID and your page/function can take either and want to simply ask isDefined('ID')
However if you want to prioritize one over the other (which you should) then you can again use either define/struct where you have a little bit of script that checks each (form.id and then url.id) and assigns a variables.myID (or whatever your inbound scope/variable is) which is then passed in to page/function to use....(this is what we do so that we DO NOT fall prey to fusebox style mixed scope variable overrides and can determine within each module what SHOULD have priority, and yes, usually FORM wins over URL, but not ALWAYS - in either case, you should KNOW where your variable came from, sometimes it could come from both and you should know how to respond to that as well if necessary, NOT trust some background mixer)
Again, this would indicate isDefined would be preferred as it would be faster for that call too where you check specific scopes, as long as it is scoped and the scope exists.
So I can't find any reason to use structKeyExists other than it might seem like better code because it forces scoping....
How sad... it seemed like it should be faster as it specifies the scope....

here is sample code for you to run and see for yourself.... i'd love to know if you find anything different than what I did by platform or other (I am using CFMX702 on a WINDOWS platform). Is linux version any faster? Anyone know what actually happens at the compiled java level?)

<CFSCRIPT>
ITERATIONS = 100000;
writeoutput('ITERATIONS : ' & ITERATIONS & '<BR>' );
i=0;
j=0;
k=0;
ttt1 =0;
ttt2 =0;
t1c =0;
t2c=0;

for(k; k lte ITERATIONS; k=k+1){

T1 = getTickCount();
for(i; i lte ITERATIONS; i=i+1){
if(isDefined('url.asdfpageid')){
//do nothing
}
}
T2 = getTickCount();
TT1 = T2-T1;
ttt1=ttt1+TT1;


T3 = getTickCount();
for(j; j lte ITERATIONS;j=j+1){
if(structKeyExists(url, 'pageeid')){
//do nothing
}
}
T4 = getTickCount();
TT2 = T4-T3;
ttt2=ttt2+TT2;
if(TT1 gt 0){t1c = t1c+1;}
if(TT1 gt 0){t2c = t2c+1;}

if(TT1 gt 0 or TT2 gt 0){writeoutput('Iteration K: ' & k & ' isDefined: ' & TT1 & 'ms'); writeoutput(' structKeyExists: ' & TT2 & 'ms<br>');}


}

writeoutput('<BR>Times isDefined used time: ' & t1c);
writeoutput('<BR>Times structKeyExists used time: ' & t2c);

writeoutput('<BR>FINAL isDefined: ' & TTT1 & 'ms');
writeoutput('<BR>FINAL structKeyExists: ' & TTT2 & 'ms');

</CFSCRIPT>
<br>



<!--- The cfchart --->
<cfchart format="flash" xaxistitle="function" yaxistitle="Loading Time">
<cfchartseries type="bar" serieslabel="isDefined">
<cfchartdata item="isDefined" value="#variables.TTT1#">
</cfchartseries>
<cfchartseries type="bar" serieslabel="structKeyExists">
<cfchartdata item="structKeyExists" value="#variables.TTT2#">
</cfchartseries>
</cfchart>
# Posted By William Broadhead | 4/3/07 9:17 PM
Chris Randall's Gravatar I ran your example code and it shows that isDefined and StructKeyExists are pretty much the same with multiple runs.
# Posted By Chris Randall | 7/26/07 5:30 PM
Dominic Watson's Gravatar I am very interested in this issue and so I decided to run your code Matt. Firstly, I got very varying results due, I think, to my server being erratic and the order the functions are tested in your code.

I changed the code so that one iteration of each function was done at a time and the timings totalled up. So:

BEGIN LOOP
Test 1 iteration of IsDefined()
Increment total time
Test 1 iteration of StructKeyExists()
Increment total time
END

The results were now much more consistent. I then went on to test out 3 different scopes: form, url and my own user defined struct. A summary of the results, using 10million iterations, is as follows (running CF7 on Linux):

Form & URL very much alike. When the variable existed, StructKeyExists() was approximately 15% slower, when it didn't exist, both methods were pretty much neck and neck.

With the user defined struct, and when the variable existed, StructKeyExists() was approximately 10-15% FASTER, and when the variable didn't exist it was consistently 120-150% faster!

My conclusion is that if you want to check against a single scope (i.e. form, url, etc), use IsDefined(), otherwise use StructKeyExists().

My tuppence worth ,

Dominic
# Posted By Dominic Watson | 9/22/07 2:58 PM
Dominic Watson's Gravatar Apologies for double posting but I just tested another scenario in which I consistantly found StructKeyExists() to be 400% faster.

The scenario is when checking for the existence of an argument to a function. The code I used is as follows:

<cffunction name="tester">
   <cfargument name="foo" required="false">
   
   <cfscript>
      var start = 0;
      var times = StructNew();
      
      times.ID = 0;
      times.SKE = 0;

      start = getTickCount();
      if(StructKeyExists(arguments, 'foo')){
         //do nothing
      }
      times.SKE = GetTickCount() - start;   

      start = getTickCount();
      if(isDefined('arguments.foo')){
         //do nothing
      }
      times.ID = GetTickCount() - start;

      return times;
   </cfscript>   
</cffunction>


<cfscript>
   iterations = 1000000;
   i = 0;
   ID_total = 0;
   SKE_total = 0;

   for(i; i lte iterations; i=i+1){
      times = Tester(5); // remove value to test non-existence
      ID_total = ID_total + times.ID;
      SKE_total = SKE_total + times.SKE;
   }
</cfscript>
<cfoutput>
   <p>Iterations: #iterations#</p>
   <p>IsDefined: #ID_total#ms</p>
   <p>StructKeyExists: #SKE_total#ms</p>
</cfoutput>

<br/>

<!--- The cfchart --->
<cfchart format="flash" xaxistitle="function" yaxistitle="Loading Time">
   <cfchartseries type="bar" serieslabel="isDefined">
      <cfchartdata item="isDefined" value="#ID_total#">
   </cfchartseries>
   <cfchartseries type="bar" serieslabel="structKeyExists">
      <cfchartdata item="structKeyExists" value="#SKE_total#">
   </cfchartseries>
</cfchart>
# Posted By Dominic Watson | 9/22/07 3:26 PM
Dominic Watson's Gravatar And for my triple posting...

Perhaps people are aware of this, but to combine the url scope with the form scope, you can do this:

<cfscript>
form.putAll(url);

// or

url.putAll(form);

// or

formURL = StructNew();
formURL.putAll(url);
formURL.putAll(form);
</cfscript>

This should be much faster than looping the keys and adding them one by one.

Dom
# Posted By Dominic Watson | 9/22/07 4:31 PM
William Broadhead's Gravatar yeah, i played around on with this for several days, just to see what was 'faster' or not. People would purport one thing or another, and i wanted to *know* for myself...On all my windows XP/2003 systems (non 64), I found that *most* of the time, isDefined was faster, except in certain instances of when variables were actually extant or not, and it did have to do with whether or not you included scoping isDefined("url.myVar").... overall isDefined was a smidgen faster anyway....*ON OUR SYSTEMS*. you can't really compare (unless you test every scope) unscoped vars. obviously, if you don't know where it comes from (the scope), you have to run multiple structf(x) or end up using isDefined anyway. Other blogs, other people, swear struct is faster using my posted code. But other people, other blogs, have run my same code and get similar results to my on my systems. I think camden chimed in on sean's blog and mentioned something to the effect that it just depends on hardware/os setup to some degree. In any case, i am of the school that a FUNCTION should not care where it comes from (OOP style). But a FUNCTION takes arguments, so it should always come from there, it's the wrapper (bean if you will) that passes the data structure IN (DSTR in, DSTR out, generally) that should care. But a f(x) should not just pull from the air (a url or form var should NOT bleed into a CFC object, imho). IF you are pretending fusebox action pages or handlers are FUNCTIONS or methods in OOP style programming, well... it's an academic distinction. I feel you handler/action pages should be controlling cross-scripting and input very specifically as wrappers (controllers/ajax wrappers may be doing this instead/too), and by the time it hands off to underlying CFCs, everything in should be in the args. This becomes esp. true when you have to pass it in to services/ajax wrappers, esp as we get real asynchronous possibilities in cf8. Your ajax/service is not going to be able to pull things from 'the air' (unscoped variables) nor should it. Ultimately, it's i have to think with cf8, the speed will become moot (more moot) than before, and the 'best practise' question is a question of what you are doing and why.... i am a big fan of everything being scoped, but i will use isDefined("scope.myVar") more often as use structKey because on the windows/xeon/xp/2003 systems we use where i work (and at home) isDefined is on average, faster....ymmv
# Posted By William Broadhead | 9/22/07 4:32 PM
Dominic Watson's Gravatar Three things:

1. I totally agree with not breaking O-O in cfcs and functions by referencing outside variables. I have just been working on a project that did this left, right and middle and my head still hurts.

2. The most common version of CF I write for is CF6.1, servicing mainly UK government websites. Such things are still very relevant to me, even with the advent of CF8 (I can't wait for them to upgrade lol)!

3. The final test I did clearly shows that StructKeyExists() out performs IsDefined() in that particular situation and by a large margin. I had to look at this as I was writing a component that needed every bit of tweaking possible and I see no reason to use IsDefined() in this instance.

But agreed, in the case of checking url, form, etc., IsDefined() is faster.
# Posted By Dominic Watson | 9/22/07 6:39 PM
Microsoft Office 2007's Gravatar You are great! But I still did good! Hey!
# Posted By Microsoft Office 2007 | 12/11/11 11:16 PM
omega replica's Gravatar Web browser to reach a particular site, they found the "Add to my shopping list
# Posted By omega replica | 1/14/12 10:32 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.9. Contact Blog Owner
House of Fusion | ColdFusion Jobs @ House of Fusion | Fusion Authority