3

免责声明:我已经查看了这个问题这个问题 ,但它们都被小细节和一般优化 - 不必要的问题所困扰。我真的需要在我当前的应用程序中获得的所有性能,它正在实时接收-处理-喷出 MIDI 数据。它还需要尽可能地扩大规模

我将array小列表的大量读取性能与ArrayList手头的变量进行比较。我发现一个数组的节拍ArrayList是 2.5 倍,甚至比只有对象引用还要好。

我想知道的是:

  1. 我的基准还好吗?我已经切换了测试的顺序和运行次数,没有任何变化。我也使用毫秒而不是纳秒无济于事。
  2. 我应该指定任何 Java 选项来最小化这种差异吗?
  3. 如果这种差异是真实的,在这种情况下,我不应该更喜欢Test[]ArrayList<Test>这种情况下并放入转换它们所需的代码吗?显然,我阅读的内容远多于写作。

JVM 是 OSX 上的 Java 1.6.0_17,它肯定在热点模式下运行。

  public class ArraysVsLists {

    static int RUNS = 100000;

    public static void main(String[] args) {
        long t1;
        long t2;

        Test test1 = new Test();
        test1.thing = (int)Math.round(100*Math.random());
        Test test2 = new Test();
        test2.thing = (int)Math.round(100*Math.random());

        t1 = System.nanoTime();

        for (int i=0; i<RUNS; i++) {
            test1.changeThing(i);
            test2.changeThing(i);
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1) + " How long NO collection");

        ArrayList<Test> list = new ArrayList<Test>(1);
        list.add(test1);
        list.add(test2);
        // tried this too: helps a tiny tiny bit 
        list.trimToSize();

        t1= System.nanoTime();

        for (int i=0; i<RUNS; i++) {
            for (Test eachTest : list) {
                eachTest.changeThing(i);
            }
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1) + " How long collection");


        Test[] array = new Test[2];
        list.toArray(array);

        t1= System.nanoTime();

        for (int i=0; i<RUNS; i++) {
            for (Test test : array) {
                test.changeThing(i);
            }
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1) + " How long array ");

    }
}

class Test {
    int thing;
    int thing2;
    public void changeThing(int addThis) {
        thing2 = addThis + thing;
    }
}
4

3 回答 3

1

只有当您的实际用例与基准代码匹配时,您的基准测试才有效,即每个元素上的操作很少,因此执行时间很大程度上取决于访问时间而不是操作本身。如果是这种情况,那么是的,如果性能至关重要,您应该使用数组。但是,如果您的实际用例涉及每个元素的更多实际计算,那么每个元素的访问时间将变得不那么重要。

于 2010-02-09T09:33:24.313 回答
1

在像 Java 这样的平台上,微基准测试非常非常难。您肯定必须提取代码以进行基准测试到单独的方法中,将它们运行数千次作为预热然后进行测量。我已经这样做了(下面的代码),结果是通过引用直接访问的速度是通过数组的三倍,但集合仍然慢了 2 倍。

这些数字基于 JVM 选项-server -XX:+DoEscapeAnalysis。没有-server,使用集合会大大慢(但奇怪的是,直接和数组访问要快得多,这表明发生了一些奇怪的事情)。-XX:+DoEscapeAnalysis为集合产生另外 30% 的加速,但它是否适用于您的实际生产代码非常值得怀疑。

总的来说,我的结论是:忘记微基准,它们很容易产生误导。测量尽可能接近生产代码,而无需重写整个应用程序。

import java.util.ArrayList;

public class ArrayTest {

    static int RUNS_INNER = 1000;
    static int RUNS_WARMUP = 10000;
    static int RUNS_OUTER = 100000;

    public static void main(String[] args) {
        long t1;
        long t2;

        Test test1 = new Test();
        test1.thing = (int)Math.round(100*Math.random());
        Test test2 = new Test();
        test2.thing = (int)Math.round(100*Math.random());

        for(int i=0; i<RUNS_WARMUP; i++)
        {
            testRefs(test1, test2);            
        }
        t1 = System.nanoTime();
        for(int i=0; i<RUNS_OUTER; i++)
        {
            testRefs(test1, test2);            
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1)/1000000.0 + " How long NO collection");

        ArrayList<Test> list = new ArrayList<Test>(1);
        list.add(test1);
        list.add(test2);
        // tried this too: helps a tiny tiny bit 
        list.trimToSize();

        for(int i=0; i<RUNS_WARMUP; i++)
        {
            testColl(list);
        }
        t1= System.nanoTime();

        for(int i=0; i<RUNS_OUTER; i++)
        {
            testColl(list);
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1)/1000000.0 + " How long collection");


        Test[] array = new Test[2];
        list.toArray(array);

        for(int i=0; i<RUNS_WARMUP; i++)
        {
            testArr(array);            
        }
        t1= System.nanoTime();

        for(int i=0; i<RUNS_OUTER; i++)
        {
            testArr(array);
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1)/1000000.0 + " How long array ");

    }

    private static void testArr(Test[] array)
    {
        for (int i=0; i<RUNS_INNER; i++) {
            for (Test test : array) {
                test.changeThing(i);
            }
        }
    }

    private static void testColl(ArrayList<Test> list)
    {
        for (int i=0; i<RUNS_INNER; i++) {
            for (Test eachTest : list) {
                eachTest.changeThing(i);
            }
        }
    }

    private static void testRefs(Test test1, Test test2)
    {
        for (int i=0; i<RUNS_INNER; i++) {
            test1.changeThing(i);
            test2.changeThing(i);
        }
    }
}

class Test {
    int thing;
    int thing2;
    public void changeThing(int addThis) {
        thing2 = addThis + thing;
    }
}
于 2010-02-09T10:29:29.823 回答
0

它可能无效。如果我了解 JIT 编译器的工作方式,编译方法不会影响对该方法的调用,该方法已经在执行。由于该main方法只被调用一次,它最终会被解释,并且由于大部分工作是在该方法的主体中完成的,因此您获得的数字不会特别表明正常执行。

JIT 编译效果可能在某种程度上解释了为什么没有集合的情况比数组的情况慢。这个结果是违反直觉的,它对您报告的另一个基准测试结果产生了怀疑。

于 2010-02-09T10:03:50.150 回答