Forum Discussion
Timing function execution using LAMBDA
I've been developing a large library of LAMBDA functions and I found myself in need of some crude performance testing, as many of my functions return large arrays and can take some time to execute. I've seen many solutions using Evaluate() in VBA, but I eventually realized I could do the same thing using a LAMBDA function. Here it is, unrolled for readability:
Name: EvaluationTime
=LAMBDA(F,
LET(
startTime, NOW(),
result, F(),
stopTime, NOW(),
reducedResult, REDUCE(, result, LAMBDA(accumulator, value,
IF(ISERROR(accumulator),
accumulator,
value
)
)),
IF(ISERROR(reducedResult),
reducedResult,
TEXT(stopTime - startTime, "m:ss.000")
)
)
)
Rolled up so that it can be added to Name Manager:
=LAMBDA(F, LET(startTime, NOW(), result, F(), stopTime, NOW(), reducedResult, REDUCE(, result, LAMBDA(accumulator, value, IF(ISERROR(accumulator), accumulator, value))), IF(ISERROR(reducedResult), reducedResult, TEXT(stopTime - startTime, "m:ss.000"))))
Suppose you want to time how long generating a 5000×5000 array of squares will take, using a combination of MAP() and SEQUENCE():
=EvaluationTime(LAMBDA(MAP(SEQUENCE(5000, 5000), LAMBDA(value, value * value))))
On my laptop, the result is "0:27.240".
To time your own LAMBDA function, assuming it's in Name Manager, wrap it as follows:
=EvaluationTime(LAMBDA(YourFunction(param1, param2, param3...)))
The parameters can, as always, be constants or refer to other cells, and your function doesn't get evaluated until inside EvaluationTime(). You can then play around with the code or the parameters to get different timing metrics.
This is how it works:
- Line 1: The only parameter is a LAMDBA function that itself takes no parameters. The function call you want to time is inside the LAMBDA.
- Line 3: Get the start time, using the NOW() function.
- Line 4: Evaluate the function and store the result.
- Line 5: Get the stop time, using the NOW() function.
- Line 6: Reduce the result by iterating over the array. The reduced result is the first error (if any) in the array.
- Line 12: If any error occurred during the evaluation, the timing is assumed to be invalid so the first error is returned. Otherwise, the evaluation time in minutes, seconds, and milliseconds is returned.
Because the NOW() function is volatile, the EvaluationTime() function is volatile as well, which means that it will be re-evaluated every time another cell changes. It's best to follow some rules so that you don't hang up your workbook for too long:
- Once you have the timing you need, delete the formula from the cell or disable it by putting the apostrophe in front, which forces Excel to treat everything that follows as a string.
- Don't have more than one EvaluationTime() call active at the same time, as they could interfere with each other by "stealing" processor time and skewing the timing.
- The evaluation time is affected by other processes on your computer, so the results may vary, even within the same session. If you can, do the timing on a device that has more limited background processing (e.g., an iPad); you'll get more consistent results.
Have fun.
- JKPieterseSilver Contributor
Kevin Dean Nice idea! Perhaps you can add an iteration to do several timing measurements so we can average the results and hopefully get a more accurate number? I've seen cases where the first time a calculation is done may be significantly slower than subsequent ones (some of the lookup functions do that), that is also good to take into account.
- PeterBartholomew1Silver Contributor
Presumably the look up also performed a sort the first time around?
- Kevin DeanCopper Contributor
JKPieterse Good point. I'll give it some thought. There's likely some optimization going on in the background the first time a formula is used which would affect the timing.
Didn't dig this solution. As a comment, CalcTime with lambdas is in samples for AFE on GitHub. It's bit specific but could be adjusted. The only the precision is 10ms since that's the limitation of the desktop app. VBA could give more precise result.
- Kevin DeanCopper Contributor
SergeiBaklan I wasn't aware of AFE until now. It looks really cool, and likely would have saved me some time in developing my library!
I can't find CalcTime, though. I don't see it anywhere in the GitHub repo, and there's no "Samples" option in the add-in.
Finally, I did try the Evaluate() in VBA, but I found it to be really inconsistent in its results and significantly slower than the LAMBDA, which tells me there's a lot of overhead that's unaccounted for. That may have been my environment (Excel on Mac, which only begrudgingly supports VBA), but given that it doesn't work on mobile or the web, I prefer a LAMBDA-based solution.
Do you have a link to CalcTime or know exactly how I can find it?
Sorry, where is no such function directly, it's the sample how to get calculation time. In general AFE sources with examples are here microsoft/advanced-formula-environment: Create, edit, and reuse formulas in Excel (github.com)
In Lib.md you may find this peace of the code
// Timing a computation wrapped in a thunk timer = LAMBDA(thunk, LET( time_0, NOW() , value, thunk() , time_1, NOW() , days, time_1 - time_0 , ms, days * 24 * 60 * 60 * 1000 // milliseconds (resolution 10ms on desktop) , pair(round(ms,0),value) ) );
It referenced pair() function in the sample file, all together return calculation time and the resulting value. We may ignore pair() and based on above create more universal function.
I played with it long ago and forget the details. In day to day activities it's not very practical - 10ms precision is not good enough and do we have 500 ms on the formula or 900ms on its variant - who cares.
PeterBartholomew1 has more experience with timing, but I guess he uses VBA. Perhaps Peter could share his approach.
- Kevin DeanCopper Contributor
SergeiBaklan After a bit more digging, I found timer() in Lib.md, which I assume is the CalcTime() function you're referring to. It uses the same logic for timing, though it treats results differently by returning both the calculation time and the results in a two-element array.
I'll give some thought to enhancing my version per a previous suggestion (average of multiple iterations) and may post an update.
- mtarlerSilver Contributor
I like this as I have been doing something more basic for a while. That said I created another alternative LAMBDA function:
evalTime = Lambda(please_insert_now_function, insert_calc_here, let(endTime, now(), vstack(expand(text(endTime-please_insert_now_function, "m:ss.000"),,columns(insert_calc_here),""), insert_calc_here)));
In this case the call is:
=evalTime(NOW(),MAP(SEQUENCE(5000, 5000), LAMBDA(value, value * value)))
so I don't have to wrap my function with another LAMBDA
interestingly I consistently get a longer time than your version. presumably because it does the now() and the calculation and then the function call overhead and then the final now() time.