如何寫(xiě)出高性能代碼(二)巧用數(shù)據(jù)特性

導(dǎo)語(yǔ)

同一份邏輯,不同人的實(shí)現(xiàn)的代碼性能會(huì)出現(xiàn)數(shù)量級(jí)的差異; 同一份代碼,你可能微調(diào)幾個(gè)字符或者某行代碼的順序,就會(huì)有數(shù)倍的性能提升;同一份代碼,也可能在不同處理器上運(yùn)行也會(huì)有幾倍的性能差異;十倍程序員 不是只存在于傳說(shuō)中,可能在我們的周圍也比比皆是。十倍體現(xiàn)在程序員的方法面面,而代碼性能卻是其中最直觀的一面。

本文是《如何寫(xiě)出高性能代碼》系列的第二篇,本文將告訴你如何利用數(shù)據(jù)的幾個(gè)特性以達(dá)到提升代碼性能的目的。

可復(fù)用性

我們?cè)诖a中所用到的大部分?jǐn)?shù)據(jù),都是可以被重復(fù)使用的,這種能被重復(fù)使用的數(shù)據(jù)就不要去反復(fù)去獲取或者初始化了,舉個(gè)例子:

在這里插入圖片描述

  上圖中在for循環(huán)中調(diào)用了getSomeThing()函數(shù),而這個(gè)函數(shù)實(shí)際和循環(huán)無(wú)關(guān),它是可以放在循環(huán)外,其結(jié)果也是可以復(fù)用的,上面代碼放在循環(huán)內(nèi)白白多調(diào)用了99次,這里如果getSomeThing()是個(gè)非常耗時(shí)或者耗CPU的函數(shù),性能將會(huì)查近百倍。
在這里插入圖片描述

  在Java代碼中,我們很常用的枚舉類,大部分的枚舉類可能經(jīng)常有獲取所有枚舉信息的接口,大部分人可能寫(xiě)出來(lái)的代碼像上面的getList()這樣。然而這種寫(xiě)法雖然功能上沒(méi)啥問(wèn)題,但每調(diào)用一次就會(huì)生成一個(gè)新的List,如果調(diào)用頻次很高就會(huì)對(duì)性能產(chǎn)生顯著的影響。正確的做法應(yīng)該靜態(tài)初始化生成一個(gè)不可變的list,之后直接復(fù)用就行。

溫馨提示:這里我特意標(biāo)注了一個(gè)不可變,在對(duì)象復(fù)用的情況下需要額外關(guān)注下是否有地方會(huì)改變對(duì)象內(nèi)容,如果對(duì)象需要被改變就不能復(fù)用了,可以deepcopy之后再更改。當(dāng)然如果這個(gè)對(duì)象生來(lái)就是會(huì)被改變的,就沒(méi)必要復(fù)用了。

非必要性

非必要性的意思是有些數(shù)據(jù)可能沒(méi)必要去做初始化。舉個(gè)簡(jiǎn)單的例子:

在這里插入圖片描述

  在上面代碼中sth對(duì)象被獲取后,才校驗(yàn)了參數(shù)的合法性,事實(shí)上如果參數(shù)是不合法的,sth就沒(méi)必要初始化了,這里sth就具備了非必要性。類似上面這種代碼其實(shí)很常見(jiàn),我在我們公司代碼庫(kù)中就遇到了很多次,基本的模式都是先獲取了某些數(shù)據(jù),但在之后有些過(guò)濾或者檢查的邏輯導(dǎo)致代碼跳出,然后這些數(shù)據(jù)就完全沒(méi)有用上。
  應(yīng)對(duì)非必要性的一個(gè)解決方案就是延遲初始化,有些地方也叫 懶加載 或者 惰性加載,像上面代碼中只需要把getSomeThing()移動(dòng)到參數(shù)校驗(yàn)的后面,就可以避免這個(gè)性能問(wèn)題了。像Java中我們?cè)谟玫腸heckstyle插件,就提供了一個(gè)VariableDeclarationUsageDistance 的規(guī)則,這個(gè)規(guī)則的作用強(qiáng)制讓代碼的聲明和使用不會(huì)間隔太多行,從而避免出現(xiàn)上述這種聲明但未使用導(dǎo)致的性能問(wèn)題。
  事實(shí)上,延遲初始化是一個(gè)非常常用的機(jī)制,比如著名的copy on write其實(shí)就是延遲初始化的典范。另外像Jdk中很多集合基本也都是延遲初始化的,就拿HashMap為例,你在執(zhí)行new HashMap()時(shí),只是創(chuàng)建了一個(gè)空殼對(duì)象,只有第一次調(diào)用put()方法時(shí)整個(gè)map才會(huì)初始化。

