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
字段将根本不被使用。下面的代码创建一个1000000Message
实例的数组,然后在循环中迭代该数组。它测量迭代所需的时间
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,它们也在一个连续的块中)。总而言之,它似乎可以分解为:
消息
实例时获取对下一个元素的引用。这是“免费”的,因为无论如何都必须访问每个消息实例(就像在数组中一样)
消息
实例可能在内存中的“任何地方”,但它们可能彼此非常接近,因为它们是“同时”分配的。但1000000个实例无法缓存,只能缓存其中的一部分。在这种情况下,顺序访问在数组和链表中都是缓存友好的,所以这并不能解释区别消息实例进行智能“预测”(预取)?也就是说,在某种程度上,消息
实例本身仍然具有缓存友好性,但仅在阵列访问的情况下才具有缓存友好性
我还要说这是谁
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 ==============================