Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/375.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 为什么ArrayList的表现要比LinkedList好?_Java_Performance_List - Fatal编程技术网

Java 为什么ArrayList的表现要比LinkedList好?

Java 为什么ArrayList的表现要比LinkedList好?,java,performance,list,Java,Performance,List,我做了一些研究并写了以下文章:我想在这里发布一个问题 class ListPerformanceSpec extends Specification { def "Throwaway"() { given: "A Linked List" List<Integer> list List<Integer> results = new LinkedList<>() when: "Adding

我做了一些研究并写了以下文章:我想在这里发布一个问题

class ListPerformanceSpec extends Specification {
    def "Throwaway"() {
        given: "A Linked List"
        List<Integer> list
        List<Integer> results = new LinkedList<>()

        when: "Adding numbers"
        Random random = new Random()
        //test each list 100 times
        for (int ix = 0; ix < 100; ++ix) {
            list = new LinkedList<>()
            LocalDateTime start = LocalDateTime.now()

            for (int jx = 0; jx < 100000; ++jx) {
                list.add(random.nextInt())
            }

            LocalDateTime end = LocalDateTime.now()
            long diff = start.until(end, ChronoUnit.MILLIS)
            results.add(diff)
        }

        then: "Should be equal"
        true
    }

    def "Linked list"() {
        given: "A Linked List"
        List<Integer> list
        List<Integer> results = new LinkedList<>()

        when: "Adding numbers"
        Random random = new Random()
        //test each list 100 times
        for (int ix = 0; ix < 100; ++ix) {
            list = new LinkedList<>()
            LocalDateTime start = LocalDateTime.now()

            for (int jx = 0; jx < 100000; ++jx) {
                list.add(random.nextInt())
            }

            long total = 0

            for (int jx = 0; jx < 10000; ++jx) {
                for (Integer num : list) {
                    total += num
                }
                total = 0
            }

            LocalDateTime end = LocalDateTime.now()
            long diff = start.until(end, ChronoUnit.MILLIS)
            results.add(diff)
        }

        then: "Should be equal"
        System.out.println("Linked list:" + results.toString())
        true
    }

    def "Array list"() {
        given: "A Linked List"
        List<Integer> list
        List<Integer> results = new LinkedList<>()

        when: "Adding numbers"
        Random random = new Random()
        //test each list 100 times
        for (int ix = 0; ix < 100; ++ix) {
            list = new ArrayList<>()
            LocalDateTime start = LocalDateTime.now()

            for (int jx = 0; jx < 100000; ++jx) {
                list.add(random.nextInt())
            }

            long total = 0

            for (int jx = 0; jx < 10000; ++jx) {
                for (Integer num : list) {
                    total += num
                }
                total = 0
            }

            LocalDateTime end = LocalDateTime.now()
            long diff = start.until(end, ChronoUnit.MILLIS)
            results.add(diff)
        }

        then: "Should be equal"
        System.out.println("Array list:" + results.toString())
        true
    }

}
为什么ArrayList在顺序访问方面比LinkedList快28%,而LinkedList应该更快

我的问题不同于,因为我不是问什么时候选择它,而是问为什么它更快

为什么ArrayList在顺序访问方面比LinkedList快28%,而LinkedList应该更快

你是在假设,但不要提供任何支持。但这并不是什么大惊喜。ArrayList有一个数组作为底层数据存储。按顺序访问这个元素的速度非常快,因为您确切地知道每个元素的位置。只有当阵列增长超过一定大小并需要扩展时,才会出现减速,但这是可以优化的


真正的答案可能是:检查Java源代码,比较ArrayList和LinkedList的实现。

与Java ArrayList一样,基于数组的列表在相同数据量下使用的内存要比基于链接的列表LinkedList少得多,并且此内存是按顺序组织的。这从本质上减少了CPU缓存对端数据的破坏。只要访问RAM所需的延迟是一级/二级缓存访问的10-20倍,就会产生足够的时间差

您可以在诸如、或类似资源的书籍中阅读有关这些缓存问题的更多信息

OTOH,基于链接的列表在操作上优于基于数组的列表,例如插入到列表中间或在其中删除


对于同时具有内存经济性、快速迭代和快速插入/删除的解决方案,应该考虑组合方法,如内存B⁺-树,或按比例增加大小的数组列表数组。

来自LinkedList源代码:

/**
 * Appends the specified element to the end of this list.
 *
 * <p>This method is equivalent to {@link #addLast}.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    linkLast(e);
    return true;
}

/**
 * Links e as last element.
 */
void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}
/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

 private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
从ArrayList源代码:

/**
 * Appends the specified element to the end of this list.
 *
 * <p>This method is equivalent to {@link #addLast}.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    linkLast(e);
    return true;
}

/**
 * Links e as last element.
 */
void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}
/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

 private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
所以链表必须为添加的每个元素创建新节点,而数组列表则不能。ArrayList并没有为每个新元素重新分配/调整大小,所以大多数时候ArrayList只是在数组中设置对象并增加大小,而链表做的工作要多得多

你还评论说:

当我在大学里写一个链表时,我一次分配一个区块,然后把它们分出去


我认为这在Java中不起作用。您无法在Java中使用指针技巧,因此必须分配大量小数组,或者提前创建空节点。在这两种情况下,开销可能会稍高。

一种解释是,乘法比内存提取慢的基本假设是有问题的

