丝袜人妻一区二区三区_少妇福利无码视频_亚洲理论片在线观看_一级毛片国产A级片

當(dāng)前位置:首頁(yè) > 娛樂(lè)

【0號(hào)元素】終端卡頓優(yōu)化的全記錄

目前手機(jī)SOC的性能越來(lái)越少,很多程序員在終端程序的開(kāi)發(fā)過(guò)程中也不太注意性能方面的優(yōu)化,尤其是不注意對(duì)齊和分支優(yōu)化,但是這兩種問(wèn)題一旦出現(xiàn)所引發(fā)的問(wèn)題,是非常非常隱蔽難查的,不過(guò)好在項(xiàng)目中用到了移動(dòng)端的性能排查神器友盟U-APM工具的支持下,最終幾個(gè)問(wèn)題得到了圓滿解決。

我們先來(lái)看對(duì)齊的問(wèn)題,對(duì)齊在沒(méi)有并發(fā)競(jìng)爭(zhēng)的情況下不會(huì)有什么問(wèn)題,編譯器一般都會(huì)幫助程序員按照CPU字長(zhǎng)進(jìn)行對(duì)齊,但這在終端多線程同時(shí)工作的情況下可能會(huì)隱藏著巨大的性能問(wèn)題,在多線程并發(fā)的情況下,即使沒(méi)有共享變量,也可能會(huì)造成偽共享,由于具體的代碼涉密,因此我們來(lái)看以下抽象后的代碼。

public class Main { public static void main(String[] args) { final MyData data = new MyData(); new Thread(new Runnable() { public void run() { da(0); } }).start(); new Thread(new Runnable() { public void run() { da(0); } }).start(); try{ T(100); } catch (InterruptedException e){ e.printStackTrace(); } long[][] arr=da(); Sy("arr0 is "+arr[0]+"arr1 is"+arr[1]); } } class MyData { private long[] arr={0,0}; public long[] Getitem(){ return arr; } public void add(int j){ for (;true;){ arr[j]++; } } }

在這段代碼中,兩個(gè)子線程執(zhí)行類似任務(wù),分別操作arr數(shù)組當(dāng)中的兩個(gè)成員,由于兩個(gè)子線程的操作對(duì)象分別是arr[0]和arr[1]并不存在交叉的問(wèn)題,因此當(dāng)時(shí)判斷判斷不會(huì)造成并發(fā)競(jìng)爭(zhēng)問(wèn)題,也沒(méi)有加synchronized關(guān)鍵字。

但是這段程序卻經(jīng)常莫名的卡頓,后來(lái)經(jīng)過(guò)多方的查找,并最終通過(guò)友盟的卡頓分析功能我們最終定位到了上述代碼段,發(fā)現(xiàn)這是一個(gè)由于沒(méi)有按照緩存行進(jìn)行對(duì)齊而產(chǎn)生的問(wèn)題,這里先將修改完成后的偽代碼向大家說(shuō)明一下:

public class Main { public static void main(String[] args) { final MyData data = new MyData(); new Thread(new Runnable() { public void run() { da(0); } }).start(); new Thread(new Runnable() { public void run() { da(0); } }).start(); try{ T(10); } catch (InterruptedException e){ e.printStackTrace(); } long[][] arr=da(); Sy("arr0 is "+arr[0][0]+"arr1 is"+arr[1][0]); } } class MyData { private long[][] arr={{0,0,0,0,0,0,0,0,0},{0,0}}; public long[][] Getitem(){ return arr; } public void add(int j){ for (;true;){ arr[j][0]++; } } }

可以看到整體程序沒(méi)有作何變化,只是將原來(lái)的數(shù)組變成了二維數(shù)組,其中除了第一個(gè)數(shù)組中除arr[0][0]元素外,其余arr[0][1]-a[0][8]元素除完全不起作何與程序運(yùn)行有關(guān)的作用,但就這么一個(gè)小小的改動(dòng),卻帶來(lái)了性能有了接近20%的大幅提升,如果并發(fā)更多的話提升幅度還會(huì)更加明顯。

緩存行對(duì)齊排查分析過(guò)程

首先我們把之前代碼的多線程改為單線程串行執(zhí)行,結(jié)果發(fā)現(xiàn)效率與原始的代碼一并沒(méi)有差很多,這就讓我基本確定了這是一個(gè)由偽共享引發(fā)的問(wèn)題,但是我初始代碼中并沒(méi)有變量共享的問(wèn)題,所以這基本可以判斷是由于對(duì)齊惹的禍。

