【问题标题】:Euler 23 in C#: 0.2 seconds, in F#: 30.5 seconds. Why?C# 中的欧拉 23:0.2 秒,F# 中:30.5 秒。为什么?
【发布时间】:2015-01-23 02:41:52
【问题描述】:

我对我对这个问题的 F# 解决方案不是很满意,因为我找不到漂亮且快速的解决方案,但这不是这里的问题。问题是,我把解决方案翻译成 C# 真是太棒了,而且速度很快。就像,非常快,相对而言。

我不知道为什么。是的,我去过Reflector,C#代码看起来很相似,不能说我真的了解IL,但看起来有点像。我唯一能想到的是 F# int[] 对 C# List 的性能。

就这样吧:

F#

module Euler023

let divisorsSum n =
  let mutable sum = 1
  let limit = (int (sqrt(float n)))
  for i in [2..limit] do
    if n%i=0 then sum <- sum+i+n/i
  if (limit*limit)=n then sum-limit else sum

let isAbundant x = (divisorsSum x)>x
let abundants  = [1..28123] |> List.filter isAbundant |> List.toArray
let domain = System.Collections.BitArray(28124)

let rec loopUntil i j =
    if i=abundants.Length then ()
    elif j=abundants.Length then loopUntil (i+1) (i+1)
    else
      let sum = abundants.[i]+abundants.[j] 
      if sum<28124 then 
        domain.Set(sum, true)
        loopUntil i (j+1)
      else 
        loopUntil (i+1) (i+1)

let solve  =    loopUntil 0 0
            [1..28123] |> List.filter (fun x -> domain.Get(x)=false) |> List.sum

C#

static int divisorsSum(int n)
{
    int sum = 0;
    var limit = (int)Math.Sqrt(n);

    for (int i=2;i<=limit;++i) if (n%i==0) sum += i + n/i;

    if ((limit * limit) == n) return sum-limit;

    return sum;
}

static List<int> getAbundants(int ceiling)
{
    var ret = new List<int>();

    for (int i = 1; i < ceiling; ++i) if (divisorsSum(i) > i) ret.Add(i);

    return ret;
}

 static void Main(string[] args)
 {

     var abundants = getAbundants(28124);
     var bitField = new bool[28124];

     for (int i = 0; i < abundants.Count; ++i)
         for (int j = i; j < abundants.Count; ++j)
         {
             var sum = abundants[i] + abundants[j];
             if (sum < 28124) bitField[sum] = true;
             else break;
         }

     var total = 0;
     for (int i = 0; i < 28124; ++i) if (bitField[i]==false) total += i;

}

更新

包含此代码的项目由每个问题的单独文件 (EulerXXX.fs) + 主程序文件组成。主程序文件如下

module Program =


let stopWatch = System.Diagnostics.Stopwatch()
let mutable totalTime = System.TimeSpan()

let inline tick()
 = 
    stopWatch.Stop();
    totalTime <- totalTime.Add stopWatch.Elapsed
    printfn " -> Elapsed: %2.2f sec Total: %2.2f s" stopWatch.Elapsed.TotalSeconds  totalTime.TotalSeconds
    stopWatch.Restart()

let _ = 

    stopWatch.Start()
    printf "Euler001 solution: %A" Euler001.solve
    tick()
    printf "Euler002 solution: %A" Euler002.solve
    tick()
    printf "Euler003 solution: %A" Euler003.solve
    tick()
    printf "Euler004 solution: %A" Euler004.solve
    tick()
    printf "Euler005 solution: %A" Euler005.solve
    tick()
    printf "Euler006 solution: %A" Euler006.solve
    tick()
    printf "Euler007 solution: %A" Euler007.solve
    tick()
    printf "Euler008 solution: %A" Euler008.solve
    tick()
    printf "Euler009 solution: %A" Euler009.solve
    tick()
    printf "Euler010 solution: %A" Euler010.solve
    tick()
    printf "Euler011 solution: %A" Euler011.solve
    tick()
    printf "Euler012 solution: %A" Euler012.solve
    tick()
    printf "Euler013 solution: %A" Euler013.solve
    tick()
    printf "Euler014 solution: %A" Euler014.solve
    tick()
    printf "Euler015 solution: %A" Euler015.solve
    tick()
    printf "Euler016 solution: %A" Euler016.solve
    tick()
    printf "Euler017 solution: %A" Euler017.solve
    tick()
    printf "Euler018 solution: %A" Euler018.solve
    tick()
    printf "Euler019 solution: %A" Euler019.solve
    tick()
    printf "Euler020 solution: %A" Euler020.solve
    tick()
    printf "Euler021 solution: %A" Euler021.solve
    tick()
    printf "Euler022 solution: %A" Euler022.solve
    tick()
    printf "Euler023 solution: %A" Euler023.solve
    tick()
    printf "Euler024 solution: %A" Euler024.solve
    tick()
    printf "Euler059 solution: %A" Euler059.solve
    tick()
    printf "Euler067 solution: %A" Euler067.solve
    tick()
    stopWatch.Stop()
    System.Console.ReadLine()