基于,AMD推土机需要1个时钟周期来执行64位整数乘法指令寄存器x寄存器,6个延迟周期为1。相比之下,寄存器加载的内存需要1个时钟周期和4个延迟周期。但是,这假设您在获取内存时得到了缓存命中。如果缓存未命中,则需要添加若干个周期。二级缓存未命中的20个时钟周期,根据

现在这只是一种架构,其他架构将有所不同。我们还需要考虑其他问题,比如可以重叠的乘法数的约束,以及编译器可以组织指令以使它们最小化指令依赖性。但事实仍然是,对于典型的现代流水线芯片体系结构,CPU执行整数倍运算的速度与执行内存到寄存器移动的速度一样快,如果内存回迁中有更多缓存未命中,则执行整数倍运算的速度要快得多

您的基准测试使用的是包含100000个整数元素的列表。当您查看所涉及的内存量以及表示列表和元素的堆节点的相对位置时,链表案例将使用更多的内存,并且相应地具有更差的内存位置。这将导致内部循环的每个周期都有更多的缓存未命中,并且性能更差

你的基准测试结果对我来说并不意外

另一件需要注意的事情是,如果您使用JavaLinkedList,那么将使用一个单独的堆节点来表示列表节点。如果元素类有自己的下一个字段可用于链接元素,则可以更有效地实现自己的链接列表。但也有其自身的局限性;e、 g.一个元素一次只能在一个列表中

最后,正如@maaartinus所指出的,在Java ArrayList中不需要完整的IMUL。在读取或写入ArrayList的数组时,索引乘法将为x4或x8,并且可以由具有标准寻址模式之一的MOV执行;e、 g

MOV EAX, [EDX + EBX*4 + 8]
这种乘法可以通过比64位IMUL的延迟小得多的移位在硬件级别完成

1-在此上下文中,延迟是指令结果可用之前的延迟周期数。。。到依赖于它的下一条指令。诀窍是对指令进行排序,以便在de过程中完成其他工作 躺着


2-如果有什么不同的话,我很惊讶LinkedList的表现如此出色。也许调用Random.nextInt和autoboxing结果会主导循环时间?

什么时候LinkedList应该更快?基于什么,确切地说?你能为这个说法提供一个来源吗?@Thom如果你只需要顺序访问,那么使用链表是有益的,因为加法不需要重新分配整个数组,但我从来没有听说在迭代中LL比数组快。@Thom,链表速度更快的情况是插入和删除,特别是考虑到一个节点注释:链表上任何地方都没有索引。顺序迭代应该不会有太大的不同,但是数组列表有一些局部性,而链表没有局部性,所以缓存的使用会有很大的不同;对于对象引用,您可能没有注意到,因为数组列表和对象本身之间没有局部性。随机访问,即在链表上基于索引的访问要比在数组列表上差得多。这可能很有趣:不是一个假设。数组列表必须执行乘法才能为每个顺序访问生成指针。链表只需取消引用指针即可。需要一半的指令。@在测试中,您正在将对象添加到列表中。链表必须为每个条目创建新节点,而ArrayList则不然。@Piro这很可能是真的,但这将是一个悲哀的实现。当我在大学里写一个链表时,我一次分配一个区块,然后把它们分出去。这可能是重点,也是我的答案。@Thom如果不检查实际执行情况,重复你在大学学到的东西是没有意义的。您还忘记了ArrayList只是将vanilla对象存储在一个数组中,而LinkedList必须为列表中的每个条目创建一个节点对象。这意味着每次加法要创建两个对象。现在,哪一个更昂贵:每隔一段时间乘以一次,还是创建成吨的节点对象?@Thom您的指令计数在大约20年前可能是正确的。在我的旧i5上,32位乘法有3个周期的延迟。寻址所需的4或8的乘法需要一个周期,因为它只是一个移位。乘以2、4或8是地址计算的一部分,不需要单独的指令。缓存未命中需要数十个周期。LinkedList没有内存局部性,并且内存消耗相当高,因此保证了许多缓存未命中。忘记LinkedList吧,至少在Java.Yep中是这样。这种实现很糟糕。谢谢。您忽略了一个事实,即只有引用存储在数组中,对象本身仍然在堆上,因此如果您实际接触对象,那么缓存位置应该没有什么不同。此外,在单链接列表中,您只有一个额外的引用要存储,因此对于相同的数据,它的内存不会少很多amount@JimGarrison是的,这种引用减少了紧凑存储的影响,但是3个引用加上对象头部加上可能的内存缺口比1个引用花费的要多。而且,Java中的具体LinkedList是双链接的,与之相比,topic starter使用的是单链接列表,因此,您的引用单链接列表用词不当。链接列表的速度慢通常与内存效率关系不大,而与数据依赖性和内存延迟关系很大。好的,您必须乘以然后进行获取。这还是一个额外的手术,不。因为Java ArrayList与LinkedList的对比是一个IMUL和一个MOV与两个MOV的对比。在第一种情况下,缓存未命中的概率低于第二种情况。即使在链接元素的情况下,缓存未命中率的差异可能足以使IMUL+MOV比一个MOV快。但我们现在还需要考虑缓存行大小、内存提取宽度等问题。或者编译器做聪明的优化来避免IMUL,虽然对于一个非常好的C++编译器来说,这是更合理的,而不是java编译器。没有乘法运算,或者我是盲目的???寻址所需的乘法只是一个移位,而寻址模式正是这样做的。@maaartinus-你很可能是对的。我一直相信OP的话,乘法是必需的。