Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/android/186.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 数组/链表:性能取决于遍历的*方向*?_Java_Android_Arrays_Linked List_Iteration - Fatal编程技术网

Java 数组/链表:性能取决于遍历的*方向*?

Java 数组/链表:性能取决于遍历的*方向*?,java,android,arrays,linked-list,iteration,Java,Android,Arrays,Linked List,Iteration,该员额分为两个主要部分。第一部分介绍了最初的测试用例和结果,以及我的想法。第二部分详细介绍了修改后的测试用例及其结果 本主题的原始标题是“在数组上的完全迭代要比使用链表快得多”。由于新的测试结果(见第二节),标题发生了变化 第一节:原始测试用例 对于完整的单向顺序遍历,已知链表和数组具有类似的性能,但由于连续数组的缓存友好性(引用位置),它的性能可能(稍微)更好。为了了解它在实践中是如何工作的(Android、Java),我检查了上述声明,并进行了一些测量 首先,我天真的假设。让我们来看看下面

该员额分为两个主要部分。第一部分介绍了最初的测试用例和结果,以及我的想法。第二部分详细介绍了修改后的测试用例及其结果

本主题的原始标题是“在数组上的完全迭代要比使用链表快得多”。由于新的测试结果(见第二节),标题发生了变化


第一节:原始测试用例 对于完整的单向顺序遍历,已知链表和数组具有类似的性能,但由于连续数组的缓存友好性(引用位置),它的性能可能(稍微)更好。为了了解它在实践中是如何工作的(Android、Java),我检查了上述声明,并进行了一些测量

首先,我天真的假设。让我们来看看下面的类:

private static class Message {
    public int value1;
    public Message next;

    public Message(java.util.Random r, Message nextmsg) {
        value1 = r.nextInt();
        next = nextmsg;
    }
}
在第一个测量场景中,其
next
字段将根本不被使用。下面的代码创建一个1000000
Message
实例的数组,然后在循环中迭代该数组。它测量迭代所需的时间

Log.i("TEST", "Preparing...");          

final int cnt = 1000000;
int val = 0;
java.util.Random r = new java.util.Random();
Message[] messages = new Message[cnt];
for (int i = 0; i < cnt; i++) {
    messages[i] = new Message(r, null);
}           

Log.i("TEST", "Starting iterating...");
long start = SystemClock.uptimeMillis();

for (int i = 0; i < cnt; i++) {
    Message msg = messages[i];
    if (msg.value1 > 564645) {
        val++;
    }
}       
Log.w("TEST", "Time: " + (SystemClock.uptimeMillis() - start) + ", result: " + val);
第一个测试持续产生41-44ms,而第二个测试产生80-85ms。链表迭代似乎要慢100%

我(可能有缺陷)的思路和问题如下。我欢迎(事实上,鼓励)任何更正

好的,我们经常可以看到数组是一个连续的内存块,因此按顺序访问它的元素比链表更容易缓存但是在我们的例子中,数组的元素只是对象引用,而不是
消息
对象本身(在Java中,我们没有C#中的值类型,即可以存储在数组中的struct)。因此,“引用的局部性”仅适用于数组元素本身,并且这些元素仅指定对象的地址。因此,
消息
实例(通常)仍可能在内存中的“任何地方”,因此“引用的位置”不适用于实例本身。从这一点来看,我们似乎与链表的情况相同:实例本身可能驻留在内存中的“任何地方”:数组仅保证它们的引用存储在连续块中

…下面是用例:完整的顺序遍历(迭代)。首先,让我们检查一下在每种情况下如何获得对实例的引用。对于数组,它非常有效,因为它们位于一个连续的块中但是对于链表,我们也很好,因为一旦我们访问了
消息
实例(这就是我们迭代的原因!),我们立即就有了对下一个实例的引用。由于我们已经访问了
Message
的一个字段,所以访问另一个字段(“下一个”)应该受到缓存的支持(同一个对象的字段也有一个引用的局部性AFAIK,它们也在一个连续的块中)。总而言之,它似乎可以分解为:

  • 该数组提供了对引用的缓存友好的迭代<代码>消息实例本身可能在内存中的“任何位置”,我们也需要访问这些实例
  • 链表提供了在访问当前
    消息
    实例时获取对下一个元素的引用。这是“免费”的,因为无论如何都必须访问每个
    消息
    实例(就像在数组中一样)
  • 因此,基于上述情况,看起来数组并不比链表好。唯一的例外是当数组是基元类型时(但在这种情况下,将其与链表进行比较是没有意义的)。所以我希望他们的表现类似,但他们没有,因为有巨大的差异。事实上,如果我们假设数组索引需要在每次访问元素时进行范围检查,那么链表(理论上)可能会更快,甚至更快。(阵列访问的范围检查可能通过JIT进行了优化,因此我知道这不是一个有效点。)

    我的猜测如下:

  • 可能不是阵列的缓存友好性造成了100%的差异。相反,JIT执行的优化在链表遍历的情况下无法完成。如果消除了范围检查和(VM级别)空检查,那么我猜“array get”字节码指令可能比链表中的“field get”(或其他任何名称)指令更快(?)

  • 尽管
    消息
    实例可能在内存中的“任何地方”,但它们可能彼此非常接近,因为它们是“同时”分配的。但1000000个实例无法缓存,只能缓存其中的一部分。在这种情况下,顺序访问在数组和链表中都是缓存友好的,所以这并不能解释区别

  • 对我将访问的
    消息
    实例进行智能“预测”(预取)?也就是说,在某种程度上,
    消息
    实例本身仍然具有缓存友好性,但仅在阵列访问的情况下才具有缓存友好性

  • 更新:由于收到了一些评论,我想在下面作出回应

    @不可改变的:

    链接列表从高地址访问到低地址。如果…怎么办 这是另一种方式,即下一个指向一个较新的对象,而不是一个新的对象 先前对象

    非常好的位置!我没有想到这个小细节,布局可能会影响测试。今天我将测试它,并返回结果。(编辑:结果在这里,我用“第2节”更新了这篇文章)

    @托本评论:


    我还要说这是谁
    Log.i("TEST", "Preparing...");          
    
    final int cnt = 1000000;
    int val = 0;
    java.util.Random r = new java.util.Random();
    Message current = null;
    Message previous = null;
    for (int i = 0; i < cnt; i++) {
        current = new Message(r, previous);
        previous = current;
    }
    previous = null;
    
    Log.i("TEST", "Starting iterating...");
    long start = SystemClock.uptimeMillis();
    while (current != null) {
        if (current.value1 > 564645) {
            val++;
        }
        current = current.next;
    }           
    
    Log.w("TEST","Time: " + (SystemClock.uptimeMillis() - start) + ", result: " + val);
    
    Message current = null;
    Message previous = new Message(r, null);
    Message first = previous;
    for (int i = 0; i < cnt; i++) {
        current = new Message(r, null);
        previous.next = current;
        previous = current;
    }       
    previous = current = null;
    
    while (first != null) {
        if (first.value1 > 564645) {
            val++;
        }
        first = first.next;
    }
    
    for (int i = cnt - 1; i >= 0; i--) {
        Message msg = messages[i];
        if (msg.value1 > 564645) {
            val++;
        }
    }
    
      length method         ns linear runtime
         100  ARRAY       63.7 =
         100 LINKED      190.1 =
        1000  ARRAY      725.7 =
        1000 LINKED     1788.5 =
     1000000  ARRAY  2904083.2 ===
     1000000 LINKED  3043820.4 ===
    10000000  ARRAY 23160128.5 ==========================
    10000000 LINKED 25748352.0 ==============================