現(xiàn)代的CPU一般都不是按位進(jìn)行內(nèi)存訪問(wèn),而是按照字長(zhǎng)來(lái)訪問(wèn)內(nèi)存,當(dāng)CPU從內(nèi)存或者磁盤(pán)中將讀變量載入到寄存器時(shí),每次操作的最小單位一般是取決于CPU的字長(zhǎng)。比如8位字是1字節(jié),那么至少由內(nèi)存載入1字節(jié)也就是8位長(zhǎng)的數(shù)據(jù),再比如32位CPU每次就至少載入4字節(jié)數(shù)據(jù), 64位系統(tǒng)8字節(jié)以此類推。那么以8位機(jī)為例咱們來(lái)看一下這個(gè)問(wèn)題。假如變量1是個(gè)bool類型的變量,它占用1位空間,而變量2為byte類型占用8位空間,假如程序目前要訪問(wèn)變量2那么,第一次讀取CPU會(huì)從開(kāi)始的0x00位置讀取8位,也就是將bool型的變量1與byte型變量2的高7位全部讀入內(nèi)存,但是byte變量的最低位卻沒(méi)有被讀進(jìn)來(lái),還需要第二次的讀取才能把完整的變量2讀入。

也就是說(shuō)變量的存儲(chǔ)應(yīng)該按照CPU的字長(zhǎng)進(jìn)行對(duì)齊,當(dāng)訪問(wèn)的變量長(zhǎng)度不足CPU字長(zhǎng)的整數(shù)倍時(shí),需要對(duì)變量的長(zhǎng)度進(jìn)行補(bǔ)齊。這樣才能提升CPU與內(nèi)存間的訪問(wèn)效率,避免額外的內(nèi)存讀取操作。但在對(duì)齊方面絕大多數(shù)編譯器都做得很好,在缺省情況下,C編譯器為每一個(gè)變量或是數(shù)據(jù)單元按其自然對(duì)界條件分配空間邊界。也可以通過(guò)pragma pack(n)調(diào)用來(lái)改變?nèi)笔〉膶?duì)界條件指令,調(diào)用后C編譯器將按照pack(n)中指定的n來(lái)進(jìn)行n個(gè)字節(jié)的對(duì)齊,這其實(shí)也對(duì)應(yīng)著匯編語(yǔ)言中的.align。那么為什么還會(huì)有偽共享的對(duì)齊問(wèn)題呢?

現(xiàn)代CPU中除了按字長(zhǎng)對(duì)齊還需要按照緩存行對(duì)齊才能避免并發(fā)環(huán)境的競(jìng)爭(zhēng),目前主流ARM核移動(dòng)SOC的緩存行大小是64byte,因?yàn)槊總€(gè)CPU都配備了自己獨(dú)享的一級(jí)高速緩存,一級(jí)高速緩存基本是寄存器的速度,每次內(nèi)存訪問(wèn)CPU除了將要訪問(wèn)的內(nèi)存地址讀取之外,還會(huì)將前后處于64byte的數(shù)據(jù)一同讀取到高速緩存中,而如果兩個(gè)變量被放在了同一個(gè)緩存行,那么即使不同CPU核心在分別操作這兩個(gè)獨(dú)立變量,而在實(shí)際場(chǎng)景中CPU核心實(shí)際也是在操作同一緩存行,這也是造成這個(gè)性能問(wèn)題的原因。

Switch的坑

但是處理了這個(gè)對(duì)齊的問(wèn)題之后,我們的程序雖然在絕大多數(shù)情況下的性能都不錯(cuò),但是還是會(huì)有卡頓的情況,結(jié)果發(fā)現(xiàn)這是一個(gè)由于Switch分支引發(fā)的問(wèn)題。

switch是一種我們?cè)趈ava、c等語(yǔ)言編程時(shí)經(jīng)常用到的分支處理結(jié)構(gòu),主要的作用就是判斷變量的取值并將程序代碼送入不同的分支,這種設(shè)計(jì)在當(dāng)時(shí)的環(huán)境下非常的精妙,但是在當(dāng)前最新的移動(dòng)SOC環(huán)境下運(yùn)行,卻會(huì)帶來(lái)很多意想不到的坑。

出于涉與之前密的原因一樣,真實(shí)的代碼不能公開(kāi),我們先來(lái)看以下這段代碼:

