CFModule vs CFImport - radically different performance?

@Zackster something you might be curious about. I don’t have this broken out into a stand-alone example just yet, but I’m trying to find performance bottlenecks in my ColdFusion custom tag email DSL. And, I think I just stumbled over something curious. With custom tags, there are two possible invocations:

CFModule:

<cfmodule template="Foo.cfm" />

CFImport:

<cfimport prefix="tags" taglib="./" />
<tags:Foo />

My assumption would be that these are essentially the same exact thing since the cfimport tag is documented as being a “compiler directive”. So, I just assume these compile down to the same thing.

However, when I execute a custom tag using <cfmodule> it seems to be much much faster than executing the same tag using <cfimport>. This was surprising to me. I havent’ looked at the Java code; but, I thought you might find this interesting.

I’ll try to get a stand-alone example working after work.

As an aside, I didn’t really see any performance problems with <cfimport> when I was working in CommandBox. However, when I started running my code inside a Docker container, things slowed down. I wonder if this has something to do with File I/O, which I know is significantly worse inside of Docker.

performance improvements are always interesting!

probably the best starting point is to see what fusion reactor says?

Ah, good call! Let me see if I can get that running locally.

from 2014

@Zackster ok, not sure if this means anything to you. Here’s my stand-alone example, MyTag.cfm:

<cfswitch expression="#thistag.executionMode#">
	<cfcase value="start">
		[
	</cfcase>
	<cfcase value="end">
		]
	</cfcase>
</cfswitch>

And, here’s my test harness:

<cfimport prefix="tags" taglib="./tags/" />

<!--- Give the FusionReactor profiler time to kick-in. --->
<cfset sleep( 50 ) />

<cftimer type="outline" label="CFModule">
	<cfoutput>
		<cfloop index="i" from="1" to="1000" step="1">
			<cfmodule template="./tags/MyTag.cfm">
				#i#
			</cfmodule>
		</cfloop>
	</cfoutput>
</cftimer>

<cftimer type="outline" label="CFImport">
	<cfoutput>
		<cfloop index="i" from="1" to="1000" step="1">
			<tags:MyTag>
				#i#
			</tags:MyTag>
		</cfloop>
	</cfoutput>
</cftimer>

So, the CFModule loop of this, with 1,000 iterations, usually executes in about 10-15ms. The CFImport loop version, with 1,000 iterations, usually executes in about 1,200-1,500ms. That’s two orders of magnitude slower! B-a-n-a-n-a-s!

Looking at the FusionReactor profile:

Looks like my suspicions about File IO might be right. It looks like Lucee is checking to see if the Custom Tag file exists every single time I go to use it. By contrast – I assume – the CFModule version doesn’t re-check the existing of the CFM template every time.

cc @bdw429s, only cause you love FusionReactor!

I was just looking at the source in GitHub – Lucee/CFImportTag.java at 6.0 · lucee/Lucee · GitHub – and, ironically, there is a comment about caching:

// MUSTMUST use cache like regular ct
// page source

… to maybe the caching of CFImport tags is a known limitation.

you beat me to it :slight_smile: just saw the same thing

cfmodule uses a different call

((PageContextImpl) pageContext).getRelativePageSourceExisting(realPathes[rp] + filenames[fn])
vs
pageContext.getCurrentPageSource().getRealPage(template)

want me to create a test build with a change?

Hmmm, very interesting. Maybe there is a reason it uses something different? I don’t really know what the docs mean when they say that CFImport is a “compiler directive”, vs what CFModule is supposed to be :man_shrugging: .

Is this something I should open a ticket about?

a compiler directive means it affects how the source is compiled, like full null support

basically you’re extending the cfml language with cfimport, so it can’t take a dynamic path, only a static path

drop this file in the lucee-server\deploy dir and wait till it disappears, lucee with auto deploy it https://drive.google.com/file/d/1SuHOqqFYJq-NDGVr_rihCv7cfZqYbcXw/view?usp=sharing no idea if it will work :slight_smile:

Groovy – I will try after work :+1: :smiley: thanks!

please file a bug, add the performance label

1 Like

Done :muscle: [LDEV-3297] - Lucee

2 Likes

It brings me such joy to see you using the FR Profiler!

I didn’t see any mentions here in this thread, but if you set “inspect templates” to “never” (the equivalent of ACF’s “trusted cache”), does that remove the file system checks. I’m always a little unclear what sort of file system access inspect templates will avoid since I think it’s limited only to loading classes. If that setting doesn’t apply, then it sounds like there should certainly be another setting for this.

I just entered a couple related tickets last week for file system “exists” checks that run on every single request, can add up under load, and aren’t affected by “inspect templates”.
https://luceeserver.atlassian.net/browse/LDEV-3288
https://luceeserver.atlassian.net/browse/LDEV-3287

Turning the template inspection from “once” to “never” was the first thing I tried :laughing: . Pretty sure we have “never” in production, but use “once” in local-development. But, that did not seem to have any effect on this particular pathway.

Ok, thanks for confirming. I’m fairly sure that’s because the check in question is not related to loading class files, but simply checking if a file exists. I have a feeling the class file is loaded after the exists check that you’re seeing. That’s how Micha explained ticket LDEV-3288 to me.

I was actually suggesting to @micstriit last week, that instead of creating a bunch of separate cache settings (which no one would understand), to consider expanding the “inspect templates” setting to be more of a “this is prod and my file system won’t change!” setting to cover all these sort of things that we’d want to cache on production, but not on development.

1 Like

I want one big nuke all caches button :bomb: :bomb: :bomb: :bomb: :bomb: :bomb: :bomb: :bomb: :bomb: :bomb:

Yeah, that’s where I think Adobe did a good thing with the “server profile” stuff where there’s just one big giant “secure profile” setting. I’ve done the same thing in CommandBox with production/dev profiles. The average user doesn’t care to track dozens of similar yet different settings and worry about whether their production server has all the boxes ticked. They want to be able to simply toggle on performance or security. Which is why I understand the technical reasons why not everything falls under “trusted cache” but I also don’t think users really care. They just want it to be easy to configure for production vs development. :slight_smile:

100%, when something isn’t refreshing, I just hit all the “clear cache” buttons that I can find until something works :laughing:

1 Like

This doesn’t add anything new to this conversation, so feel free to ignore; but, I wrote this up on my blog:

It states mostly the same information as is in this thread. Except for calling out Docker for Mac, some performance mitigations, and why I’m not too concerned about how this will run in production.

1 Like

slow IO? hmmm, you could try copying your custom tags into the ram:// drive