Good morning everyone! We found an interesting per...
# cfml-general
j
Good morning everyone! We found an interesting performance situation yesterday that we wanted to share and see if anyone has noticed it. To our understanding, always scoping your variables is supposed to be more efficient for ColdFusion. We are seeing the opposite in regard to using
var
to create a pseudo local scope vs. using the built in
local
scope. Here's a CF Fiddle. The use-case in this Fiddle is not real by any means, but each code block is doing the same thing (unless someone sees something we missed) and producing much different results than expected, with the third being the most wild. The code examples were designed to perform access and assignment operations. We are seeing this on all versions of CF that CF Fiddle supports, with CF 2023 being the most affected. Does anyone have any feedback about why ColdFusion handles situation 1 and 3 more efficiently than 2? Thanks for your time!
r
You're using var on j in the third loop. Remove that and you'll get the expected slower results.
j
Thanks - we understand that, so I guess I wasn't clear enough in my question. Apologies. That is intentional. What we are interested in is why is that solution faster than the other two? Based on Adobe's documentation, using local. for all variables and explicitly scoping to local should be the most efficient, but it's actually the worst. https://helpx.adobe.com/coldfusion/developing-applications/the-cfml-programming-language/using-coldfusion-variables/about-scopes.html
a
var
scoping, scopes the variable to the current function scope. So it's not really having to hunt through all the scopes to find it. Personally I think the inbuilt "local' scope is horrible - just use
var
. I'd also write your function without using the
local
scope at all. Something like:
šŸ‘šŸ» 1
Copy code
function d() {
    var result = {};
    for (var j = 1; j <= 10000; j++) {
		result[j] = j;
		if (result[j] > 0) {
			result[j] = result[j];
		}
	}
	return result;
}
Code should be readable firstly as it needs to be maintained. Optimising can be important but it needs to be a trade off against code that can be maintained - so needs to be easy to understand when you have to come back to it in a years time!
šŸ’Æ 1
Hmm - seems that not using 'local' at all is also fastest for what it's worth - so readable and performant. https://cffiddle.org/app/file?filepath=6cbdaf3c-e7ae-4b96-9381-35cb047bb208/47c3f207-4d00-42ad-ae24-fd3a5ebfcffd/48afa545-29e4-4475-9c10-5efca89dec37.cfm
j
What is your reasoning for saying the local scope is terrible? It appears to me that the
var
keyword simply leverages the local scope to keep the variable local to the function. https://cffiddle.org/app/file?filepath=9b87dd30-202b-41f3-828c-762521f0ae55/819e5e6[…]df9-9ad1-bbb5363e7aa6/a8dfaa03-ab0a-4095-b15c-8206a1c7a3e9.cfm
a
Because it's ugly to read (everything is prefixed with
local
. It's something I've only seen in CFML so esoteric and it doesn't solve anything that just using
var
doesn't already do and
var
does better.
j
Understood and partially agreed. In the MVC world having the
local
prefix assists with visually separating what is available within your controller's action vs. what is available in the view. Opinions aside, though, I am really just trying to get an understanding about why using the
local
scope is slower when
var
is using local scope anyhow. This behavior contradicts the CFML documentation, which recommends always scoping your variables to avoid scope lookups.
a
It doesn't contradict the documentation as, as you've just said -
var
is essentially
local
Why it's slower - I don't know. Over 10,000 iterations I think you saved a few milliseconds, which comes back to writing maintainable code. If you want crazy fast - don't use CFML šŸ™‚
j
I stated that
var
utilizes the
local
scope, not that they are the same. In my eyes, using
var
and then not specifying scope on the variable would in fact cause ColdFusion to perform a scope lookup. Granted in this instance it is the first scope evaluated, hence the speed is great. However, to me, that does not jive with the CFML docs. And again - I am not after opinions on which to use, rather I am seeking to try and understand what's happening under the hood.
d
FWIW I agree with everything @aliaspooryorik said, especially that readability, understandability, and ease of maintenance are Job One, in just about every case.. @James Harris I don't think it's true that using var and not OTHERWISE specifying scope causes a scope lookup. Using var makes it explicitly local to the method/function, no scope search needed.
j
@Dave Merrill It might not be true, it just seemed like it. Like I said, the very first scope ColdFusion would look at is local, so the variable created with var would be available almost immediately. That said, and this could be an area where I do not understand CFML's processing accurately enough, but it would seem to me that every variable referenced, regardless of where it is stored, would go through some sort of "where do I find this?" decision. Hence Adobe's statement in the attached screenshot.
If you use a variable name without a scope prefix, ColdFusion checks the scopes in the following order to find the variable:
a
I'm not going to get into an argument that goes round and round but that screen shot of the documentation has the title "evaluating unscoped variables" so, as
var
is scoping the variable none of that applies, or that's how I read it anyway.
r
I read it as John does. If the variable is var scoped and then requested it is retrieved from some hidden scope. If a requested variable is not var scoped, then it goes through the scopes per the documentation. IIRC, all kinds of variable copying happens when using local scope since they didn't want to break existing code. They still managed to break code https://blog.adamcameron.me/2013/12/34-dumb-things-coldfusion-does.html .
Unfortunately, ACF is closed source so it's all speculation.
j
Thank you for the post, Rodney. I don't feel like it answered anything for me in this instance, but it was an interesting read regardless. Alias, David, and Rodney, I appreciate the input thus far. We have all established our own preferences and reasons for why we personally do or do not use the local scope prefix vs. var, and that's all valid. While it doesn't help me in this understanding issue, it gives me some more input when considering future code. For others who might see this thread, I am still interested in any insight as to why using the local scope prefix is less efficient. If this thread ends in, "var just does some magic and is more efficient", then that's unfortunate for us all and I would love it if Adobe put some more detailed information out there for us.
s
I believe this is due to a backward compatibility issue when
local
scope was introduced. Prior to
local
scope, a lot of people defined a
local
variable as an empty struct and then used
local.foo
for a local variable. When both ACF and Lucee introduced the
local
scope, in order to be backward compatible, the engine still had to do a lookup on
local
first to establish whether there was a legacy-style "local scope" variable defined, before assuming
local
meant the newly-introduced scope. So
local.foo
now does two lookups, effectively, where
foo
does only one.
šŸ‘ 2
(Adam's article explains more about this but doesn't touch on the performance impact)
j
Sean, thank you very much! If that's what the engine is doing, that would absolutely explain what we are seeing, and makes a lot of sense.
s
FWIW, I eventually settled on a rule of thumb that was: scope all variables except ARGUMENTS and LOCAL "scopes". With the additional controls added on restricting variable lookup (in both ACF and Lucee), you can improve performance (since the lookup chain for unscoped variables is shorter), and -- subjectively -- I find CFML code easier to read if it follows my "rule of thumb" because it more closely matches what other languages do.
j
So you're saying it is also more efficient to leave
arguments.
off, or is that more of a readability thing?
s
I didn't bother analyzing performance to that degree -- other languages tend to refer to arguments and locals without any "scope" so that makes CFML more readable for me. I find
ARGUMENTS.foo
(or
arguments.foo
) just plain ugly. Whereas other (outer, increasingly global) scopes add useful semantic hints (
this
,
variables
,
request
,
application
,
server
,
url
,
form
, etc).
šŸ’Æ 1
I don't scope
variables.foo
in a
.cfm
either -- again, because it adds no semantic value.
This is a case where, for me, readability trumps absolute performance -- but the lack of
local
scope also happens to improve performance...
šŸ’Æ 1
j
Okay, thanks for your input Sean. This all makes more sense now. Be nice if it wasn't so, but it is what it is.
d
FWIW, I agree w Sean, except that I do find explicitly scoping vars to arguments to be valuable. • Improves readability -- that's definitely where that value is coming from • Avoids dumb scoping errors and the scope search performance hit -- without it, referencing foo would see everything in the scope lookup chain, which cf has to search I understand the desire to make it like other languages, but that's less important to me than those considerations.
j
I agree with you on the legibility of having arguments., Dave. šŸ‘
s
Avoids dumb scoping errors and the scope search performance hit
If you enable the restricted scope lookup, most of that performance hit for
arguments.
scope goes away. Of course, not all (legacy) code is compatible with that restricted scope lookup (all mine is tho' šŸ™‚ ).
āž• 1
šŸ™ƒ 1
j
Where would one find some reading about that setting, Sean?
r
It's an Application setting named searchImplicitScope. Check out https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-tags/tags-a-b/cfapplication.html or https://www.petefreitag.com/blog/scope-injection-cfml/ Adobe's documentation is poorly worded.
j
That's what I figured - just didn't want to assume since it's named a bit differently. Thanks Rodney.
s
It has a different name on Lucee, as I recall, unless they've made it compatible now?
d
I believe this is due to a backward compatibility issue when
local
scope was introduced. Prior to
local
scope, a lot of people defined a
local
variable as an empty struct and then used
local.foo
for a local variable. When both ACF and Lucee introduced the
local
scope, in order to be backward compatible, the engine still had to do a lookup on
local
first to establish whether there was a legacy-style "local scope" variable defined, before assuming
local
meant the newly-introduced scope.
@Mark Takata (Adobe) could we get a setting added to cfadmin to stop this backward compatibility check on local scope? We use local everywhere, and I'm guessing others do too. A quick setting could make a substantial improvement for many apps with the flick of a setting. Based on that fiddle that's a 2x improvement on CF2023.
m
There's something in the works that may cover this. I'll be making the announcement tomorrow morning.
šŸ‘€ 3
ā¤ļø 1
d
@seancorfield Yeah can't do the restricted scope thing at this point. Worthy goal though.
s
When we first enabled it at work, we caught a couple of bugs -- we had two places that referred to
form
scope vars without the scope-qualifier and one place that referred to a
request
scope var. And both were in internal-facing code, not customer-facing. Given the 250kloc codebase, that wasn't too bad...
r
Setting searchImplicitScopes to false should be part of the secure profile. šŸ˜‰
šŸ‘ 1