ReplaceNoCase Performance Regression after Lucee 5.4.4.38

We noticed a severe performance issue in a part of our code when running on Lucee 6.2.1.122 in a test instance; this seems to have been introduced as far back as some time between 5.4.4.38 (running fine) and 5.4.5.23 (first affected version). I have created test code to illustrate the issue:

2025-05-27_LuceeReplaceNoCase.zip (64.1 KB)

<cfscript>
	/* 
		start cfengine=lucee@5.4.4.38
		start cfengine=lucee@5.4.5.23
	 */
	echo("Lucee Version: #server.lucee.version#<br />");

	i=0;
	cftimer(label=i++, type="inline"){
		myText = fileRead("./example.txt");
	}
	echo('<br />');
	cftimer(label=i++, type="inline"){
		myText = ReplaceNoCase(myText, chr(9), "", "ALL");
	}
	echo('<br />');
	cftimer(label=i++, type="inline"){
		myText = ReplaceNoCase(myText, chr(10), "", "ALL");
	}
	echo('<br />');
	cftimer(label=i++, type="inline"){
		myText = ReplaceNoCase(myText, chr(13), "", "ALL");
	}
	echo('<br />');
	cftimer(label=i++, type="inline"){
		myText = ReplaceNoCase(myText, "[IMAGEGALLERY]", "", "ALL");
	}
	echo('<br />');
	cftimer(label=i++, type="inline"){
		myText = ReplaceNoCase(myText, "[INDEX]", "", "ALL");
	}
	echo('<br />');
	cftimer(label=i++, type="inline"){
		myText = ReplaceNoCase(myText, "[br]", "<br>", "ALL");
	}
	echo('<br />');
</cfscript>

We usually run on Debian 12, Tomcat 9 and Java 11, but I can reproduce the issue using commandbox on my home desktop computer running Windows 11, Java 11 and undertow on AMD Ryzen 7 5700X3D with 16GB RAM.

Here are the timings:

Lucee Version: 5.4.4.38
box start cfengine=lucee@5.4.4.38

0: 1ms
1: 1ms
2: 50ms
3: 11ms
4: 2ms
5: 0ms
6: 38ms

Lucee Version: 5.4.5.23
box start cfengine=lucee@5.4.5.23

0: 0ms
1: 2ms
2: 1384ms
3: 381ms
4: 1ms
5: 0ms
6: 1129ms

Lucee Version: 6.2.1.122
box start cfengine=lucee@6.2.1.122

0: 1ms
1: 2ms
2: 1403ms
3: 391ms
4: 0ms
5: 1ms
6: 1137ms

In extreme cases on our production machines, this means a total execution time of about 200ms for 5.4.4.38 increasing to 30 or 40 seconds when running on Lucee >=5.4.5.23 or Lucee 6.2.1.122, the latter already running on tomcat 10 and OpenJDK Runtime Environment Temurin-21.0.6+7, so it’s not an issue of the Java or servlet container version.

I know that I wouldn’t actually need to use replaceNoCase for specific chars, but this is part of a dynamic replacement mechanism, I just tried to boil this down to a clear test case.

So something was introduced after 5.4.4.38 and in or before 5.4.5.23 that severely affects the performance of replaceNoCase.

If it helps at all, here’s an excerpt from the FusionReactor profiler for this issue (running Lucee 5.4.7.3 here, which is also affected):

