以前很長一段時間,潛心修煉彙編,專門裝了一個dos7,慢慢玩到win32彙編,再到linux的AT&A彙編,嘗試寫mbr的時候期間好幾回把centos弄的開不了機,又莫名其妙的修好了,現在最大的感觸就是:球莫名堂,還不如寫JAVA。linux
對於比較高層的語言來講,都不會太在乎底層是如何運做的,這是個好事,也是個壞事,好事是不用關心底層的繁瑣的事情,只需聚焦到業務實現,壞處就是出現比較嚴重的問題難以排錯,很容易出現看起來很漂亮但就是性能很渣的代碼。程序員
有以下兩段代碼:編程
for (int i = 0; i < longs.length; i++) { for (int j = 0; j < longs[i].length; j++) { Long k = longs[i][j]; } }
for (int i = 0; i < longs.length; i++) { for (int j = 0; j < longs[i].length; j++) { Long k1 = longs[j][i]; } }
看起來長的同樣是否是?兩段代碼看起來都沒啥問題是吧,相信不少人都或多或少的擼過這樣的兩段代碼,可是這兩段代碼的運行效率比較是:centos
第二段代碼執行效率比第一段代碼低300倍數組
完整的測試代碼:緩存
public class RepeatIterator { private static final int ARRAY_SIZE = 10240; private Long[][] longs = new Long[ARRAY_SIZE][ARRAY_SIZE]; public static void main(String[] args) { new RepeatIterator().iteratorByRow(); new RepeatIterator().iteratorByColumn(); } private void iteratorByRow() {long start = System.currentTimeMillis(); for (int i = 0; i < longs.length; i++) { for (int j = 0; j < longs[i].length; j++) { Long k = longs[i][j]; } } System.out.println("iterator by row:" + (System.currentTimeMillis() - start)); } private void iteratorByColumn() {long start = System.currentTimeMillis(); for (int i = 0; i < longs.length; i++) { for (int j = 0; j < longs[i].length; j++) { Long k1 = longs[j][i]; } } System.out.println("iterator by column:" + (System.currentTimeMillis() - start)); } }
執行結果:服務器
iterator by row:6 iterator by column:1737 Process finished with exit code 0
代碼爲什麼執行緩慢,機器爲什麼頻繁卡死,服務器爲什麼屢屢宕機,看似美麗的代碼背後又隱藏着什麼,這一切的背後,是程序員人性的扭曲仍是道德的淪喪,是碼農憤怒的爆發仍是飢渴的無奈,讓咱們跟隨鏡頭走進計算機的心裏世界,解刨那一段小巧的for循環。dom
當咱們擼了以下一行代碼的時候:性能
private static final int ARRAY_SIZE = 10240; private Long[][] longs = new Long[ARRAY_SIZE][ARRAY_SIZE];
在計算機的內存裏面是以下分佈(至少在個人計算機裏面是這樣分佈的):測試
能夠明確的看到在內存中的數組大小爲10240,也就是咱們定義的大小,以及他的的地址(這並非實際的物理地址,8086裏面是段的偏移地址,i386裏面是分頁地址),可是當遍歷該數組的時候,並非直接從內存地址中取出這些數據,由於內存對於cpu來講:太慢了。爲了充分利用cpu的效率,因而人們設計出了cpu緩存,目前已經存在三級cpu緩存,而不一樣的緩存意義並不同,特別是寫多核編程的時候,若是對cpu緩存的理解不到位,很容易死在僞共享裏面。
一個具備三級緩存的圖示以下:
其中1級緩存並非一塊緩存,而是2個部分,分別爲代碼緩存和數據緩存,1級和2級緩存爲單個cpu獨享,其餘cpu不能修改到裏面的數據,而3級緩存,則爲多個cpu共享,而cpu僞共享,也是發生在這個位置,程序定義的數據,大多時候緩存在3級緩存,緩存也是行導向存儲,經過以下方式能夠查看一行緩存可以存儲多少數據:
cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
64
64表明64個字節,一個Long對象的長度是8個字節,那麼64個字節能夠緩存8個Long,數組在內存中是一片連續的地址空間(物理也許不必定,但邏輯地址必定連續),這就意味着若是定義個一個8個長度的Long數組,當訪問第一個數組元素被添加到緩存的時候,那麼其餘7個順帶的0消耗的就加載到了緩存中,這時候若是訪問數組,那麼速度是最高效的。也就是意味着,要充分利用緩存的特性,數據已定要按照行訪問,不然會形成cache miss,這時候會從內存中獲取數據,而且計算是否須要將其緩存,會極大的下降速度。
在上面的例子中,定義的二維數組,當使用第一種方式訪問的時候,會發生以下狀況:
1.訪問第一行第一個元素,若是緩存中不存在(cache miss),從內存中獲取,而且將其相鄰的元素同時緩存。
2.訪問第一行第二個元素,直接緩存取出(cache命中)
舉個例子:
public class CacheLoad { private static final int ARRAY_SIZE = 10240; private Long[][] longs = null; public static void main(String[] args) { new CacheLoad().iterator(); new CacheLoad().iterator(); } private void iterator() { if (longs == null) { longs = new Long[ARRAY_SIZE][ARRAY_SIZE]; for (int i = 0; i < longs.length; i++) { for (int j = 0; j < longs[i].length; j++) { longs[i][j] = new Random().nextLong(); } } } long start = System.currentTimeMillis(); for (int i = 0; i < longs.length; i++) { for (int j = 0; j < longs[i].length; j++) { Long k = longs[i][j]; } } System.out.println("iterator:" + (System.currentTimeMillis() - start)); } }
iterator:5 iterator:1 Process finished with exit code 0
第二次的查詢速度理論(實際可能會大於,由於cpu線程切換,訪問過程當中可能被系統其餘資源搶佔cpu)是小於等於第一次,由於會減小將第一個元素緩存的時間,另外並非所有的數據都會盡緩存,這不是程序所能控制。
當咱們採起第二種方式訪問的時候,會發生以下狀況:
1.訪問第一行第一個元素,若是緩存中不存在(cache miss),從內存中獲取,而且將其相鄰的元素同時緩存。
2.訪問第二行第一個元素,若是緩存中不存在(cache miss),從內存中獲取,而且將其相鄰的元素同時緩存。
。。。。。。。
由此能夠看到,採用第二種方式訪問數組的時候,很大的機率會形成cache miss,第二條cache沖掉第一條cache,極端狀況是每次都miss,而且不管執行多少次,始終會miss,例如:
public class CacheLoad { private static final int ARRAY_SIZE = 10240; private Long[][] longs = new Long[ARRAY_SIZE][ARRAY_SIZE];; public static void main(String[] args) { new CacheLoad().iterator(); new CacheLoad().iterator(); new CacheLoad().iterator(); new CacheLoad().iterator(); } private void iterator() { long start = System.currentTimeMillis(); for (int i = 0; i < longs.length; i++) { for (int j = 0; j < longs[i].length; j++) { Long k = longs[j][i]; } } System.out.println("iterator:" + (System.currentTimeMillis() - start)); } }
iterator:1658 iterator:1697 iterator:1915 iterator:1728 Process finished with exit code 0
能夠看到不管執行多少次,速度並不會所以變快,能夠看見幾本cache 所有失效,由此帶來的性能是極低的。
擼代碼的時候,且擼且當心。。。