繁体   English   中英

可能在我的代码中优化?

[英]Possible optimization in my code?

为了解决欧拉项目问题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秒
  • 删除了外部和内部的enumerable.Range调用:1.5秒
  • 只取偶数:(只有循环,没有enumerable.range):小于1秒
  • 使用drhirsch建议的Gcd / LCm欧几里德算法:0.004 ms

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

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

这要求数字1..20的最小公倍数 ,可以通过找到lcm (1,2),然后lcm(lcm(1,2),3)等直到20来连续计算。

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

#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;
}

这会立即吐出解决方案。

使用Enumerable很慢(请参阅此内容以比较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秒。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM