Skip to main content
cancel
Showing results for 
Search instead for 
Did you mean: 

Be one of the first to start using Fabric Databases. View on-demand sessions with database experts and the Microsoft product team to learn just how easy it is to get started. Watch now

Reply
PwerQueryKees
Impactful Individual
Impactful Individual

I created some M functions to time the execution of M functions

Hi All,

I was doing some testing with excution times of various M code solutions, and that proved more difficult than I first imaged.
The main reason is that M code is not excuted in the order of the statements, but is evaluated lazily. Just in time so to speak.
I cam accross a number of 'obvious' solutions that did not work (or were very complicated), so I created my own.

The main function is this:

 

 

        Benchmark.Time = (f as function) => // making 1 call to the function, returning the number of seconds it took
            let
                f = () => Logical.From(f()),  // to cast the result to a logical
                start_time = DateTime.LocalNow(),  // not evaluated yet!!
                end_time = DateTime.LocalNow(),    // not evaluated yet!!
                duration = 
                    if start_time <> null then  // start_time evaluates here
                        if f() ?? true <> null then  // function is called here and never returns null
                            (end_time - start_time)     // so, this is always executed and evaluates the end_time to calculate the duration
                        else null   // never executed
                    else null   // never executed
            in
                Duration.TotalSeconds(duration)   // in seconds as a floating point number

 

 

Note the hoops I go through to make sure start_time and end_time are evaluated at the right moment...

 

The full solution includes running many samples of a list of functions and producing some basix statistics:

 

 

// Function.Benchmark Compare  produces some basic statistics comparing 2 benchmark samples
(functionList as list, sampleSize as number) => // a list of functions and the number of times to call each function
    let
        Benchmark.Time = (f as function) => // making 1 call to the function, returning the number of seconds it took
            let
                f = () => Logical.From(f()),  // to cast the result to a logical
                start_time = DateTime.LocalNow(),  // not evaluated yet!!
                end_time = DateTime.LocalNow(),    // not evaluated yet!!
                duration = 
                    if start_time <> null then  // start_time evaluates here
                        if f() ?? true <> null then  // function is called here and never returns null
                            (end_time - start_time)     // so, this is always executed and evaluates the end_time to calculate the duration
                        else null   // never executed
                    else null   // never executed
            in
                Duration.TotalSeconds(duration)   // in seconds as a floating point number
            ,
            
        Benchmark.Sample = (f as function) =>   // repeatedly call, and time, the function
            List.Generate( 
                    () => 0,                    // from 0
                    each _ < sampleSize,        // to sampleSize -1 (taken from the enlosing function)
                    each _ + 1,                 // by 1
                    each Benchmark.Time(f)),    // call and time

        Benchmark.Statistics = (runTimes as list) =>    // calculate some basic statistics and return them as a record
            [
                Average = List.Average(runTimes),
                StandardDeviation = List.StandardDeviation(runTimes),
                Median = List.Median(runTimes),
                Percentile25 = List.Percentile(runTimes, 0.25),
                Percentile75 = List.Percentile(runTimes, 0.75)
            ],
        
        results = List.Accumulate(      // loop through the list of functions
                functionList, 
                {},                     // start with an empty list
                (state,current) =>      
                    state & {Benchmark.Statistics(Benchmark.Sample(current))})  // take the sample and append the statistics
    in
        results // a list of records

 

 

Enjoy!

4 REPLIES 4
Andreas_Killer
Helper II
Helper II

I did a simple test with 10000 calls with the same parameters, so the range of the shortest and longest should be very close to the mean. Therefore, we can calculate the total with mean * number of calls.

 

According to your measurement, both functions take about 5 seconds for everything.

The queries are included in the same example file.

 

Even if I refresh the preview with the PQ Editor open, the whole process takes a bit more than 25 seconds on my machine!

 

IMHO your time measurement cannot be correct, even if we estimate the execution time for your code and subtract it, we still get a difference of more than 4 times.

 

Andreas.

Hm. I see your point. And I agree that it is unlike for my code to take 25 seconds...

 

I'll do some more testing myself... I didsee lots of 0 second elapse times if when looking at the details. Initially I thought that could be due to function call caching, but that does not explain your observations.

Now I think it is the measurement process itself... 

Any idease? Anyone?

PwerQueryKees
Impactful Individual
Impactful Individual

You copy the code into a new query. Let's say you name it "fnBenchmark".
In a 2nd query, where your own functions reside or where they are at least accessible:

let
    ...
    test_results = fnBenchmark({()=>fnCompareWildcards(<your paremeters>), ()=>fnRegexExtr(<your paremeters>)},1000)
in
    test_results

Be aware: If you functions are quick, they may be to quick for the precision of DateTime.LocalNow() to measure. I tested with calculating 5000 fibonacci numbers and that proved to be too quick...

 

If that would be the case with you, you may need to create your own function to invoke them multiple times. If your functions are meant to invoked on all rows in a table, create your test cases like that.

Andreas_Killer
Helper II
Helper II

Here I have a test file that contains 2 functions: fnCompareWildcards and fnRegexExtr

https://www.dropbox.com/scl/fi/hnv86prqiewyajra5h0yr/CompareRegEx.xlsm?rlkey=70ge7534tuugp17ottiw2ww... 

 

Let us assume I copied your code as query fnBenchmark into that file. How can I call it to check the runtimes of the two functions?

 

Andreas.

Helpful resources

Announcements
Las Vegas 2025

Join us at the Microsoft Fabric Community Conference

March 31 - April 2, 2025, in Las Vegas, Nevada. Use code MSCUST for a $150 discount!

Dec Fabric Community Survey

We want your feedback!

Your insights matter. That’s why we created a quick survey to learn about your experience finding answers to technical questions.

ArunFabCon

Microsoft Fabric Community Conference 2025

Arun Ulag shares exciting details about the Microsoft Fabric Conference 2025, which will be held in Las Vegas, NV.

December 2024

A Year in Review - December 2024

Find out what content was popular in the Fabric community during 2024.

Top Solution Authors