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

Enhance your career with this limited time 50% discount on Fabric and Power BI exams. Ends August 31st. Request your voucher.

Reply
PwerQueryKees
Super User
Super User

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
Anonymous
Not applicable

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
Super User
Super User

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.

Anonymous
Not applicable

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
August Power BI Update Carousel

Power BI Monthly Update - August 2025

Check out the August 2025 Power BI update to learn about new features.

August 2025 community update carousel

Fabric Community Update - August 2025

Find out what's new and trending in the Fabric community.

Top Solution Authors