【问题标题】:Possible optimization in my code?我的代码中可能的优化?
【发布时间】:2012-01-15 00:40:22
【问题描述】:

为了解决Euler Project problem n°5,我写了如下程序:

class p5
{
    const int maxNumber = 20;
    static void Main(string[] args)
    {
        Job(); // First warm-up call to avoid Jit latency

        var sw = Stopwatch.StartNew();
        var result = Job();
        sw.Stop();

        Debug.Assert(result == 232792560);
        Console.WriteLine(result);
        Console.WriteLine(sw.Elapsed);
        Console.ReadLine();
    }

    private static int Job()
    {
        var result = Enumerable.Range(1, int.MaxValue - 1)
            .Where(
                n => Enumerable.Range(maxNumber / 2 + 1, maxNumber / 2).All(c => n % c == 0)
            ).First();
        return result;
    }
}

但是,我发现这有点长(17 秒,在释放模式下),即使它正在工作。

有什么可能的优化吗?

仅供参考,我尝试使用AsParallel 方法,但正如预期的那样,工作量太小,上下文切换比收益重(超过 1 分钟):

    var result = Enumerable.Range(1, int.MaxValue - 1).AsParallel()
        .Where(
            n => Enumerable.Range(maxNumber / 2 + 1, maxNumber / 2).All(c => n % c == 0)
        ).First();
    return result;

[编辑]根据马丁的建议,这个版本除以10所用的时间:

    private static int Job()
    {
        var n =2;
        bool result;
        do
        {
            result = true;
            for (int c = maxNumber / 2; c <= maxNumber; c++)
            {
                if (n % c > 0)
                {
                    result = false;
                    break;
                }
            }
            n ++;//= 2;
        } while (!result);
        return n;
    }

[编辑]总结一下我所有的测试,粗略的执行时间:

  • 我的第一次实现:20 秒
  • 删除了内部 enumerable.Range 调用(由简单的 for 循环替换):3 秒
  • 删除了外部和内部枚举。范围调用:1.5 秒
  • 只取偶数:(只有循环,没有 enumerable.range):不到 1 秒
  • 使用 drhirsch 建议的 Gcd/LCm 欧几里得算法:0.004 毫秒

最新的建议显然是好的答案。我感谢 drhirsch 提出了另一种方法,而不仅仅是简单的循环优化

【问题讨论】:

    标签: c# math


    【解决方案1】:

    一个好的优化应该是使用更好的算法。

    这是要求数字 1..20 的 最小公倍数,可以通过找到 lcm(1,2),然后找到 lcm( lcm(1,2),3) 等到 20 岁。

    求lcm的一个简单算法是将两个数的乘积除以最大公约数gcd 可以在很短的时间内被众所周知的euklidian algorithm 找到。

    #include <iostream>
    
    long gcd(long a, long b) {
        if (!b) return a;
        return gcd(b, a-b*(a/b));
    }
    
    long lcm(long a, long b) {
        return (a*b)/gcd(a, b);
    }
    
    int main(int argc, char** argv) {
        long x = 1;
        for (long i=2; i<20; ++i) 
            x = lcm(x, i);
        std::cout << x << std::endl;
    }
    

    这会立即吐出解决方案。

    【讨论】:

    • 难以置信的答案。基准不言自明(放在我原来的问题中)
    【解决方案2】:

    嗯,有一件事是你只需要测试偶数,所以从 0 开始,然后增加 2。 这是因为偶数永远不会均匀地分成奇数。 你也可以从 10 的阶乘开始搜索,所以 10*9*8*7..等等其他词从 10 开始!这是3 628 800。 这可能有助于更快地运行它。我在 C 语言中的平均速度也是 10 秒,所以你的代码实际上很好。

    【讨论】:

      【解决方案3】:

      使用 Enumerable 很慢(参见 this 比较 Enumerable.Repeat 和 for 循环初始化数组)。尝试这样的普通 while/for 循环:

              int maxNumber = 21;
              int n = 1;
              bool found = false;
              while(!found)
              {
                  found = true;
                  for(int i = 1; i < maxNumber; i++)
                  {
                      if(n % i != 0)
                      {
                          found = false;
                          break;
                      }
                  }
                  n++;
              }
              return n-1;
      

      这在我的电脑上运行了大约 4 秒,处于调试状态。

      编辑

      在考虑进一步优化时,最好开始测试较大数字的模,所以当我将 for 循环更改为:

      for (int i = maxNumber; i > 1; i--)
      

      时间降至 2 秒以下。

      数学上的见解是,我们只需要测试不是我们已经测试过的数字的倍数的数字的可分性。在我们的例子中,我们可以这样写:

          private int[] p = { 19, 17, 16, 13, 11, 9, 7, 5, 4, 3, 2 };
          int Job()
          {
              int n = 1;
              bool found = false;
              while (!found)
              {
                  found = true;
                  foreach (int i in p)
                  {
                      if (n % i != 0)
                      {
                          found = false;
                          break;
                      }
                  }
                  n++;
              }
              return n - 1;
          }
      

      但是,这实际上更慢,大约 2.5 秒。

      【讨论】:

      • 你是对的。通过用 for 循环替换我的内部 Enumerable.Range,我将所用时间除以 5
      • 嗯 - 看起来它的速度大约是 Jon Skeet 的 12 倍,但我不知道我会称之为“非常”。在这种情况下,它可能会有所作为,但通常我会使用最容易阅读的内容。 stackoverflow.com/questions/408452/…
      • 用 Enumerable.Range 除以 10。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-03-01
      • 2023-03-21
      • 2019-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多