Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Joe Gores
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: Joe Gores

Sanity Checking Closure Invocation Overhead And Performance In Lucee 5.3.2.77

By Ben Nadel on
Tags: ColdFusion

PERFORMANCE CAVEAT: As with all micro load-testing scenarios, the results should be taken with caution. Testing in a local development environment, that is not under production load, is never a perfect representation of real-world performance.


One of my favorite use-cases for the Closure construct in Lucee 5.3.2.77 is to simplify interactions with a managed resource. For example, instead of explicitly requesting a resource from a connection pool, consuming it, and then returning the resource to the connection pool, I can create a method that manages the resource allocation for me and simply invokes a callback, providing the given resource as a callback argument. This way, the calling context doesn't have to care about the connection pool at all. That said, the one thing that always makes me nervous about this approach is the possibility that the closure-based workflow is adding a lot of processing and performance overhead. As such, I wanted to sanity check the overhead of creating and then invoking a closure in Lucee 5.3.2.77.

Since a key use-case for closures in ColdFusion is the encapsulation of resource management, I'm going to test the performance of doing just that: managing a Redis connection pool. In my base test case, I'm going to explicitly request a Jedis resource, perform a get-and-set operation, and then return the resource to the connection pool:

<cfscript>

	try {

		jedis = jedisPool.getResource();

		// Perform set-and-get.
		jedis.set( testKey, getTickCount() );
		systemOutput( jedis.get( testKey ), true );

	} finally {

		jedis?.close();

	}

</cfscript>

As you can see, the code is explicitly handling the connection.

Then, in my "test case" variation, I'm going to use a ColdFusion Closure - withJedisResource() - to hide the resource management details:

<cfscript>

	withJedisResource(
		( jedis ) => {

			// Perform set-and-get.
			jedis.set( testKey, getTickCount() );
			systemOutput( jedis.get( testKey ), true );

		}
	);

</cfscript>

As you can see, with this approach, I don't even know that there is a connection pool. I simply create a ColdFusion closure that accepts a Jedis instance; and then, I consume that Jedis instance within my closure. The withJedisResource() function is doing all the heavy lifting in terms of resource management.

ASIDE: It is not lost on me the fact that the cost of getting, consuming, and returning a Redis resource from a connection pool is likely to outweigh the cost of the closure itself. This is not an oversight. I am interested in testing the cost of "real world" usage - not the "absolute cost" of using a closure.

In a follow-up post, it may be worth testing the absolute cost of a closure by comparing a for-in loop to a Array.each() iteration.

And now, my test harness. In this test, I'm going to perform 10-sets of comparisons. And, within each comparison, I'm going to perform 1,0000 iterations of the two approaches that I've outlined above:

<cfscript>

	testKey = "ben_test_closure_overhead";
	testCount = 10;
	iterationCount = 1000;
	jedisPool = getJedisPool();
	
	loop times = testCount {

		// --------------------------------------------------------------------------- //
		// --------------------------------------------------------------------------- //

		// TEST VARIATION: In this test, we're going to create a baseline for the
		// performance of getting a Jedis instance from the connection pool and then
		// performing a SET-and-GET on a given key. The logic in this test is inline
		// with the rest of the code.
		stopwatch variable = "test" {

			loop times = iterationCount {

				try {

					jedis = jedisPool.getResource();

					// Perform set-and-get.
					jedis.set( testKey, getTickCount() );
					systemOutput( jedis.get( testKey ), true );

				} finally {

					jedis?.close();

				}

			} // END: Loop.

		} // END: Stopwatch.

		echo( "Inline: #test# ms<br />" );

		// --------------------------------------------------------------------------- //
		// --------------------------------------------------------------------------- //

		// TEST VARIATION: In this test, we're going to see how well Closures perform
		// when they are used to encapsulate resource handling. The withJedisResource()
		// function is used to hide the complexities of connection-pool handling. It just
		// accepts a Callback that will be handed a managed Jedis resource.
		stopwatch variable = "test" {

			loop times = iterationCount {

				withJedisResource(
					( jedis ) => {

						// Perform set-and-get.
						jedis.set( testKey, getTickCount() );
						systemOutput( jedis.get( testKey ), true );

					}
				);

			} // END: Loop.

		} // END: Stopwatch.

		echo( "Closure: #test# ms<br /><br />" );

	} // END: Outer loop.

	// --------------------------------------------------------------------------- //
	// --------------------------------------------------------------------------- //

	jedisPool.close();

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I instantiate and return the Jedis connection pool.
	*/
	public any function getJedisPool() {

		var load = ( classPath ) => {

			var jarPaths = [
				expandPath( "./commons-pool2-2.6.2.jar" ),
				expandPath( "./jedis-3.1.0.jar" )
			];

			return( createObject( "java", classPath, jarPaths ) );

		};

		var config = load( "redis.clients.jedis.JedisPoolConfig" ).init();
		
		var pool = load( "redis.clients.jedis.JedisPool" ).init(
			config,
			"127.0.0.1"
		);

		return( pool );

	}


	/**
	* I invoke the given callback, providing it with a managed Jedis connection. The
	* connection will be returned to the connection pool after the callback has finished
	* executing.
	* 
	* @callback I will be invoked with a Jedis connection.
	*/
	public any function withJedisResource( required any callback ) {

		try {

			var jedis = jedisPool.getResource();

			return( callback( jedis ) );

		} finally {

			jedis?.close();

		}

	}