程序的输出如下:

Euler001 solution: 233168 -> Elapsed: 0.02 sec Total: 0.02 s
Euler002 solution: 4613732 -> Elapsed: 0.03 sec Total: 0.04 s
...
Euler022 solution: 871198282 -> Elapsed: 0.02 sec Total: 4.11 s
Euler023 solution: 4179871 -> Elapsed: 81.11 sec Total: 85.22 s
Euler024 solution: [2; 7; 8; 3; 9; 1; 5; 4; 6; 0] -> Elapsed: 0.01 sec Total: 85.23 s
...
Euler067 solution: [7273] -> Elapsed: 0.01 sec Total: 85.31 s

所以问题不在于项目参数。此外,如果我将代码从 Euler023 复制到 Program,它将立即运行。 问题是,为什么这种减速只发生在这个问题上?

【问题讨论】:

  • 您是否考虑过使用分析器来找出您的陈述中的哪一点实际上需要时间? =)
  • 我不需要分析它,loopUntil 很慢。不管里面有什么,它都很慢。问题是,如果 Reflector 中的 C# 几乎相同,为什么?其次,你如何在 F# 中实现一个不慢的嵌套循环?
  • @Hans - 这里没有递归,这只是编写 while 循环的一种奇特方式。使用大约 7k 的丰富长度,无论深度如何,您都需要 2400 万次堆栈。它不会很慢,它会立即爆炸。
  • 在我的 FSI 上,F# 代码需要 2.5 秒,其中 1.5 秒是因为 abundants 的启动。 loopUntil 0 0 需要 0.35 秒。
  • 在我的 FSI 上,它几乎是即时的。我不明白。

标签: c# f# c#-to-f#


【解决方案1】:

您的 F# 版本一点也不慢;在我的机器上使用 F# Interactive 需要 0.44 秒。我不知道你怎么能观察到这样的缓慢(30.5 秒)。如果您编译并运行代码,请确保您处于发布模式并打开优化和尾调用消除。

但是,您仍然可以通过消除冗余中间集合的使用来进一步优化。

A.将(冗余)列表[2..limit] 更改为divisorsSum 中的范围表达式2..limit

for i in 2..limit do
    if n%i=0 then sum <- sum+i+n/i

B.在不创建大列表的情况下生成丰富数组(更忠实于 C# 版本):

let abundants = 
    let arr = ResizeArray(28123)
    for i in 1..28123 do
        if isAbundant i then arr.Add i
    arr.ToArray()

C.在不创建大列表的情况下计算solve

let solve = 
    loopUntil 0 0
    let mutable sum = 0
    for i in 1..28123 do
       if not <| domain.Get(i) then
            sum <- sum + i
    sum

新的 F# 版本比原来的版本快 4 倍;大约需要 0.1 秒才能完成。

更新:

您的测量结果不准确。首先,您测量了两次打印值调用之间的时间差。其次,EulerXXX.solve 是值;因此它们是在您编译程序时预先计算的。您应该将EulerXXX.solve 声明为函数:

let solve() = ...

并测量函数调用的执行时间:

let time fn =
    let sw = new System.Diagnostics.Stopwatch()
    sw.Start()
    let f = fn()
    sw.Stop()
    printfn "Time taken: %.2f s" <| (float sw.ElapsedMilliseconds)/1000.0
    f

let s023 = time Euler023.solve
printf "Euler023 solution: %A" s023

【讨论】:

  • 这确实做到了,但我仍然不明白为什么。我已经解决了程序模块中的一个值,而且速度很快。我把计时功能注释掉了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-17
  • 1970-01-01
  • 2019-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多