为什么经验结果与TreeSet的最后一种方法中的理论数据不同?

时间:2012-05-08 09:57:31

标签: java algorithm benchmarking

我们正在对Java数据结构进行一些实证测试,并得到一些我们无法正确解释的结果。

例如,当我们测试TreeSet的last-method哪个时间要求应该是常量时,我​​们在TreeSet的大小超过30 000后得到了运行时间的冲击。我们运行last-method,增加了treeSet中元素的数量1000次对于每个尺寸,然后取结果的中值。

相关代码是:

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.ArrayList;
import java.util.Collections;
import jxl.write.WriteException;

public class TestRunner {


public void test(Testable testeCase, String outputFileName, Integer... initArgs){
    ExcelWriter excel = null;
    try {
        excel = new ExcelWriter(outputFileName);
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    ThreadMXBean threadMxBean = ManagementFactory.getThreadMXBean();
    int measurementsPoints = 35;
    //calculate median to every dataset size

    for(int j = 0; j < measurementsPoints; j++){
        int testCount = 1000;
        long startTime; 
        long endTime; 
        //double sum = 0;
        ArrayList<Integer> results = new ArrayList<Integer>();


        for (int i = 0; i < testCount; i++) {

            //initialize tested data structure
            testeCase.initTestRun(initArgs);

            startTime = threadMxBean.getCurrentThreadCpuTime();
            // run tested method
            testeCase.runTestMethod();
            endTime = threadMxBean.getCurrentThreadCpuTime();

            results.add((int)(endTime - startTime));

        }
        Collections.sort(results);
        excel.addNumber(j, 5, new Double(initArgs[0]));
        excel.addNumber(j, 6, new Double(results.get(testCount / 2)));

        //increase the size of the data structure 10, 15, 20, 30, 40, 60, 80...
        if(j % 2 == 0){
            initArgs[0]  = (int)(initArgs[0]  * 1.5);
        }
        else{
            initArgs[0] = (int)(initArgs[0]  / 3 * 4);
        }


    }
    try {
        excel.write();
    } catch (WriteException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

import java.util.TreeSet;


public class TreeSetLastTest implements Testable {

private TreeSet<Integer> values;



@Override
public void initTestRun(Integer... integers) {
    Integer initialCapacity = integers[0];
    values = new TreeSet<Integer>();
    for(int i = Integer.MIN_VALUE; i < Integer.MIN_VALUE + initialCapacity; i++){
        values.add(i);
    }


}

@Override
public void runTestMethod() {
    values.last();
}

}

当treeSet中的元素数量在10-30,000个元素之间时,测量的中值为3000 ns。当treeSet的大小增加到120 000个元素时,将测量的中值增加到13 000 ns,然后当元素数量增加超过一百万时保持在那里。那么导致增加的原因或者时间单位是如此之小以至于差异在现实中毫无意义。谢谢你的帮助。

1 个答案:

答案 0 :(得分:4)

我认为它值得回答。

您假设TreeSet有O(1)last()是错误的。首先,文档没有说明任何此类事实,实际上java中的TreeSet是使用TreeMap实现的,这是一个红黑树的实现。

红黑树类似于AVL树,可以更好地知道它保证查找O(log n),即确保树不会退化为链表。基本上,您的last()查找具有O(log n)复杂度,因此随着它变大会变得更糟。

大概是因为缓存,甚至是分页效果,你不能直接在基准测试中看到O(log n)

它类似于LinkedLists和数组 - 理论上链表有很多东西可供使用,实际上链表是你可以在现代CPU上使用的最糟糕的数据结构之一。常数因素确实很重要,内存访问模式是常数因子。