</cfscript>

Now, again, I have to remind you that this approach to testing is over-simplified and not necessarily a great indicator of how a production server will perform under heavy load. That said, this test hopefully gives us, at least, an indication of the "order of magnitude" that we're working with. And, with that said, if we run the above Lucee CFML code, we get the following output:

Speed tests indicate that closures don't add a significant performance cost in Lucee 5.3.2.77.

Caveats aside, these are some very exciting results. In this particular test run, the Closure-based workflow was actually faster than the baseline test in more than half of the comparisons. Though, this ratio did go back-and-forth a bit with each test run.

The key take-away for me is that using closures within my Lucee ColdFusion code is not inherently a contraindication for performance. Or, put more simply, it is safe to use ColdFusion closures without significantly impacting request processing in Lucee CFML 5.3.2.77. Though, of course, I can only draw this conclusion for workflows that contain similar logic. This is a wonderful outcome since ColdFusion closures can serve to greatly simplify all kinds of workflows through the encapsulation of low-level details.



Reader Comments

The closure approach here is really interesting - definitely a paradigm shift for me in terms of how I think about data handling. That the penalty for using it seems minimal is also really encouraging.

Could you explain a little further the benefits of this abstraction? Is it that it's DRYer? And more flexible than using a component/function to perform the Redis/Jedis action? I guess I'm trying to learn/understand more clearly what situations might arise in which you'd look at the problem and think "this calls for a closure".

As always, really appreciate these posts!

Reply to this Comment

@Zachary,

Which ones are you talking about? arguments.jedis? Or, variables.testKey? Do you mean just in the closure instance? Or anytime I refer to a variables-scoped value?

Reply to this Comment

@Matthew,

In this case, using a closure -- or more specifically, using a Function that accepts a closure -- just works to separate the concerns more cleanly. So, instead of the code having to both figure out how to get a Redis connection and perform the operations, you have your closure know about the operations and have the higher-order function, withJedisResource(), worry about the connection management.

That's really all that it is -- a better separation of concerns.

Another one that I've been using a lot is the application of retry logic in some database call. We've been having issues with our MongoDB connection pools (for reasons I don't fully understand); so, I've created a service that takes a Closure and then wraps it in retry logic.

Something like (pseudo-code):

mongoService.withRetry(
	() => {
	
		return( mongo.getDB().getCollection( .... ).find( .... ).toArray() );
	
	}
);

Now, the .withRetry() higher-order function can wrap the invocation of the closure in a try/catch; and, if the caught error is something network related, it can start performing an exponential back-off, re-invoking the closure.

But, really, this is also just a separation of concerns - the thing inside the closure does "one thing"; and, the thing that consumes the closure does "another thing".

Reply to this Comment

@ben

Got it. Thanks for the explanation, especially that second example. I think I've got a better grasp of it. At least as good a grasp as I can without having used it. Time to acquire some hands on knowledge.

Reply to this Comment

I still have a hard time getting used to the "=>" notation for arrow functions. Maybe it's just my age, but I still like to see the word "function" in my closures. When my eyes encounter "=>" my brain interprets that as "equal to or greater than".

Reply to this Comment

@David,

Ha ha, I know exactly what you mean. In fact, when I first started playing with the Fat-arrow syntax in JavaScript, I would prefix it with a comment:

/* operator */() => {
	// ....
}

But, that immediately became laborious :D And, it quickly got me used to seeing the => in the markup. That said, I still cannot stand the look of the one-liner lambda expressions like:

things.map( ( thing ) => thing.value );

To me, this always looks wrong; and, at this point, I'm just going to assume that I will never grow accustomed to it :D

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.