【问题标题】:Why is the second execution of this combinator 10 times faster than the first?为什么这个组合器的第二次执行比第一次快 10 倍?
【发布时间】:2013-09-07 05:38:40
【问题描述】:

我正在研究一个解析器组合器库,并发现了一些我无法解释的行为。我第一次运行组合器时,它的运行速度比我第二次运行它慢得多。我用这个小应用程序重新发布了这个行为(在优化的情况下运行发布)

let (>>=) first callback state = 
    let reply = first state
    callback reply state

let time f = 
    let start = System.DateTime.Now
    f()
    printfn "%s" ((System.DateTime.Now - start).ToString())

[<EntryPoint>]
let main args = 

    let x1 state = "foo"

    let compound = 
        x1 >>= fun i ->
        x1 >>= fun j ->
        x1 >>= fun a ->
        x1 >>= fun b ->
        x1 >>= fun c ->
        x1 >>= fun d ->
        x1 >>= fun e ->
        x1 >>= fun f ->
        x1 >>= fun j ->                
               fun _ -> [i;j;a;b;c;d;e;f]


    time (fun () -> compound "a" |> ignore)
    time (fun () -> compound "b" |> ignore)
    time (fun () -> compound "c" |> ignore)


    0

运行这个输出我得到

00:00:00.0090009
00:00:00.0010001
00:00:00

为什么第一次迭代比第二次或第三次慢很多?


编辑,所以我也在 C# 中进行了尝试。它运行得更快,但结果相似。

using System;

namespace fssharp
{
    public delegate string Parser(string state);

    public delegate Parser Callback(string result);

    public class Combinator
    {
        public static Parser Combine(Parser p, Callback callback)
        {
            Parser r = state =>
            {
                var result = p(state);
                return callback(result)(state);
            };

            return r;
        }

        public static string X1(string state)
        {
            return "foo";
        } 
    }
    class Program
    {
        static void Main(string[] args)
        {
            Parser comb = state => 
                                Combinator.Combine(Combinator.X1, result  =>
                                Combinator.Combine(Combinator.X1, result2 =>
                                Combinator.Combine(Combinator.X1, result3 =>
                                Combinator.Combine(Combinator.X1, result4 =>
                                Combinator.Combine(Combinator.X1, result5 =>
                                Combinator.Combine(Combinator.X1, result6 =>
                                Combinator.Combine(Combinator.X1, result7 =>
                                Combinator.Combine(Combinator.X1, result8 =>
                                Combinator.Combine(Combinator.X1, result9 => s => result + result2 + result3 +result4 +result5 +result6 +result7+result8+result9)))
                                ))))))(state);

            var now = DateTime.Now;

            comb("foo");

            Console.WriteLine(DateTime.Now - now);

            now = DateTime.Now;

            comb("foo2");

            Console.WriteLine(DateTime.Now - now);
        }
    }
}

打印出来

00:00:00.0030003
00:00:00

我现在很好奇为什么 C# 在这里更快

【问题讨论】:

  • 第一次快还是第二次快?您在原始问题中都说明了。
  • 抱歉,第一个比较慢。我会更新问题
  • 可能与编译器预加载字节码有关?第三次和第二次一样吗?
  • @mydogisbox,是的,所有后续时间都与第二次一样快。

标签: f#


【解决方案1】:

虽然我无法确定,但还是:

  1. JIT:函数第一次运行时是 JIT 的,然后它使用已经编译的版本

  2. 检测到同一个函数调用同一个参数,并缓存结果。

尝试使用不同的参数调用它。如果时间保持不变,则为 2,如果时间不同,则为 1

【讨论】:

  • 更新了示例以使用不同的输入参数。听起来像它的 A。有没有办法预先强制 JIT 来验证假设?
  • 据我所知,没有办法做到这一点。这与虚拟机如何加载字节码有关。
  • @VincentHubert 我已经尝试使用 ngen 安装该应用程序,但并没有什么不同。我得到了同样的结果
  • 另外,可能与硬件有关。如果代码运行得足够快,它可能会在 CPU 缓存中停留足够长的时间,以便在第二次运行时出现。如果你在两次通话之间 sleep() 一分钟,你有同样的结果吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-19
  • 2012-03-03
  • 1970-01-01
  • 2011-07-27
相关资源
最近更新 更多