在Java中对小数组与列表进行基准测试:我的基准测试代码是错误的吗?

时间:2010-02-09 09:26:13

标签: java performance microbenchmark

  

免责声明:我查看了this questionthis question   但是他们两个都出轨了   细节和一般   优化是不必要的问题。   我真的需要所有的表现   可以进入我当前的应用程序,这是   接收处理 - 喷出的MIDI数据   实时。它还需要扩大规模   尽可能。

我将array针对小型列表的大量读取的性能与ArrayList进行比较,并将变量放在手中。我发现一个数组比ArrayList高2.5倍,而且甚至只有对象引用。

我想知道的是:

  1. 我的基准可以吗? 我已经切换了测试顺序和运行次数没有变化。我也用毫秒而不是纳秒来无济于事。
  2. 我是否应该指定任何Java选项以最大限度地减少这种差异?
  3. 如果这种差异是真实的,在这种情况下在这种情况下我不应该更喜欢Test[]ArrayList<Test>并输入转换它们所需的代码吗?显然我阅读不仅仅是写作。
  4. JVM是OSX上的Java 1.6.0_17,它肯定在Hotspot模式下运行。

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

3 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:1)

Microbenchmarks非常非常难以在像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;
    }
}

答案 2 :(得分:0)

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

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