ArrayList与LinkedList
我必須承認(rèn)這篇文章的標(biāo)題有點(diǎn)吸引人。 我最近閱讀了此博客文章 ,這是有關(guān)此主題的討論和辯論的一個很好的摘要。
但是這次,我想嘗試一種不同的方法來比較這兩個眾所周知的數(shù)據(jù)結(jié)構(gòu):使用硬件性能計(jì)數(shù)器 。
我不會進(jìn)行微基準(zhǔn)測試,也不能直接進(jìn)行。 我不會使用System.nanoTime()計(jì)時,而是使用HPC(例如高速緩存命中/未命中)。
無需介紹這些數(shù)據(jù)結(jié)構(gòu),每個人都知道它們的用途以及實(shí)現(xiàn)方式。 我將研究重點(diǎn)放在列表迭代上,因?yàn)槌颂砑釉刂?#xff0c;這是列表最常見的任務(wù)。 同時也因?yàn)榱斜淼膬?nèi)存訪問模式是CPU緩存交互的一個很好的例子。
這是我的用于測量LinkedList和ArrayList的列表迭代的代碼:
import java.util.ArrayList; import java.util.LinkedList; import java.util.List;import ch.usi.overseer.OverHpc;public class ListIteration {private static List<String> arrayList = new ArrayList<>();private static List<String> linkedList = new LinkedList<>();public static void initializeList(List<String> list, int bufferSize){for (int i = 0; i < 50000; i++){byte[] buffer = null;if (bufferSize > 0){buffer = new byte[bufferSize];}String s = String.valueOf(i);list.add(s);// avoid buffer to be optimized awayif (System.currentTimeMillis() == 0){System.out.println(buffer);}}}public static void bench(List<String> list){if (list.contains("bar")){System.out.println("bar found");}}public static void main(String[] args) throws Exception{if (args.length != 2) return;List<String> benchList = "array".equals(args[0]) ? arrayList : linkedList;int bufferSize = Integer.parseInt(args[1]);initializeList(benchList, bufferSize);HWCounters.init();System.out.println("init done");// warmupfor (int i = 0; i < 10000; i++){bench(benchList);}Thread.sleep(1000);System.out.println("warmup done");HWCounters.start();for (int i = 0; i < 1000; i++){bench(benchList);}HWCounters.stop();HWCounters.printResults();HWCounters.shutdown();} }為了進(jìn)行測量,我使用基于監(jiān)督程序庫的名為HWCounters的類來獲取硬件性能計(jì)數(shù)器。 您可以在這里找到此類的代碼。
該程序采用2個參數(shù):第一個參數(shù)用于ArrayList實(shí)現(xiàn)或LinkedList之間的選擇,第二個參數(shù)用于initializeList方法中使用的緩沖區(qū)大小。 此方法使用50K字符串填充列表實(shí)現(xiàn)。 每個字符串都是剛創(chuàng)建的,即將添加到列表中。 我們也可以根據(jù)程序的第二個參數(shù)分配一個緩沖區(qū)。 如果為0,則不分配緩沖區(qū)。
bench方法執(zhí)行對列表中未包含的常量字符串的搜索,因此我們完全遍歷了列表。
最后, main方法是執(zhí)行列表的初始化,對基準(zhǔn)方法進(jìn)行預(yù)熱并測量該方法的1000次運(yùn)行。 然后,我們從HPC打印結(jié)果。
讓我們在不帶2 Xeon X5680的Linux上不分配緩沖區(qū)的情況下運(yùn)行程序:
[root@archi-srv]# java -cp .:overseer.jar com.ullink.perf.myths.ListIteration?array 0 init done warmup done Cycles: 428,711,720 Instructions: 776,215,597 L2 hits: 5,302,792 L2 misses: 23,702,079 LLC hits: 42,933,789 LLC misses: 73 CPU migrations: 0 Local DRAM: 0 Remote DRAM: 0[root@archi-srv]# /java -cp .:overseer.jar com.ullink.perf.myths.ListIteration?linked 0 init done warmup done Cycles: 767,019,336 Instructions: 874,081,196 L2 hits: 61,489,499 L2 misses: 2,499,227 LLC hits: 3,788,468 LLC misses: 0 CPU migrations: 0 Local DRAM: 0 Remote DRAM: 0第一次運(yùn)行是在ArrayList實(shí)現(xiàn)上,第二次是使用LinkedList。
- 周期數(shù)是執(zhí)行代碼所花費(fèi)的CPU周期數(shù)。 顯然,LinkedList比ArrayList花費(fèi)了更多的周期。
- LinkedList的說明要高一些。 但這在這里并不重要。
- 對于L2緩存訪問,我們有一個明顯的區(qū)別:與LinkedList相比,ArrayList的L2未命中率要高得多。
- 從機(jī)械上講,LLC命中對ArrayList非常重要。
進(jìn)行此比較的結(jié)論是,列表迭代期間訪問的大多數(shù)數(shù)據(jù)位于LinkedList的L2中,但位于ArrayList的L3中。
我對此的解釋是,添加到列表中的字符串是在之前創(chuàng)建的。 對于LinkedList,這意味著它是本地的在添加元素時創(chuàng)建的Node條目。 我們在節(jié)點(diǎn)上有更多位置。
但是,讓我們使用為每個新添加的String分配的中間緩沖區(qū)重新運(yùn)行比較。
[root@archi-srv]# java -cp .:overseer.jar com.ullink.perf.myths.ListIteration array 256 init done warmup done Cycles: 584,965,201 Instructions: 774,373,285 L2 hits: 952,193 L2 misses: 62,840,804 LLC hits: 63,126,049 LLC misses: 4,416 CPU migrations: 0 Local DRAM: 824 Remote DRAM: 0[root@archi-srv]# java -cp .:overseer.jar com.ullink.perf.myths.ListIteration linked 256 init done warmup done Cycles: 5,289,317,879 Instructions: 874,350,022 L2 hits: 1,487,037 L2 misses: 75,500,984 LLC hits: 81,881,688 LLC misses: 5,826,435 CPU migrations: 0 Local DRAM: 1,645,436 Remote DRAM: 1,042這里的結(jié)果有很大的不同:
- 循環(huán)的重要性提高了10倍。
- 說明與以前相同
- 對于緩存訪問,ArrayList具有比先前運(yùn)行更多的L2未命中/ LLC命中,但仍處于相同的數(shù)量級順序。 相反,LinkedList具有更多的L2未命中/ LLC命中,但此外,還有相當(dāng)數(shù)量的LLC未命中/ DRAM訪問。 區(qū)別就在這里。
使用中間緩沖區(qū),我們可以推開條目和字符串,這會產(chǎn)生更多的高速緩存未命中,并且最終還會訪問DRAM,這比訪問高速緩存要慢得多。
ArrayList在這里更可預(yù)測,因?yàn)槲覀儽舜酥g保持元素的局部性。
此處的內(nèi)存訪問模式對于列表迭代性能至關(guān)重要。 ArrayList比LinkedList更穩(wěn)定,因?yàn)樵诿總€元素添加之間進(jìn)行任何操作,都可以使數(shù)據(jù)保持比LinkedList更本地。
還要記住,對數(shù)組進(jìn)行迭代對于CPU而言效率要高得多,因?yàn)樗梢杂|發(fā)硬件預(yù)取,因?yàn)樵L問模式是非常可預(yù)測的。
翻譯自: https://www.javacodegeeks.com/2013/12/arraylist-vs-linkedlist.html
總結(jié)
以上是生活随笔為你收集整理的ArrayList与LinkedList的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: cvvhdf参数设置(如何设置cv参数)
- 下一篇: 古墓丽影崛起流畅设置(古墓丽影崛起最低配