【问题标题】:long loop counter appears to be very inefficient长循环计数器似乎效率很低
【发布时间】:2014-06-30 03:48:48
【问题描述】:

我刚刚在一些 Java 玩具示例中观察到一个奇怪的性能问题。这是完整的程序:

import java.util.Random;

public class Weird
{
    public static long fib(int n)
    {
        long a = 0;
        long b = 1;
        for (long i = 0; i < n; ++i)   // note: loop counter of type long
        {
            long c = a + b;
            a = b;
            b = c;
        }
        return a;
    }

    public static void main(String[] args)
    {
        System.out.print("Warming up the JIT... ");
        warmUpTheJIT();
        System.out.println("Starting measurement");

        long bogus = 0L;
        long before = System.nanoTime();
        for (int i = 0; i < 500_000_000; ++i)
        {
            bogus += fib(46);
        }
        long after = System.nanoTime();
        System.out.println(bogus);

        long duration = after - before;
        long ms = duration / 1_000_000;
        System.out.println(ms +" ms");
    }

    private static void warmUpTheJIT()
    {
        Random rand = new Random();
        long bogus = 0L;
        for (int i = 0; i < 1_000_000; ++i)
        {
            bogus += fib(rand.nextInt(47));
        }
        System.out.println(bogus);
    }
}

代码在我的系统(64 位 Java 8 VM)上需要 21 秒。但是,如果我将循环计数器从 long 更改为 int,则只需 27 毫秒。这大约快了三个数量级。

那么...为什么循环计数器的类型会产生如此巨大的差异?

【问题讨论】:

  • 如果你把它改成short它会运行什么
  • 我们不是在讨论fib 函数中的for 循环吗?永远不会看到大于 47 的值,这肯定适合 short。然而,这将是糟糕的代码,因为参数类型是 int 并且 可能 比 short 大。
  • JIT 对int 计数器使用了许多优化。通常不会有太大的不同,但它们可以产生很大的不同,这种情况很少见。

标签: java performance loops for-loop long-integer


【解决方案1】:

我可能是这样写的

public class IntLoopMain {
    public static long fib(int n) {
        long a = 0;
        long b = 1;
        for (long i = 0; i < n; ++i) {  // note: loop counter of type long
            long c = a + b;
            a = b;
            b = c;
        }
        return a;
    }

    static volatile long total = 0;

    public static void main(String... args) {
        System.out.println("Warming up the JIT... ");

        long start = 0;
        for (int i = -100000; i < 200_000_000; ++i) {
            if (i == 0) {
                start = System.nanoTime();
                System.out.println("Starting measurement");
            }
            total += fib(46 + (i & 3));
        }
        long time = System.nanoTime() - start;
        if (total == 0)
            System.out.println(total);

        System.out.printf("Took %.3f secs%n", time / 1e9);
    }
}

带有long i 循环

Took 8.166 secs

带有int i 循环

Took 3.549 secs

如果我拿出+ (i &amp; 3)

Took 7.296 secs <- long
Took 1.317 secs <- int

我的观点是int 循环被优化得更重(因为它们更常见)

假设我做了一些手动循环展开。 AFAIK int 循环做更多展开。

public static long fib2(int n) {
    long a = 0;
    long b = 1;
    for (int i = 1; i < n; i+=2) {  // note: loop counter of type long
        long c = a + b;
        long d = b + c;
        a = c;
        b = d;
    }
    return (n & 1) == 0 ? a : b;
}

使用fib2(46)

Took 3.828 secs <- long
Took 1.321 secs <- int

手动展开没有帮助(除非 JIT 还没有展开)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-18
    • 2012-07-19
    • 2016-06-28
    • 2019-02-12
    • 2020-10-10
    • 1970-01-01
    • 2020-12-09
    • 1970-01-01
    相关资源
    最近更新 更多