【问题标题】:Does Java JIT compiler sacrifice performance to favor Collections?Java JIT 编译器是否会牺牲性能来支持集合?
【发布时间】:2013-03-04 13:43:04
【问题描述】:

考虑以下两个代码示例。所有基准测试都是在用于计算采样执行时间平均值的容器之外完成的。在我的机器上,运行 Windows 7 和 JDK 1.6,我看到示例 2 中的平均执行时间比示例 1 慢了近 1,000 倍。我可以推测的唯一解释是编译器正在优化 LinkedList 使用的一些代码以其他一切的损害。有人可以帮我理解这一点吗?

示例 1:使用数组

public class TimingTest 
{

    static long startNanos, endNanos;
    static long[] samples = new long[1000];


    public static void main(String[] args) 
    {
    for (int a = 0; a < 100; a++) 
    {
        for (int numRuns = 0; numRuns < 1000; numRuns++) 
        {
            startNanos = System.nanoTime();
            long sum = 0;
            for (long i = 1; i <= 500000; i++) 
            {
                sum += i % 13;
            }
            endNanos = System.nanoTime() - startNanos;
            samples[numRuns] =(endNanos);
        }
        long avgPrim = 0L;
        for (long sample : samples) 
        {
            avgPrim += sample;
        }
        System.out.println("Avg: " + (avgPrim / samples.length) );
        }
    }
}

示例 2:使用 LinkedList

public class TimingTest 
{

    static long startNanos, endNanos;
    static List<Long> samples = new LinkedList<Long>();

    public static void main(String[] args) 
    {
        for (int a = 0; a < 100; a++) 
        {
            for (int numRuns = 0; numRuns < 1000; numRuns++) 
            {
                startNanos = System.nanoTime();
                long sum = 0;
                int index = 0;
                for (long i = 1; i <= 500000; i++) 
                {
                    sum += i % 13;
                }
                endNanos = System.nanoTime() - startNanos;
                samples.add(endNanos);
            }
            long avgPrim = 0L;
            for (long sample : samples) 
            {
                avgPrim += sample;
            }
            System.out.println("Avg: " + (avgPrim / samples.size()));
        }
    }
}

【问题讨论】:

  • 链表的性能特征比数组差,没什么大不了的。
  • 未对 LinkedList 进行基准测试! LinkedList 仅存储基准测试结果。
  • @vemv 这是一个不完整且具有误导性的分析。链表具有比数组更好的性能特征。另外,在第二个例子中,求和代码的执行时间比较慢。
  • 我的赌注。将元素添加到列表的操作比添加到数组所需的操作要长;因此,当您返回循环代码时,您更有可能遭受 缓存未命中。尝试在每个循环之后放置一些复杂的操作(一些System.out.println,创建一些新对象),您会发现两个代码的性能都会收敛。
  • 你的内部循环基本上什么都不做。它可以被完全删除。通常的自适应编译器行为意味着这不会立即发生。基准代码的方法很长,因此可能会影响行为。我建议首先将两次调用 nanoTime 之间的代码移动到不同的方法中。 (LinkedList 通常性能很差,即使人们认为它会运行得很快。)

标签: java arrays performance collections jit


【解决方案1】:

这里出了点问题:当我运行数组版本时,我得到的平均执行时间为 20000 纳秒。我的 2 GHz CPU 在那个时候执行 500000 次循环迭代是完全不可能的,因为这意味着平均循环迭代需要 20000/500000 = 0.04 ns,或 0.08 个 cpu cpu 周期...

主要原因是你的时序逻辑中的一个错误:在数组版本中,你这样做了

int index = 0;

对于每个时间,因此

samples[index++] =(endNanos);

将始终分配给第一个数组元素,而将所有其他元素保留为默认值 0。因此,当您取数组的平均值时,您会得到最后一个样本的 1/1000,而不是所有样本的平均值。

确实,如果将 index 的声明移到循环之外,两个变体之间不会报告显着差异。

【讨论】:

    【解决方案2】:

    这是您的代码的真实运行(为了清楚起见,重命名了类,并将每个中的外部 for 循环剪切为 a &lt; 1 为时间缘故):

    $ for f in *.class
    do
      class=$(echo $f | sed 's`\(.*\)\.class`\1`')
      echo Running $class
      java $class
    done
    Running OriginalArrayTimingTest
    Avg: 18528
    Running UpdatedArrayTimingTest
    Avg: 41111273
    Running LinkedListTimingTest
    Avg: 41340483
    

    显然,您最初的担忧是由@meriton 指出的错字引起的,您在问题中已对此进行了更正。我们可以看到,对于您的测试用例,数组和 LinkedList 的行为几乎相同。一般来说,对 LinkedList 的插入非常快。由于您使用 Meriton 的更改更新了您的问题,但没有更新您声称前者比后者快得多的说法,因此您不再清楚您在问什么;但是我希望您现在可以看到,在这种情况下,两种数据结构的行为都相当相似。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-07
      相关资源
      最近更新 更多