// new HashMap()只是初始化出來(lái)一個(gè)空殼hashmap
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                       initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                       loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
    
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    // 第一次put觸發(fā)內(nèi)部真正的初始化
    if ((tab = table) == null || (n = tab.length) == 0) 
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
       // 省略其它代碼
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

局部性

在這里插入圖片描述

  局部性也是老生常談的特性了,局部性有好多種,數(shù)據(jù)局部性、空間局部性、時(shí)間局部性……可以說(shuō)就是因?yàn)榫植啃缘拇嬖?,世界才能更高效地運(yùn)行。更多關(guān)于局部性的內(nèi)容,可以參考下我之前寫(xiě)的一篇文章局部性原理——各類優(yōu)化的基石。
  這里先說(shuō)下數(shù)據(jù)局部性,在大多數(shù)情況下,只有少量的數(shù)據(jù)是會(huì)被頻繁訪問(wèn)的,俗稱熱點(diǎn)數(shù)據(jù)。處理熱點(diǎn)數(shù)據(jù)最簡(jiǎn)單的方法就是給它加緩存加分片,具體方案就得看具體問(wèn)題了。我來(lái)舉個(gè)在互聯(lián)網(wǎng)公司很常見(jiàn)的例子,很多業(yè)務(wù)數(shù)據(jù)都是存在數(shù)據(jù)庫(kù)中,然而數(shù)據(jù)庫(kù)在面對(duì)超大量的請(qǐng)求就有點(diǎn)力不從心了,因?yàn)榫植啃缘拇嬖冢挥猩倭康臄?shù)據(jù)是被頻繁訪問(wèn)的,我們可以將這部分?jǐn)?shù)據(jù)緩存在Redis中,從而減少對(duì)數(shù)據(jù)庫(kù)的壓力。
  另外說(shuō)個(gè)大家比較容易忽略的一點(diǎn),代碼局部性。系統(tǒng)中只有少量的代碼是被反復(fù)執(zhí)行的,而且如果系統(tǒng)有性能問(wèn)題,也是少量的代碼導(dǎo)致的,所以只要找出并優(yōu)化好這部分代碼,系統(tǒng)性能就能顯著提升。依賴一些性能分析工具,比如用arthas火焰圖就能很容易找到這部分代碼(其他工具會(huì)在本系列第五篇文章中介紹)。

多讀少寫(xiě)

除了局部性外,數(shù)據(jù)還有另外一個(gè)非常顯著的特性,就是多讀少寫(xiě)。這個(gè)也很符合大家的直覺(jué)和習(xí)慣,比如大部分人都是看文章而不是寫(xiě)文章,你到如何網(wǎng)站上也都是看的多,改的少,這是一條幾乎放之四海而皆準(zhǔn)的規(guī)律。 那這個(gè)特性對(duì)我們寫(xiě)代碼有什么意義? 這個(gè)特性意味著大概率你的代碼局部性就產(chǎn)生在讀數(shù)據(jù)的代碼上,額外關(guān)注下這部分代碼。
  當(dāng)然也不是說(shuō)寫(xiě)數(shù)據(jù)不重要,這里就不得不說(shuō)到多讀少寫(xiě)的另外一個(gè)特點(diǎn)了,那就是寫(xiě)的成本遠(yuǎn)高于讀的成本,而且寫(xiě)的重要性也遠(yuǎn)高于讀的重要性。 重要性不言而喻,去銀行只是看不到余額可以接受,但取不了錢(qián)那肯定就是不行了。 那為什么寫(xiě)數(shù)據(jù)的成本會(huì)遠(yuǎn)高于讀數(shù)據(jù)的成本呢? 簡(jiǎn)單可以這么理解,由于數(shù)據(jù)局部性的加持,很多讀都可以通過(guò)各種手段來(lái)優(yōu)化,而寫(xiě)就不大行,而且寫(xiě)可能會(huì)產(chǎn)生很多額外的副作用,需要添加很多校驗(yàn)之類的邏輯避免不必要的副作用。

以上就是本文的全部?jī)?nèi)容,希望大家有所收獲。

如何寫(xiě)出高性能代碼系列文章

本文來(lái)自https://blog.csdn.net/xindoo

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容