在下面的代码中,在我的测试用例中,indexOfSlow大约比indexOf慢17倍,尽管前者进行的比较比后者少.

Why is that?

(我的直觉是,indexOf的卓越性能归功于顺序数组访问,它具有缓存等好处--但我想确切地知道)

来self 的机器的性能数据:

| elementCount | indexOf msec | indexOfSlow msec | slow/fast |
|--------------+--------------+------------------+-----------|
|        10000 |           41 |              852 |        21 |
|        20000 |          232 |             3387 |        15 |
|        30000 |          375 |             6642 |        18 |
|        40000 |          710 |            12699 |        18 |
|        50000 |         1197 |            19182 |        16 |
|--------------+--------------+------------------+-----------|
|      average |              |                  |      17.6 |

注:Java的PriorityQueue.indexOf()与我的indexOf类似,所以它不会像我的indexOfSlow那样try 爬树和早点停车.

class Heap<T extends Comparable<T>> {

    public static void main(String[] args) {
        int elementCount = 50_000;

        final List<Integer> elements = new ArrayList<>(elementCount);
        for (int i = 0; i < elementCount; i++)
            elements.add(i);

        for (int j = 0; j < 3; j++) {
            final var heap = new Heap<Integer>();
            for (int i : elements)
                heap.add(i);
            assert heap.peek() == 0;
            final long nanoBefore = System.nanoTime();
            for (int i = elementCount; i > 0; i--)
                heap.indexOf(i - 1);
//                heap.indexOfSlow(i - 1);
            final long nanoAfter = System.nanoTime();
            if (j > 0) // discard first round as warmup
                System.out.println("Time taken: " + (nanoAfter - nanoBefore) / 1_000_000 + " msec");
        }
    }

    private final ArrayList<T> list = new ArrayList<>();

    public T peek() {
        return list.isEmpty() ? null : list.get(0);
    }

    private void siftUp(int i, T value) {
        while (i > 0) {
            int parentI = (i - 1) / 2;
            final T parentValue = list.get(parentI);
            if (parentValue.compareTo(value) <= 0)
                return;
            list.set(parentI, value);
            list.set(i, parentValue);
            i = parentI;
        }
    }

    public void add(T value) {
        list.add(value);
        siftUp(list.size() - 1, value);
    }

    public int indexOf(T value) {
        final int size = list.size();
        if (size > 0) {
            for (int i = 0; i < list.size(); i++) {
                if (value.compareTo(list.get(i)) == 0)
                    return i;
            }
        }
        return -1;
    }

    public int indexOfSlow(T value) {
        final int size = list.size();
        if (size > 0) {
            Queue<Integer> childrenToVisit = new LinkedList<>();
            childrenToVisit.add(0);
            while (!childrenToVisit.isEmpty()) {
                int i = childrenToVisit.poll();
                final int cmp = list.get(i).compareTo(value);
                if (cmp == 0)
                    return i;
                if (cmp > 0)
                    continue;
                int rightChildIdx = (i + 1) * 2;
                int leftChildIdx = rightChildIdx - 1;
                if (leftChildIdx < size)
                    childrenToVisit.add(leftChildIdx);
                if (rightChildIdx < size)
                    childrenToVisit.add(rightChildIdx);
            }
        }
        return -1;
    }

}

推荐答案

首先,通过使用

final long nanoBefore = System.nanoTime();
//...
final long nanoAfter = System.nanoTime();

完全不可靠.要获得可靠的结果,您需要使用JMH.

其次,在indexOfSlow()人中,你使用的是LinkedList人,这是非常无效的.

第三,在indexOf()中不分配任何内存,一遍又一遍地迭代相同的列表.在indexOfSlow()中,您 for each 方法调用分配一个新的LinkedList.这显然耗费了我们的时间和GC.

第四,尽管在理论上线性搜索的复杂度为O(N),但在您的示例中,实际搜索的复杂度超过ArrayList,这是基于数组的,并且当您通过索引访问元素时速度极快(即使您需要迭代整个列表).相反,在indexOfSlow()中,您依赖于连续的poll()/add()操作.假设您正在查找49733,然后对于123等值,您将同时执行poll()add()运算.因此,在返回您正在查找的值之前,您的childrenToVisit列表被修改了99734(!)次.所以这个算法的实施效率非常低,这就是为什么你在数字上会有如此巨大的差异.

Java相关问答推荐

Informix PrepareStatement引发错误-将LIMIT Clause添加到查询时,字符到数字的转换过程失败

在没有maven或IDE的情况下从命令行运行PFA模块化应用程序时出现神秘错误

如何在Java中对自定义协议进行主机名验证?

参数值[...]与预期类型java.util.Date不匹配

JVM会优化这个数学运算吗?

为什么一个Test的instance?& gt;在构造函数中接受非空对象?

是否在允许数组元素为空时阻止 idea 为空性警告?

如何从错误通道回复网关,使其不会挂起

带错误BER验证的itext8签名返回pdf

如何让JVM在SIGSEGV崩溃后快速退出?

Docker不支持弹性APM服务器

如何将Java文档配置为在指定的项目根目录中生成?

Kotlin Val是否提供了与Java最终版相同的可见性保证?

如何创建模块信息类文件并将其添加到JAR中?

有谁能帮我修一下这个吗?使输出变得更加整洁

Java中HashSet的搜索时间与TreeSet的搜索时间

当我将JTextField的getText函数与相等的String进行比较时;t返回true

如何判断元素计数并在流的中间抛出异常?

如何以事务方式向ibmmq发送消息

OpenJDK20:JEP434:Foreign Function&;内存API(第二次预览)