public class Main { public static void main(String[] args) { long now=Sy(); int max=100,min=0; long a=0; long b=0; long c=0; for(int j=0;j<10000000;j++){ int ran=(in()*(max-min)+min); switch(ran){ case 0: a++; break; case 1: a++; break; default: c++; } } long diff=Sy()-now; Sy("a is "+a+"b is "+b+"c is "+c); } }

其中隨機(jī)數(shù)其實(shí)是一個(gè)rpc遠(yuǎn)程調(diào)用的返回,但是這段代碼總是莫名其妙的卡頓,為了復(fù)現(xiàn)這個(gè)卡頓,定位到這個(gè)代碼段也是通過(guò)友盟U-APM的卡頓分析找到的,想復(fù)現(xiàn)這個(gè)卡頓只需要我們?cè)偕晕裮ax范圍由調(diào)整為5。

public class Main { public static void main(String[] args) { long now=Sy(); int max=5,min=0; long a=0; long b=0; long c=0; for(int j=0;j<10000000;j++){ int ran=(in()*(max-min)+min); switch(ran){ case 0: a++; break; case 1: a++; break; default: c++; } } long diff=Sy()-now; Sy("a is "+a+"b is "+b+"c is "+c); } }

那么運(yùn)行時(shí)間就會(huì)有30%的下降,不過(guò)從我們分析的情況來(lái)看,代碼一平均每個(gè)隨機(jī)數(shù)有97%的概念要行2次判斷才能跳轉(zhuǎn)到最終的分支,總體的判斷語(yǔ)句執(zhí)行期望為2*0.97+1*0.03約等于2,而代碼二有30%的概念只需要1次判斷就可以跳轉(zhuǎn)到最終分支,總體的判斷執(zhí)行期望也就是0.3*1+0.6*2=1.5,但是代碼二卻反比代碼一還慢30%。也就是說(shuō)在代碼邏輯完全沒(méi)變只是返回值范圍的概率密度做一下調(diào)整,就會(huì)使程序的運(yùn)行效率大大下降,要解釋這個(gè)問(wèn)題要從指令流水線說(shuō)起。

指令流水線原理

我們知道CPU的每個(gè)動(dòng)作都需要用晶體震蕩而觸發(fā),以加法ADD指令為例,想完成這個(gè)執(zhí)行指令需要取指、譯碼、取操作數(shù)、執(zhí)行以及取操作結(jié)果等若干步驟,而每個(gè)步驟都需要一次晶體震蕩才能推進(jìn),因此在流水線技術(shù)出現(xiàn)之前執(zhí)行一條指令至少需要5到6次晶體震蕩周期才能完成

指令/時(shí)刻

T1

T2

T3

T4

T5

ADD

取指

譯碼

取操作數(shù)

執(zhí)行

取結(jié)果

為了縮短指令執(zhí)行的晶體震蕩周期,芯片設(shè)計(jì)人員參考了工廠流水線機(jī)制的提出了指令流水線的想法,由于取指、譯碼這些模塊其實(shí)在芯片內(nèi)部都是獨(dú)立的,完成可以在同一時(shí)刻并發(fā)執(zhí)行,那么只要將多條指令的不同步驟放在同一時(shí)刻執(zhí)行,比如指令1取指,指令2譯碼,指令3取操作數(shù)等等,就可以大幅提高CPU執(zhí)行效率:

指令/時(shí)

T1

T2

T3

T4

T5

T6

T7

T8

指令1

取指

譯碼

取操作數(shù)

執(zhí)行

取結(jié)果

指令2

取指

譯碼

取操作數(shù)

執(zhí)行

取結(jié)果

指令3

取指

譯碼

取操作數(shù)

執(zhí)行

取結(jié)果

指令4

取指

譯碼

取操作數(shù)

執(zhí)行

取結(jié)果

指令5

取指

譯碼

取操作數(shù)

執(zhí)行

指令6

取指

譯碼

取操作數(shù)

指令7

取指

譯碼

指令8

取指

以上圖流水線為例 ,在T5時(shí)刻之前指令流水線以每周期一條的速度不斷建立,在T5時(shí)代以后每個(gè)震蕩周期,都可以有一條指令取結(jié)果,平均每條指令就只需要一個(gè)震蕩周期就可以完成。這種流水線設(shè)計(jì)也就大幅提升了CPU的運(yùn)算速度。

