Reputation: 105037
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:
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.
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
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.
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
):
Upvotes: 4