100.0% - 59.981s lucee.runtime.functions.string.ReplaceNoCase.call(ReplaceNoCase.java)
100.0% - 59.981s lucee.runtime.functions.string.ReplaceNoCase._call(ReplaceNoCase.java)
100.0% - 59.981s lucee.runtime.functions.string.ReplaceNoCase._call(ReplaceNoCase.java)
100.0% - 59.981s lucee.commons.lang.StringUtil.replace(StringUtil.java)
100.0% - 59.981s lucee.commons.lang.StringUtil._replace(StringUtil.java)
100.0% - 59.981s lucee.commons.lang.StringUtil.indexOfIgnoreCase(StringUtil.java)
93.5% - 56.074s java.lang.String.toUpperCase(String.java)
93.5% - 56.074s java.lang.String.toUpperCase(String.java) (hide)
93.5% - 56.074s java.lang.StringUTF16.toUpperCase(StringUTF16.java)
93.5% - 56.074s java.lang.StringUTF16.toUpperCaseEx(StringUTF16.java)
59.7% - 35.815s java.lang.StringUTF16.newBytesFor(StringUTF16.java)
59.7% - 35.815s Self Time
33.4% - 20.034s Self Time
0.4% - 225ms java.lang.StringUTF16.newString(StringUTF16.java)
0.4% - 225ms java.lang.StringUTF16.compress(StringUTF16.java)
0.4% - 225ms Self Time

Shall I file a new bug for this?

2 Likes

Hey mate, was great to see you at CFCAMP

Nice bug report and triage! please go ahead and file a ticket.

The regression was introduced with 5.4.5.13-SNAPSHOT, last available version before the regression is 5.4.5.11-SNAPSHOT

	
java.base/java.lang.StringUTF16.toUpperCaseEx(StringUTF16.java:993)
java.base/java.lang.StringUTF16.toUpperCase(StringUTF16.java:936)
java.base/java.lang.String.toUpperCase(String.java:3720)
java.base/java.lang.String.toUpperCase(String.java:3743)
lucee.commons.lang.StringUtil.indexOfIgnoreCase(StringUtil.java:922)
lucee.commons.lang.StringUtil._replace(StringUtil.java:818)
lucee.commons.lang.StringUtil.replace(StringUtil.java:788)
lucee.runtime.functions.string.ReplaceNoCase._call(ReplaceNoCase.java:54)
lucee.runtime.functions.string.ReplaceNoCase._call(ReplaceNoCase.java:60)
lucee.runtime.functions.string.ReplaceNoCase.call(ReplaceNoCase.java:45)
_2025_05_27_luceereplacenocase937.index_cfm$cf.call(/2025-05-27 LuceeReplaceNoCase/index.cfm:18)

https://luceeserver.atlassian.net/browse/LDEV-4155

Added a reduced test to lucee-testlab the extent of the performance regression is directly proportional to the number of matches

1 Like

It was a great pleasure meeting you, as always. Shame that I didn’t notice this issue any earlier, but most of our articles are so short that I didn’t really notice this change in behaviour before. I’ll take that as an opportunity to replace more reReplaceNoCase with proper Regex reReplaceNoCase where possible, as this seems to be just as fast as before in Lucee 6.2.

Here’s the new ticket: Jira

1 Like

Did some refactoring of the approach, haven’t merged it yet

it’s also going improve the overall performance of Lucee a bit as the underlying function is used in several places, including compiling cfml and some date processing

running some before and after benchmarks

image

2 Likes

Wow, awesome - that was quick! Will this be backported into the 5.4 branch eventually? We’ll be switching to 6.2 in a couple of months anyway, so we’re good, but those still on 5.4 may find this helpful for the remainder of their 5.x lifecycle.

Lucee uses the same tip and tail approach as the JDK

The Tip & Tail Model of Library Development

https://openjdk.org/jeps/14

Which basically means, as 5.4 is in LTS mode, it only gets security fixes, people have already survived this long with this issue, this becomes just yet another good reason to upgrade…

1 Like

Good to know. I’ve rolled back the last 5.4 update for our production servers for now to be on the safe side for performance reasons and the security issue doesn’t affect us at all, so all the more motivation to get straight to 6.2 ASAP. If only everything else were that easy to upgrade - I really find it hard not to hate PHP for their neglect to backwards compatibility, but I digress. It’ll be nice to make use of the new and shiny Java toys in Lucee 6.2 and hopefully 7, soon.

merged into 6.2.2.45-SNAPSHOT and 7.0.0.250-SNAPSHOT

while the memory usage looks higher, it’s actually good, due to there being less memory pressure, and less GC going on!

Testlab does a GC after each test run and it’s reporting the memory usage after completing the test

1 Like