但是CPU流水線高度依賴指指令預(yù)測(cè)技術(shù),假如在流水線上指令5本是不該執(zhí)行的,但卻在T6時(shí)刻已經(jīng)拿到指令1的結(jié)果時(shí)才發(fā)現(xiàn)這個(gè)預(yù)測(cè)失敗,那么指令5在流水線上將會(huì)化為無(wú)效的氣泡,如果指令6到8全部和指令5有強(qiáng)關(guān)聯(lián)而一并失效的話,那么整個(gè)流水線都需要重新建立。

指令/時(shí)刻

T1

T2

T3

T4

T5

T6

T7

T8

指令1

取指

譯碼

取操作數(shù)

執(zhí)行

取結(jié)果

指令2

取指

譯碼

取操作數(shù)

執(zhí)行

取結(jié)果

指令3

取指

譯碼

取操作數(shù)

執(zhí)行

取結(jié)果

指令4

取指

譯碼

取操作數(shù)

執(zhí)行

取結(jié)果

指令5

取指

譯碼

取操作數(shù)

執(zhí)行

指令6

取指

譯碼

取操作數(shù)

指令7

取指

譯碼

指令8

取指

所以可以看出例子當(dāng)中的這個(gè)效率差完全是CPU指令預(yù)測(cè)造成的,也就是說(shuō)CPU自帶的機(jī)制就是會(huì)對(duì)于執(zhí)行概比較高的分支給出更多的預(yù)測(cè)傾斜。

處理建議-用哈希表替代switch

我們上文也介紹過(guò)哈希表也就是字典,可以快速將鍵值key轉(zhuǎn)化為值value,從某種程度上講可以替換switch的作用,按照第一段代碼的邏輯,用哈希表重寫(xiě)的方案如下:

import java.u; public class Main { public static void main(String[] args) { long now=Sy(); int max=6,min=0; HashMap<Integer,Integer> hMap = new HashMap<Integer,Integer>(); (0,0); (1,0); (2,0); (3,0); (4,0); (5,0); for(int j=0;j<10000000;j++){ int ran=(in()*(max-min)+min); int value = (ran)+1; (ran,value); } long diff=Sy()-now; Sy(hMap); Sy("time is "+ diff); } }

上述這段用哈希表的代碼雖然不如代碼一速度快,但是總體非常穩(wěn)定,即使出現(xiàn)代碼二的情況也比較平穩(wěn)。

經(jīng)驗(yàn)總結(jié)

一、有并發(fā)的終端編程一定要注意按照緩存行(64byte)對(duì)齊,不按照緩存行對(duì)齊的代碼就是每增加一個(gè)線程性能會(huì)損失20%。

二、重點(diǎn)關(guān)注switch、if-else分支的問(wèn)題,一旦條件分支的取值條件有所變化,那么應(yīng)該首選用哈希表結(jié)構(gòu),對(duì)于條件分支進(jìn)行優(yōu)化。

三、選擇一款好用的性能監(jiān)測(cè)工具,如:友盟U-APM,不僅免費(fèi)且捕獲類型較為全面,推薦大家使用。

原文鏈接:

本文為阿里云原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。

1.《【0號(hào)元素】終端卡頓優(yōu)化的全記錄》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識(shí),僅代表作者本人觀點(diǎn),與本網(wǎng)站無(wú)關(guān),侵刪請(qǐng)聯(lián)系頁(yè)腳下方聯(lián)系方式。

2.《【0號(hào)元素】終端卡頓優(yōu)化的全記錄》僅供讀者參考,本網(wǎng)站未對(duì)該內(nèi)容進(jìn)行證實(shí),對(duì)其原創(chuàng)性、真實(shí)性、完整性、及時(shí)性不作任何保證。

3.文章轉(zhuǎn)載時(shí)請(qǐng)保留本站內(nèi)容來(lái)源地址,http://f99ss.com/yule/2233748.html

上一篇

【10月12日是什么星座】12星座10月12日運(yùn)勢(shì),天秤座煩惱,白羊座惹事,射手座起爭(zhēng)執(zhí)

下一篇

【2016美洲杯】梅西終圓夢(mèng)!阿根廷1:0擊敗巴西28年后再奪美洲杯冠軍

0號(hào)元素,干貨看這篇!終端卡頓優(yōu)化的全記錄

0號(hào)元素,干貨看這篇!終端卡頓優(yōu)化的全記錄

