分析 F# 函数——分析器看不到函数体的任何调用。为啥?

Posted

技术标签:

【中文标题】分析 F# 函数——分析器看不到函数体的任何调用。为啥?【英文标题】:Profiling F# function -- profiler doesn't see any of the calls of the function's body. Why?分析 F# 函数——分析器看不到函数体的任何调用。为什么? 【发布时间】:2013-06-16 18:30:02 【问题描述】:

我写了以下函数

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

现在是时候调整它了。

为此,我安装了dot Trace Performance 并在我的应用程序上运行了一个非常大的maxPerimeter,以确保程序需要一段时间才能运行。

这是我得到的:

正如你想象的那样,我真正想知道的是使用时间是如何分布的内部 getTriangles'函数体,所以这似乎没有特别的帮助。我尝试在Build 窗格中关闭代码优化,但它似乎对我没有一点帮助。

    我做错了吗? 我应该如何分析这个函数? 这是 F# 或 CLR 特定的行为吗?

我所拥有的所有性能分析经验都与 Java 相关,因此我在 CLR 世界中可能有点偏离。我也尝试过 ANTS Performance,但结果是一样的。

【问题讨论】:

显然,基本上所有时间都花在了内部循环的最后三行上,如果它没有行级分辨率,分析器不会告诉你。一小部分时间用于各种Math. 例程。如果没有为他们启用分析,它可能不会向您显示这些内容。 【参考方案1】:

我不了解 dotTrace,我使用 ANTS Performance Profiler,因为我发现它与 F# 配合得很好;我最近一直在使用新版本 (v8) 来分析我的 fsharp-tools 项目。

在 ANTS Performance Profiler 中完成分析运行并显示结果后,默认视图仅显示您拥有源的方法(即,您正在分析的 .exe/.dll 旁边有一个 .pdb它指向您机器上的某个有效源位置)。您可以使用下拉菜单(参见屏幕截图)显示所有方法,这对于 F# 代码非常有用;因为您正在传递函数,执行堆栈往往会进出您可能正在使用的库,因此查看“所有方法”可以更好地了解您的代码实际在做什么,以及来自外部库的代码如何可能会影响代码的性能。

也就是说——F# 有两种不同的for 循环;它们看起来很相似,但实际上在引擎盖下却大不相同。您使用的那个 (for x in y do) 大致相当于 C# 中的 foreach 循环——也就是说,循环编译为一个迭代器,该迭代器从某个值序列中提取每个值。第二种更快的循环(for i = x to y dofor i = x downto y do)编译为非常简单的 IL,就像在 C#、Java、C 等语言中使用 for 循环一样。

这是您的函数的修改版本,它使用第二种for 循环。在这种情况下,它只是稍微快一点(13.749s13.520sN = 5000 在我的笔记本电脑上),但毫无疑问,如果您在数值范围内执行任何紧密循环,您想要编写代码的方式.

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

就函数内的行为而言,ANTS Performance Profiler 还可以为您提供行级计时(但仅适用于您有源代码的方法)。我编译并分析了你的函数和我的修改版本(maxPerimeter = 2000):

【讨论】:

i.stack.imgur.com/jORA0.png 。看?那里什么都没有。我敢说,这两个分析器都会发生这种情况,这只能意味着它与 .NET 框架有关。 @devouredelysium 我不认为这是一个分析器问题——当我分析你的代码以获得第二个屏幕截图时,几乎所有的执行时间都被 getTrianglesgetTriangles' 函数占用; IIRC,我的版本只调用了一个外部函数,即Math.Sqrt——其他所有内容都内联到函数中。但是,如果您真的想查看所有内容,请进入 ANTS Performance Profiler 中的选项菜单并取消选中隐藏无关方法的复选框;分析器运行得更慢,但它绝对会记录所有内容。 @devouredelysium 另外,如果您不介意,请花点时间将您的屏幕截图发布到ANTS Performance Profiler Support Forum。我怀疑右边的 NaN 值应该在那里。

以上是关于分析 F# 函数——分析器看不到函数体的任何调用。为啥?的主要内容,如果未能解决你的问题,请参考以下文章

第35课 函数对象分析(函数操作符()重载)

使用链式构造函数避免代码分析 CA2000 警告?

JavaScript词法分析(尽力理解)

数学分析函数连续性部分问题设f在[a,正无穷)上满足lipschitz条件(F...

Java-构造函数(盲目分析)

JS之预编译和执行顺序(全局和函数)