【问题标题】:Confused by the running time for finding the sum of even fibonacci numbers对查找偶数斐波那契数之和的运行时间感到困惑
【发布时间】:2012-12-02 07:08:41
【问题描述】:

我对 Project Euler 问题 2 有两个解决方案,即找到所有小于 400 万的偶数斐波那契数的总和。

解决方案一(平均需要 11,000 纳秒):

public class Solution {

static long startTime = System.nanoTime();
static final double UPPER_BOUND = 40e5;
static int sum = 2;

public static int generateFibNumber(int number1, int number2){
    int fibNum = number1+ number2;
    return fibNum;
}

public static void main( String args[] ) {
    int i = 2;
    int prevNum = 1;
    while(i <= UPPER_BOUND) {
        int fibNum = generateFibNumber(prevNum,i);
        prevNum = i;
        i = fibNum;
        if (fibNum%2 == 0){
            sum += fibNum;
        }
    }
    long stopTime = System.nanoTime();
    long time = stopTime - startTime;
    System.out.println("Sum: " + sum);
    System.out.println("Time: "+ time);
}

和解决方案二(平均需要 14,000 纳秒):

public class Solution2 {
static long startTime = System.nanoTime();  
final static int UPPER_BOUND = 4_000_000;
static int penultimateTerm = 2;                                         
static int prevTerm = 8;                                                
static int currentTerm = 34;                                             
static int sum = penultimateTerm+ prevTerm;                 

public static void main( String args[]) {
    while (currentTerm <= UPPER_BOUND) {
        sum+= currentTerm;
        penultimateTerm = prevTerm;
        prevTerm = currentTerm;
        currentTerm = (4*prevTerm) + penultimateTerm;
    }

    long stopTime = System.nanoTime();
    long time = stopTime - startTime;
    System.out.println("Sum: " + sum);
    System.out.println("Time: " + time);

}

当我在 while 循环中执行更少的迭代并且也没有 if 语句时,为什么解决方案二需要更长的时间? 这可以更有效地完成吗?

【问题讨论】:

  • 您的计时码不准确;您应该在进入 while 循环之前在 main 方法中启动计时器,而不是作为字段初始化器。
  • 就是这样。第一个得到 3400 纳秒,第二个得到 3000 纳秒。这是我所期待的(第二个更快)
  • 没有执行足够的迭代来预热 JVM,这毫无价值。这意味着您正在处理解释器行为方式的变幻莫测。注意:您可以通过使用每三个偶数的斐波那契数来减少数字迭代(这会将迭代次数减少三倍)

标签: java performance algorithm


【解决方案1】:

只运行一次算法是评估其性能的一种非常不可靠的方法,尤其是当时间约为 10ns 时。您的第二种方法确实更快。我重写了你的代码,将每个算法迭代了 100 次,得到了完全不同的结果。

代码:

public class Fib {
    private static int UPPER_BOUND = 4000000;
    private static int ITERS = 100;
    public static void main(String[] args) {
        long time1, time2;
        int sum1 = 0, sum2 = 0;
        long startTime = System.nanoTime();
        for (int iter = 0; iter < ITERS; ++iter) {
            sum1 = sol1();
        }
        time1 = System.nanoTime() - startTime;

        startTime = System.nanoTime();
        for (int iter = 0; iter < ITERS; ++iter) {
            sum2 = sol2();
        }
        time2 = System.nanoTime() - startTime;
        System.out.println("Time1 = " + time1 + "; sum1 = " + sum1);
        System.out.println("Time2 = " + time2 + "; sum2 = " + sum2);
    }

    private static int sol1() {
        int sum = 2;
        int i = 2;
        int prevNum = 1;
        while(i <= UPPER_BOUND) {
            int fibNum = generateFibNumber(prevNum,i);
            prevNum = i;
            i = fibNum;
            if (fibNum%2 == 0){
                sum += fibNum;
            }
        }
        return sum;
    }

    private static int sol2() {
        int penultimateTerm = 2;
        int prevTerm = 8;
        int currentTerm = 34;
        int sum = penultimateTerm + prevTerm;
        while (currentTerm <= UPPER_BOUND) {
            sum += currentTerm;
            penultimateTerm = prevTerm;
            prevTerm = currentTerm;
            currentTerm = (prevTerm << 2) + penultimateTerm;
        }
        return sum;
    }

    private static int generateFibNumber(int number1, int number2) {
        return number1+ number2;
    }
}

结果(典型):

时间1 = 189910; sum1 = 4613732
时间2 = 35501; sum2 = 4613732

请注意,在第二种算法中,我将(4*prevTerm) 更改为(prevTerm &lt;&lt; 2),这会稍微快一些。这将时间缩短了大约 5%。每个测试仍然有很多开销:一个函数调用并将结果分配给一个局部变量。但是,通过迭代,您不会在调用 System.nanoTime() 时陷入困境。

请注意,您的第一个代码也将double 用于UPPER_BOUND,这会减慢它的速度。我的代码试图使测试尽可能并行。

【讨论】:

  • 我可能应该编写一个 bash 脚本来运行我的代码 1000 次。有没有更可靠的方法来查看/图形算法运行时间?
  • @yudhishthirsingh - 我不会使用 bash 脚本。这增加了开销,因为您每次都需要加载和启动程序。开销越多,您尝试测量的事物之间的区别就越模糊。最好的方法是尽可能多地隔离您要测量的内容,然后将时序代码尽可能紧密地包裹起来。
【解决方案2】:

第二个版本更快。正如在 cmets 中指出的那样,您的计时不准确。此外,对需要几微秒的功能进行计时也是不可靠的。您应该循环运行代码并计算 x 次迭代的总时间,然后使用它来计算每次迭代的平均时间。

我还认为说明代码为何有效可能很有用。请注意,偶数出现在每三个索引处。

1  1  2  3  5  8 13 21 34
      ^        ^        ^

第二个版本只直接计算偶数。它通过从 F(n) 和 F(n-3) 计算 F(n+3) 的值来做到这一点。

F(n + 3) = F(n + 2) + F(n + 1)
         = F(n + 1) + F(n) + F(n + 1)                              [1]
         = F(n) + F(n - 1) + F(n) + F(n) + F(n - 1)                [2]
         = F(n) + F(n - 2) + F(n - 3) + F(n) + F(n) + F(n - 1)     [3]
         = F(n) + F(n) + F(n - 3) + F(n) + F(n)                    [4]
         = 4 * F(n) + F(n - 3)

使用以下身份:

  1. F(n + 2) = F(n + 1) + F(n)
  2. F(n + 1) = F(n) + F(n - 1)
  3. F(n - 1) = F(n - 2) + F(n - 3)
  4. F(n) = F(n - 1) + F(n - 2)

【讨论】:

  • 问题不在于为什么第二种解决方案有效。这就是执行时间更长的原因。
  • @TedHopp:哦,我误读了这个问题……但是我想这个答案对某些人来说可能还是有用的。
  • 确实是一个不错的推导。
猜你喜欢
  • 2015-07-25
  • 1970-01-01
  • 2011-02-04
  • 2016-04-07
  • 2015-11-12
  • 1970-01-01
  • 2021-12-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多