0號(hào)元素相關(guān)介紹,目前手機(jī)SOC的性能越來(lái)越少,很多程序員在終端程序的開(kāi)發(fā)過(guò)程中也不太注意性能方面的優(yōu)化,尤其是不注意對(duì)齊和分支優(yōu)化,但是這兩種問(wèn)題一旦出現(xiàn)所引發(fā)的問(wèn)題,是非常非常隱蔽難查的,不過(guò)好在項(xiàng)目中用到了移動(dòng)端...

0號(hào)元素看這里!終端卡頓優(yōu)化的全記錄

0號(hào)元素看這里!終端卡頓優(yōu)化的全記錄

0號(hào)元素相關(guān)介紹,目前手機(jī)SOC的性能越來(lái)越少,很多程序員在終端程序開(kāi)發(fā)過(guò)程中也不注重性能優(yōu)化,特別不注重排序和分支優(yōu)化,但如果出現(xiàn)這兩個(gè)問(wèn)題,就非常隱秘,很難找到,但在項(xiàng)目中使用了移動(dòng)的性能調(diào)查器ULAD U-APM...

【0號(hào)元素】專題香蕉居然有輻射!網(wǎng)友:經(jīng)常吃還有救嗎?

  • 【0號(hào)元素】專題香蕉居然有輻射!網(wǎng)友:經(jīng)常吃還有救嗎?
  • 【0號(hào)元素】專題香蕉居然有輻射!網(wǎng)友:經(jīng)常吃還有救嗎?
  • 【0號(hào)元素】專題香蕉居然有輻射!網(wǎng)友:經(jīng)常吃還有救嗎?

0號(hào)元素,干貨看這篇!什么是X射線,它是如何被發(fā)現(xiàn)的,對(duì)人類有多大影響?

  • 0號(hào)元素,干貨看這篇!什么是X射線,它是如何被發(fā)現(xiàn)的,對(duì)人類有多大影響?
  • 0號(hào)元素,干貨看這篇!什么是X射線,它是如何被發(fā)現(xiàn)的,對(duì)人類有多大影響?
  • 0號(hào)元素,干貨看這篇!什么是X射線,它是如何被發(fā)現(xiàn)的,對(duì)人類有多大影響?

0號(hào)元素,干貨看這篇!香蕉居然有輻射!網(wǎng)友:經(jīng)常吃還有救嗎?

  • 0號(hào)元素,干貨看這篇!香蕉居然有輻射!網(wǎng)友:經(jīng)常吃還有救嗎?
  • 0號(hào)元素,干貨看這篇!香蕉居然有輻射!網(wǎng)友:經(jīng)常吃還有救嗎?
  • 0號(hào)元素,干貨看這篇!香蕉居然有輻射!網(wǎng)友:經(jīng)常吃還有救嗎?

0號(hào)元素看這里!什么是X射線,它是如何被發(fā)現(xiàn)的,對(duì)人類有多大影響?

  • 0號(hào)元素看這里!什么是X射線,它是如何被發(fā)現(xiàn)的,對(duì)人類有多大影響?
  • 0號(hào)元素看這里!什么是X射線,它是如何被發(fā)現(xiàn)的,對(duì)人類有多大影響?
  • 0號(hào)元素看這里!什么是X射線,它是如何被發(fā)現(xiàn)的,對(duì)人類有多大影響?

關(guān)于0號(hào)元素我想說(shuō)香蕉居然有輻射!網(wǎng)友:經(jīng)常吃還有救嗎?

  • 關(guān)于0號(hào)元素我想說(shuō)香蕉居然有輻射!網(wǎng)友:經(jīng)常吃還有救嗎?
  • 關(guān)于0號(hào)元素我想說(shuō)香蕉居然有輻射!網(wǎng)友:經(jīng)常吃還有救嗎?
  • 關(guān)于0號(hào)元素我想說(shuō)香蕉居然有輻射!網(wǎng)友:經(jīng)常吃還有救嗎?

0號(hào)元素專題之什么是X射線,它是如何被發(fā)現(xiàn)的,對(duì)人類有多大影響?

  • 0號(hào)元素專題之什么是X射線,它是如何被發(fā)現(xiàn)的,對(duì)人類有多大影響?
  • 0號(hào)元素專題之什么是X射線,它是如何被發(fā)現(xiàn)的,對(duì)人類有多大影響?
  • 0號(hào)元素專題之什么是X射線,它是如何被發(fā)現(xiàn)的,對(duì)人類有多大影響?