devoured elysium
devoured elysium

Reputation: 105037

Profiling F# function -- profiler doesn't see any of the calls of the function's body. Why?

I wrote the following function

let getTriangles maxPerimeter =
    let mutable count = 0
    for c in 1..maxPerimeter do
        let cc = (int64 (c*c))
        for b in 1..Math.Min(c-1, maxPerimeter-c-1) do
            let bb = (int64 (b*b))
            for a in 1..Math.Min(maxPerimeter-c-b, (int (Math.Ceiling(Math.Sqrt(float (cc+1L-bb)))))) do
                let aa = (int64 (a*a))
                if cc + 1L = aa + bb then
                    count <- count + 1
    count

and now it's time to tune it up.

To do so, I've installed dot Trace Performance and ran it over my application for a very big maxPerimeter, so as to make sure the program would take a while to run.

This is what I get:

enter image description here

As you may imagine, what I actually wanted to know is how the usage time is distributed inside getTriangles' function body, so this doesn't seem to be of particular help. I've tried turning off code optimizations in the Build pane, but it doesn't seem to help me a bit.

  1. Am I doing something wrong?
  2. How should I go about to profile this function?
  3. Is this F# or CLR specific behavior?

All the profiling experience I have is with Java, so I may be a bit off here on the CLR world. I've also dabbled with ANTS Performance but the result was the same.

Upvotes: 1

Views: 631

Answers (1)

Jack P.
Jack P.

Reputation: 11525

I don't know about dotTrace, I use ANTS Performance Profiler as I've found it works pretty well with F#; I've been using the new version (v8) recently to profile my fsharp-tools projects.

Once you complete a profiling run in ANTS Performance Profiler and the results are displayed, the default view only shows methods for which you have the sources (i.e., the .exe/.dll you're profiling has a .pdb alongside it which points to some valid source location on your machine). You can use the drop-down (see the screenshot) to show all methods, which is pretty useful for F# code; because you're passing functions around, the execution stack tends to go in and out of the libraries you may be using, so viewing "All Methods" gives a better picture of what your code is actually doing, and how code from an external library might be affecting the performance of your code.

ANTS Performance Profiler v8 screenshot, profiling F# code (fsharp-tools)

That said -- F# has two different kinds of for loops; they look similar but are actually quite different under the hood. The one you used (for x in y do) is roughly equivalent to a foreach loop in C# -- that is, the loop compiles down to an iterator which pulls each value from some sequence of values. The second, much faster kind of loop (for i = x to y do or for i = x downto y do) compiles down to very simple IL, just like you'd get with a for loop in C#, Java, C, etc.

Here's a modified version of your function which uses the second kind of for loop. In this case, it's only slightly faster (13.749s vs. 13.520s, N = 5000 on my laptop), but it is without question the way you want to write your code if you're doing any tight loops over numerical ranges.

let getTriangles' maxPerimeter =
    let mutable count = 0
    for c = 1 to maxPerimeter do
        let cc = int64 (c * c)
        for b = 1 to min (c-1) (maxPerimeter-c-1) do
            let bb = int64 (b * b)
            for a = 1 to min (maxPerimeter-c-b) (int <| ceil (sqrt <| float (cc+1L-bb))) do
                let aa = int64 (a * a)
                if cc + 1L = aa + bb then
                    count <- count + 1
    count

As far as behavior within the function, ANTS Performance Profiler can also give you line-level timings (but only for methods you have the source for). I compiled and profiled your function and my modified version (with maxPerimeter = 2000):

ANTS Performance Profiler v8, line-level timings for F# project

Upvotes: 4

Related Questions