Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linked List vs. Array Traversal Efficiency

I know that an array is allocated as a contiguous block of memory and we can therefore access its elements by calculating the byte/word offset from the beginning of the array very easily.

I'm aware that linked list traversal is less efficient than array traversal due to cache inefficiency, where branch prediction won't work well in the way it would for an array. However, I've also heard that its quicker to iterate from one element of an array to the next than it is to access the pointer of the next element in a linked list due to the way we access the array using an offset.

How is the pointer access in the linked list slower than the offset access in the array?

like image 884
screeb Avatar asked Dec 12 '17 20:12

screeb


People also ask

Why are linked lists more efficient than arrays?

From a memory allocation point of view, linked lists are more efficient than arrays. Unlike arrays, the size for a linked list is not pre-defined, allowing the linked list to increase or decrease in size as the program runs. This is possible because to insert or delete from a linked list, the pointers need to be updated accordingly.

What is the difference between ArrayList and LinkedList in Java?

Array gets memory allocated in the Stack section. Whereas, linked list gets memory allocated in Heap section.

What is the O (k) for traversing a linked list?

The operation is O (k) in a linked list (where k is the index and may always be << n, depending) but if the linked list is already being traversed then this is O (1) per step which is "the same" as an array. If an array traversal ( for (i=0;i<len;i++) is faster (or slower) depends upon particular implementation/language/run-time.

What is the difference between an array and an ArrayList?

The array has a fixed size, and elements can be accessed using the index ( [ ]). The ArrayList does not have a fixed size. Elements can be accessed and modified using a set of predefined methods. Is a linked list faster than an array? No, a linked list allows sequential access and is slower while arrays allow direct and sequential access both.


1 Answers

cache inefficiency, where branch prediction won't work well

These are different things. Linked lists suffer from cache inefficiency:

  1. Nodes are usually not necessarily allocated contiguously and in order, which is bad for spatial locality. You can sometimes avoid this, for example with custom allocators. With generational garbage collection, allocating nodes closely together in time also tends to put them close together in space, but that's probably not a very common thing to actually happen when using a linked list.
  2. Having a pointer (and potentially other junk, like an object header and padding) in the node wastes space. Wasting a bunch of space is not inherently super bad, but it is bad when the wasted space is touched, which loads it into the cache. That actually happens here: that pointer to the next node is definitely needed, and the other junk is likely in the same cache line so it gets pulled in as well. This wastes both cache space and bandwidth (both to higher level caches and maybe to memory), which is pretty bad.

Linked lists don't really suffer from branch misprediction inherently. Yes, if you iterate over one, the last branch (the one that exits the loop) has a decent chance of being mispredicted, but that is not specific to linked lists.

How is the pointer access in the linked list slower than the offset access in the array?

Loading a pointer at all is slower than calculating the next address of an element in an array, both in terms of latency and in terms of throughput. For a quick comparison, typical on a modern machine is that loading that point takes around 4 cycles (at best! if there is a cache miss, it takes much longer) and could be done twice per cycle. Adding the size of an array element to the current address takes 1 cycle and can be done 4 times per cycle, and you (or the compiler) may be able to re-use the increment of the loop counter for this with some clever coding. For example, maybe you can use indexed addressing with the loop counter (which is incremented anyway) as index, or you can "steal" the loop counter entirely and increment it by the size of an element (scaling the loop-end correspondingly), or have no loop counter and directly compare the current address to the address just beyond the end of the array. Compilers like to use tricks like these automatically.

It's actually much worse than that makes it sound, because loading those pointers in a linked list is completely serial. Yes, the CPU can load two things per cycle, but it takes 4 cycles until it knows where the next node is so that it can start loading the next pointer, so realistically it can find the address of a node only once every 4th cycle. Computing the addresses of array elements has no such problem, maybe there will be a latency of 1 between the computation of successive addresses but (because actual loops cannot be faster than that anyway) that only hurts when the loop is unrolled, and if necessary the address of the element k steps ahead can be computed just by adding k*sizeof(element) (so several addresses can be computed independently, and compilers do this too when they unroll loops).

Doing a sufficient amount of work per "step" through a linked list can hide the latency problem.

like image 169
harold Avatar answered Sep 30 '22 11:09

harold