一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

系統(tǒng)性能優(yōu)化的十大策略

jf_ro2CN3Fa ? 來(lái)源:芋道源碼 ? 作者:芋道源碼 ? 2022-12-14 15:08 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群


最近看到一個(gè)關(guān)于性能優(yōu)化的不錯(cuò)的文章。作者寫了上中下三篇,由淺入深的寫了關(guān)于性能優(yōu)化的方方面面,并不僅僅局限于代碼層面。

我看了之后還是很有收獲的,同時(shí)也驚嘆于作者扎實(shí)的技術(shù)能力與思考能力。于是借花獻(xiàn)佛,把作者的三篇整理合并之后分享給大家。希望你也能有所收獲。

上篇

引言:取與舍

軟件設(shè)計(jì)開(kāi)發(fā)某種意義上是“取”與“舍”的藝術(shù)。

關(guān)于性能方面,就像建筑設(shè)計(jì)成抗震9度需要額外的成本一樣,高性能軟件系統(tǒng)也意味著更高的實(shí)現(xiàn)成本,有時(shí)候與其他質(zhì)量屬性甚至?xí)_突,比如安全性、可擴(kuò)展性、可觀測(cè)性等等。

大部分時(shí)候我們需要的是:在業(yè)務(wù)遇到瓶頸之前,利用常見(jiàn)的技術(shù)手段將系統(tǒng)優(yōu)化到預(yù)期水平。

那么,性能優(yōu)化有哪些技術(shù)方向和手段呢?

性能優(yōu)化通常是“時(shí)間”與“空間”的互換與取舍。

本篇分兩個(gè)部分,在上篇,講解六種通用的“時(shí)間”與“空間”互換取舍的手段:

  • 索引術(shù)
  • 壓縮術(shù)
  • 緩存術(shù)
  • 預(yù)取術(shù)
  • 削峰填谷術(shù)
  • 批量處理術(shù)

在下篇,介紹四種進(jìn)階性的內(nèi)容,大多與提升并行能力有關(guān)

  • 八門遁甲 —— 榨干計(jì)算資源
  • 影分身術(shù) —— 水平擴(kuò)容
  • 奧義 —— 分片術(shù)
  • 秘術(shù) —— 無(wú)鎖術(shù)

每種性能優(yōu)化的技術(shù)手段,我都找了一張應(yīng)景的《火影忍者》中人物或忍術(shù)的配圖 ,評(píng)論區(qū)答出任意人物或忍術(shù)送一顆小星星。

(注:所有配圖來(lái)自動(dòng)漫《火影忍者》,部分圖片添加了文字方便理解,僅作技術(shù)交流用途)

索引術(shù)

5c83c066-7b61-11ed-8abf-dac502259ad0.png

10ms之后。

5ce3aec2-7b61-11ed-8abf-dac502259ad0.png

索引的原理是拿額外的存儲(chǔ)空間換取查詢時(shí)間,增加了寫入數(shù)據(jù)的開(kāi)銷,但使讀取數(shù)據(jù)的時(shí)間復(fù)雜度一般從O(n)降低到O(logn)甚至O(1)。

索引不僅在數(shù)據(jù)庫(kù)中廣泛使用,前后端的開(kāi)發(fā)中也在不知不覺(jué)運(yùn)用。

在數(shù)據(jù)集比較大時(shí),不用索引就像從一本沒(méi)有目錄而且內(nèi)容亂序的新華字典查一個(gè)字,得一頁(yè)一頁(yè)全翻一遍才能找到;

用索引之后,就像用拼音先在目錄中先找到要查到字在哪一頁(yè),直接翻過(guò)去就行了。

書籍的目錄是典型的樹(shù)狀結(jié)構(gòu),那么軟件世界常見(jiàn)的索引有哪些數(shù)據(jù)結(jié)構(gòu),分別在什么場(chǎng)景使用呢?

  • 哈希表 (Hash Table):哈希表的原理可以類比銀行辦業(yè)務(wù)取號(hào),給每個(gè)人一個(gè)號(hào)(計(jì)算出的Hash值),叫某個(gè)號(hào)直接對(duì)應(yīng)了某個(gè)人,索引效率是最高的O(1),消耗的存儲(chǔ)空間也相對(duì)更大。K-V存儲(chǔ)組件以及各種編程語(yǔ)言提供的Map/Dict等數(shù)據(jù)結(jié)構(gòu),多數(shù)底層實(shí)現(xiàn)是用的哈希表。
  • 二叉搜索樹(shù) (Binary Search Tree):有序存儲(chǔ)的二叉樹(shù)結(jié)構(gòu),在編程語(yǔ)言中廣泛使用的紅黑樹(shù)屬于二叉搜索樹(shù),確切的說(shuō)是“不完全平衡的”二叉搜索樹(shù)。從C++、Java的TreeSet、TreeMap,到LinuxCPU調(diào)度,都能看到紅黑樹(shù)的影子。Java的HashMap在發(fā)現(xiàn)某個(gè)Hash槽的鏈表長(zhǎng)度大于8時(shí)也會(huì)將鏈表升級(jí)為紅黑樹(shù),而相比于紅黑樹(shù)“更加平衡”的AVL樹(shù)反而實(shí)際用的更少。
  • 平衡多路搜索樹(shù) (B-Tree):這里的B指的是Balance而不是Binary,二叉樹(shù)在大量數(shù)據(jù)場(chǎng)景會(huì)導(dǎo)致查找深度很深,解決辦法就是變成多叉樹(shù),MongoDB的索引用的就是B-Tree。
  • 葉節(jié)點(diǎn)相連的平衡多路搜索樹(shù) (B+ Tree):B+ Tree是B-Tree的變體,只有葉子節(jié)點(diǎn)存數(shù)據(jù),葉子與相鄰葉子相連,MySQL的索引用的就是B+樹(shù),Linux的一些文件系統(tǒng)也使用的B+樹(shù)索引inode。其實(shí)B+樹(shù)還有一種在枝椏上再加鏈表的變體:B*樹(shù),暫時(shí)沒(méi)想到實(shí)際應(yīng)用。
  • 日志結(jié)構(gòu)合并樹(shù) (LSM Tree):Log Structured Merge Tree,簡(jiǎn)單理解就是像日志一樣順序?qū)懴氯?,多層多塊的結(jié)構(gòu),上層寫滿壓縮合并到下層。LSM Tree其實(shí)本身是為了優(yōu)化寫性能犧牲讀性能的數(shù)據(jù)結(jié)構(gòu),并不能算是索引,但在大數(shù)據(jù)存儲(chǔ)和一些NoSQL數(shù)據(jù)庫(kù)中用的很廣泛,因此這里也列進(jìn)去了。
  • 字典樹(shù) (Trie Tree):又叫前綴樹(shù),從樹(shù)根串到樹(shù)葉就是數(shù)據(jù)本身,因此樹(shù)根到枝椏就是前綴,枝椏下面的所有數(shù)據(jù)都是匹配該前綴的。這種結(jié)構(gòu)能非常方便的做前綴查找或詞頻統(tǒng)計(jì),典型的應(yīng)用有:自動(dòng)補(bǔ)全、URL路由。其變體基數(shù)樹(shù)(Radix Tree)在Nginx的Geo模塊處理子網(wǎng)掩碼前綴用了;Redis的Stream、Cluster等功能的實(shí)現(xiàn)也用到了基數(shù)樹(shù)(Redis中叫Rax)。
  • 跳表 (Skip List):是一種多層結(jié)構(gòu)的有序鏈表,插入一個(gè)值時(shí)有一定概率“晉升”到上層形成間接的索引。跳表更適合大量并發(fā)寫的場(chǎng)景,不存在紅黑樹(shù)的再平衡問(wèn)題,Redis強(qiáng)大的ZSet底層數(shù)據(jù)結(jié)構(gòu)就是哈希加跳表。
  • 倒排索引 (Inverted index):這樣翻譯不太直觀,可以叫“關(guān)鍵詞索引”,比如書籍末頁(yè)列出的術(shù)語(yǔ)表就是倒排索引,標(biāo)識(shí)出了每個(gè)術(shù)語(yǔ)出現(xiàn)在哪些頁(yè),這樣我們要查某個(gè)術(shù)語(yǔ)在哪用的,從術(shù)語(yǔ)表一查,翻到所在的頁(yè)數(shù)即可。倒排索引在全文索引存儲(chǔ)中經(jīng)常用到,比如ElasticSearch非常核心的機(jī)制就是倒排索引;Prometheus的時(shí)序數(shù)據(jù)庫(kù)按標(biāo)簽查詢也是在用倒排索引。

數(shù)據(jù)庫(kù)主鍵之爭(zhēng) :自增長(zhǎng) vs UUID。主鍵是很多數(shù)據(jù)庫(kù)非常重要的索引,尤其是MySQL這樣的RDBMS會(huì)經(jīng)常面臨這個(gè)難題:是用自增長(zhǎng)的ID還是隨機(jī)的UUID做主鍵?

自增長(zhǎng)ID的性能最高,但不好做分庫(kù)分表后的全局唯一ID,自增長(zhǎng)的規(guī)律可能泄露業(yè)務(wù)信息;而UUID不具有可讀性且太占存儲(chǔ)空間。

爭(zhēng)執(zhí)的結(jié)果就是找一個(gè)兼具二者的優(yōu)點(diǎn)的折衷方案:

用雪花算法生成分布式環(huán)境全局唯一的ID作為業(yè)務(wù)表主鍵,性能尚可、不那么占存儲(chǔ)、又能保證全局單調(diào)遞增,但引入了額外的復(fù)雜性,再次體現(xiàn)了取舍之道。

再回到數(shù)據(jù)庫(kù)中的索引,建索引要注意哪些點(diǎn)呢?

  • 定義好主鍵并盡量使用主鍵,多數(shù)數(shù)據(jù)庫(kù)中,主鍵是效率最高的聚簇索引;
  • 在Where或Group By、Order By、Join On條件中用到的字段也要按需建索引或聯(lián)合索引,MySQL中搭配explain命令可以查詢DML是否利用了索引;
  • 類似枚舉值這樣重復(fù)度太高的字段不適合建索引(如果有位圖索引可以建),頻繁更新的列不太適合建索引;
  • 單列索引可以根據(jù)實(shí)際查詢的字段升級(jí)為聯(lián)合索引,通過(guò)部分冗余達(dá)到索引覆蓋,以避免回表的開(kāi)銷;
  • 盡量減少索引冗余,比如建A、B、C三個(gè)字段的聯(lián)合索引,Where條件查詢A、A and B、A and B and C
  • 都可以利用該聯(lián)合索引,就無(wú)需再給A單獨(dú)建索引了;根據(jù)數(shù)據(jù)庫(kù)特有的索引特性選擇適合的方案,比如像MongoDB,還可以建自動(dòng)刪除數(shù)據(jù)的TTL索引、不索引空值的稀疏索引、地理位置信息的Geo索引等等。

數(shù)據(jù)庫(kù)之外,在代碼中也能應(yīng)用索引的思維,比如對(duì)于集合中大量數(shù)據(jù)的查找,使用Set、Map、Tree這樣的數(shù)據(jù)結(jié)構(gòu),其實(shí)也是在用哈希索引或樹(shù)狀索引,比直接遍歷列表或數(shù)組查找的性能高很多。

緩存術(shù)

5d2b7342-7b61-11ed-8abf-dac502259ad0.png

緩存優(yōu)化性能的原理和索引一樣,是拿額外的存儲(chǔ)空間換取查詢時(shí)間。緩存無(wú)處不在,設(shè)想一下我們?cè)跒g覽器打開(kāi)這篇文章,會(huì)有多少層緩存呢?

  • 首先解析DNS時(shí),瀏覽器一層DNS緩存、操作系統(tǒng)一層DNS緩存、DNS服務(wù)器鏈上層層緩存;
  • 發(fā)送一個(gè)GET請(qǐng)求這篇文章,服務(wù)端很可能早已將其緩存在KV存儲(chǔ)組件中了;
  • 即使沒(méi)有擊中緩存,數(shù)據(jù)庫(kù)服務(wù)器內(nèi)存中也緩存了最近查詢的數(shù)據(jù);
  • 即使沒(méi)有擊中數(shù)據(jù)庫(kù)服務(wù)器的緩存,數(shù)據(jù)庫(kù)從索引文件中讀取,操作系統(tǒng)已經(jīng)把熱點(diǎn)文件的內(nèi)容放置在Page Cache中了;
  • 即使沒(méi)有擊中操作系統(tǒng)的文件緩存,直接讀取文件,大部分固態(tài)硬盤或者磁盤本身也自帶緩存;
  • 數(shù)據(jù)取到之后服務(wù)器用模板引擎渲染出HTML,模板引擎早已解析好緩存在服務(wù)端內(nèi)存中了;
  • 歷經(jīng)數(shù)十毫秒之后,終于服務(wù)器返回了一個(gè)渲染后的HTML,瀏覽器端解析DOM樹(shù),發(fā)送請(qǐng)求來(lái)加載靜態(tài)資源;
  • 需要加載的靜態(tài)資源可能因Cache-Control在瀏覽器本地磁盤和內(nèi)存中已經(jīng)緩存了;
  • 即使本地緩存到期,也可能因Etag沒(méi)變服務(wù)器告訴瀏覽器304 Not Modified繼續(xù)緩存;
  • 即使Etag變了,靜態(tài)資源服務(wù)器也因其他用戶訪問(wèn)過(guò)早已將文件緩存在內(nèi)存中了;
  • 加載的JS文件會(huì)丟到JS引擎執(zhí)行,其中可能涉及的種種緩存就不再展開(kāi)了;
  • 整個(gè)過(guò)程中鏈條上涉及的所有的計(jì)算機(jī)和網(wǎng)絡(luò)設(shè)備 ,執(zhí)行的熱點(diǎn)代碼和數(shù)據(jù)很可能會(huì)載入CPU的多級(jí)高速緩存。

這里列舉的僅僅是一部分 常見(jiàn)的緩存,就有多種多樣的形式:從廉價(jià)的磁盤到昂貴的CPU高速緩存,最終目的都是用來(lái)?yè)Q取寶貴的時(shí)間。

既然緩存那么好,那么問(wèn)題就來(lái)了:緩存是“銀彈”嗎?

不,Phil Karlton 曾說(shuō)過(guò):

There are only two hard things in Computer Science: cache invalidation and naming things.

計(jì)算機(jī)科學(xué)中只有兩件困難的事情:緩存失效和命名規(guī)范。

緩存的使用除了帶來(lái)額外的復(fù)雜度以外,還面臨如何處理緩存失效的問(wèn)題。

  • 多線程并發(fā)編程需要用各種手段(比如Java中的synchronized volatile)防止并發(fā)更新數(shù)據(jù),一部分原因就是防止線程本地緩存的不一致;
  • 緩存失效衍生的問(wèn)題還有:緩存穿透、緩存擊穿、緩存雪崩。解決用不存在的Key來(lái)穿透攻擊,需要用空值緩存或布隆過(guò)濾器;解決單個(gè)緩存過(guò)期后,瞬間被大量惡意查詢擊穿的問(wèn)題需要做查詢互斥;解決某個(gè)時(shí)間點(diǎn)大量緩存同時(shí)過(guò)期的雪崩問(wèn)題需要添加隨機(jī)TTL;
  • 熱點(diǎn)數(shù)據(jù)如果是多級(jí)緩存,在發(fā)生修改時(shí)需要清除或修改各級(jí)緩存,這些操作往往不是原子操作,又會(huì)涉及各種不一致問(wèn)題。

除了通常意義上的緩存外,對(duì)象重用的池化技術(shù),也可以看作是一種緩存的變體。

常見(jiàn)的諸如JVM,V8這類運(yùn)行時(shí)的常量池、數(shù)據(jù)庫(kù)連接池、HTTP連接池、線程池、Golang的sync.Pool對(duì)象池等等。

在需要某個(gè)資源時(shí)從現(xiàn)有的池子里直接拿一個(gè),稍作修改或直接用于另外的用途,池化重用也是性能優(yōu)化常見(jiàn)手段。

壓縮術(shù)

5d468c36-7b61-11ed-8abf-dac502259ad0.png

說(shuō)完了兩個(gè)“空間換時(shí)間”的,我們?cè)倏匆粋€(gè)“時(shí)間換空間”的辦法——壓縮。

壓縮的原理消耗計(jì)算的時(shí)間,換一種更緊湊的編碼方式來(lái)表示數(shù)據(jù)。

為什么要拿時(shí)間換空間?時(shí)間不是最寶貴的資源嗎?

舉一個(gè)視頻網(wǎng)站的例子,如果不對(duì)視頻做任何壓縮編碼,因?yàn)閹捰邢?,巨大的?shù)據(jù)量在網(wǎng)絡(luò)傳輸?shù)暮臅r(shí)會(huì)比編碼壓縮的耗時(shí)多得多。

對(duì)數(shù)據(jù)的壓縮雖然消耗了時(shí)間來(lái)?yè)Q取更小的空間存儲(chǔ),但更小的存儲(chǔ)空間會(huì)在另一個(gè)維度帶來(lái)更大的時(shí)間收益。

這個(gè)例子本質(zhì)上是:“操作系統(tǒng)內(nèi)核與網(wǎng)絡(luò)設(shè)備處理負(fù)擔(dān) vs 壓縮解壓的CPU/GPU負(fù)擔(dān) ”的權(quán)衡和取舍。

我們?cè)诖a中通常用的是無(wú)損壓縮,比如下面這些場(chǎng)景:

  • HTTP協(xié)議中Accept-Encoding添加Gzip/deflate,服務(wù)端對(duì)接受壓縮的文本(JS/CSS/HTML)請(qǐng)求做壓縮,大部分圖片格式本身已經(jīng)是壓縮的無(wú)需壓縮;
  • HTTP2協(xié)議的頭部HPACK壓縮;
  • JS/CSS文件的混淆和壓縮(Uglify/Minify);
  • 一些RPC協(xié)議和消息隊(duì)列傳輸?shù)南⒅?,采用二進(jìn)制編碼和壓縮(Gzip、Snappy、LZ4等等);
  • 緩存服務(wù)存過(guò)大的數(shù)據(jù),通常也會(huì)事先壓縮一下再存,取的時(shí)候解壓;
  • 一些大文件的存儲(chǔ),或者不常用的歷史數(shù)據(jù)存儲(chǔ),采用更高壓縮比的算法存儲(chǔ);
  • JVM的對(duì)象指針壓縮,JVM在32G以下的堆內(nèi)存情況下默認(rèn)開(kāi)啟“UseCompressedOops”,用4個(gè)byte就可以表示一個(gè)對(duì)象的指針,這也是JVM盡量不要把堆內(nèi)存設(shè)置到32G以上的原因;
  • MongoDB的二進(jìn)制存儲(chǔ)的BSON相對(duì)于純文本的JSON也是一種壓縮,或者說(shuō)更緊湊的編碼。但更緊湊的編碼也意味著更差的可讀性,這一點(diǎn)也是需要取舍的。純文本的JSON比二進(jìn)制編碼要更占存儲(chǔ)空間但卻是REST API的主流,因?yàn)閿?shù)據(jù)交換的場(chǎng)景下的可讀性是非常重要的。

信息論告訴我們,無(wú)損壓縮的極限是信息熵。進(jìn)一步減小體積只能以損失部分信息為代價(jià),也就是有損壓縮。

5d806e92-7b61-11ed-8abf-dac502259ad0.png

那么,有損壓縮有哪些應(yīng)用呢?

  • 預(yù)覽和縮略圖,低速網(wǎng)絡(luò)下視頻降幀、降清晰度,都是對(duì)信息的有損壓縮;
  • 音視頻等多媒體數(shù)據(jù)的采樣和編碼大多是有損的,比如MP3是利用傅里葉變換,有損地存儲(chǔ)音頻文件;jpeg等圖片編碼也是有損的。雖然有像WAV/PCM這類無(wú)損的音頻編碼方式,但多媒體數(shù)據(jù)的采樣本身就是有損的,相當(dāng)于只截取了真實(shí)世界的極小一部分?jǐn)?shù)據(jù);
  • 散列化,比如K-V存儲(chǔ)時(shí)Key過(guò)長(zhǎng),先對(duì)Key執(zhí)行一次“傻”系列(SHA-1、SHA-256)哈希算法變成固定長(zhǎng)度的短Key。另外,散列化在文件和數(shù)據(jù)驗(yàn)證(MD5、CRC、HMAC)場(chǎng)景用的也非常多,無(wú)需耗費(fèi)大量算力對(duì)比完整的數(shù)據(jù)。

除了有損/無(wú)損壓縮,但還有一個(gè)辦法,就是壓縮的極端——從根本上減少數(shù)據(jù)或徹底刪除。

能減少的就減少:

  • JS打包過(guò)程“搖樹(shù)”,去掉沒(méi)有使用的文件、函數(shù)、變量;
  • 開(kāi)啟HTTP/2和高版本的TLS,減少了Round Trip,節(jié)省了TCP連接,自帶大量性能優(yōu)化;
  • 減少不必要的信息,比如Cookie的數(shù)量,去掉不必要的HTTP請(qǐng)求頭;
  • 更新采用增量更新,比如HTTP的PATCH,只傳輸變化的屬性而不是整條數(shù)據(jù);
  • 縮短單行日志的長(zhǎng)度、縮短URL、在具有可讀性情況下用短的屬性名等等;
  • 使用位圖和位操作,用風(fēng)騷的位操作最小化存取的數(shù)據(jù)。典型的例子有:用Redis的位圖來(lái)記錄統(tǒng)計(jì)海量用戶登錄狀態(tài);布隆過(guò)濾器用位圖排除不可能存在的數(shù)據(jù);大量開(kāi)關(guān)型的設(shè)置的存儲(chǔ)等等。

能刪除的就刪除:

  • 刪掉不用的數(shù)據(jù);
  • 刪掉不用的索引;
  • 刪掉不該打的日志;
  • 刪掉不必要的通信代碼,不去發(fā)不必要的HTTP、RPC請(qǐng)求或調(diào)用,輪詢改發(fā)布訂閱;
  • 終極方案:砍掉整個(gè)功能。

畢竟有位叫做 Kelsey Hightower 的大佬曾經(jīng)說(shuō)過(guò):

No code is the best way to write secure and reliable applications. Write nothing; deploy nowhere

不寫代碼,是編寫安全可靠的應(yīng)用程序的最佳方式。什么都不寫;哪里都不部署。

預(yù)取術(shù)

預(yù)取通常搭配緩存一起用,其原理是在緩存空間換時(shí)間基礎(chǔ)上更進(jìn)一步,再加上一次“時(shí)間換時(shí)間”,也就是:用事先預(yù)取的耗時(shí),換取第一次加載的時(shí)間。

當(dāng)可以猜測(cè)出以后的某個(gè)時(shí)間很有可能會(huì)用到某種數(shù)據(jù)時(shí),把數(shù)據(jù)預(yù)先取到需要用的地方,能大幅度提升用戶體驗(yàn)或服務(wù)端響應(yīng)速度。

5d9019a0-7b61-11ed-8abf-dac502259ad0.png

是否用預(yù)取模式就像自助餐餐廳與廚師現(xiàn)做的區(qū)別,在自助餐餐廳可以直接拿做好的菜品,一般餐廳需要坐下來(lái)等菜品現(xiàn)做。

那么,預(yù)取在哪些實(shí)際場(chǎng)景會(huì)用呢?

  • 視頻或直播類網(wǎng)站,在播放前先緩沖一小段時(shí)間,就是預(yù)取數(shù)據(jù)。有的在播放時(shí)不僅預(yù)取這一條數(shù)據(jù),甚至還會(huì)預(yù)測(cè)下一個(gè)要看的其他內(nèi)容,提前把數(shù)據(jù)取到本地;
  • HTTP/2 Server Push,在瀏覽器請(qǐng)求某個(gè)資源時(shí),服務(wù)器順帶把其他相關(guān)的資源一起推回去,HTML/JS/CSS幾乎同時(shí)到達(dá)瀏覽器端,相當(dāng)于瀏覽器被動(dòng)預(yù)取了資源;
  • 一些客戶端軟件會(huì)用常駐進(jìn)程的形式,提前預(yù)取數(shù)據(jù)或執(zhí)行一些代碼,這樣可以極大提高第一次使用的打開(kāi)速度;
  • 服務(wù)端同樣也會(huì)用一些預(yù)熱機(jī)制,一方面熱點(diǎn)數(shù)據(jù)預(yù)取到內(nèi)存提前形成多級(jí)緩存;另一方面也是對(duì)運(yùn)行環(huán)境的預(yù)熱,載入CPU高速緩存、熱點(diǎn)函數(shù)JIT編譯成機(jī)器碼等等;
  • 熱點(diǎn)資源提前預(yù)分配到各個(gè)實(shí)例,比如:秒殺、售票的庫(kù)存性質(zhì)的數(shù)據(jù);分布式唯一ID等等

天上不會(huì)掉餡餅,預(yù)取也是有副作用的 。

正如烤箱預(yù)熱需要消耗時(shí)間和額外的電費(fèi),在軟件代碼中做預(yù)取/預(yù)熱的副作用通常是啟動(dòng)慢一些、占用一些閑時(shí)的計(jì)算資源、可能取到的不一定是后面需要的。

削峰填谷術(shù)

5eb8f4d2-7b61-11ed-8abf-dac502259ad0.png

削峰填谷的原理也是“時(shí)間換時(shí)間”,谷時(shí)換峰時(shí)。

削峰填谷與預(yù)取是反過(guò)來(lái)的:預(yù)取是事先花時(shí)間做,削峰填谷是事后花時(shí)間做。就像三峽大壩可以抗住短期巨量洪水,事后雨停再慢慢開(kāi)閘防水。軟件世界的“削峰填谷”是類似的,只是不是用三峽大壩實(shí)現(xiàn),而是用消息隊(duì)列、異步化等方式。

常見(jiàn)的有這幾類問(wèn)題,我們分別來(lái)看每種對(duì)應(yīng)的解決方案:

  • 針對(duì)前端、客戶端的啟動(dòng)優(yōu)化或首屏優(yōu)化 :代碼和數(shù)據(jù)等資源的延時(shí)加載、分批加載、后臺(tái)異步加載、或按需懶加載等等。
  • 背壓控制 - 限流、節(jié)流、去抖等等 。一夫當(dāng)關(guān),萬(wàn)夫莫開(kāi),從入口處削峰,防止一些惡意重復(fù)請(qǐng)求以及請(qǐng)求過(guò)于頻繁的爬蟲,甚至是一些DDoS攻擊。簡(jiǎn)單做法有網(wǎng)關(guān)層根據(jù)單個(gè)IP或用戶用漏桶控制請(qǐng)求速率和上限;前端做按鈕的節(jié)流去抖防止重復(fù)點(diǎn)擊;網(wǎng)絡(luò)層開(kāi)啟TCP SYN Cookie防止惡意的SYN洪水攻擊等等。徹底杜絕爬蟲、黑客手段的惡意洪水攻擊是很難的,DDoS這類屬于網(wǎng)絡(luò)安全范疇了。
  • 針對(duì)正常的業(yè)務(wù)請(qǐng)求洪峰,用消息隊(duì)列暫存再異步化處理 :常見(jiàn)的后端消息隊(duì)列Kafka、RocketMQ甚至Redis等等都可以做緩沖層,第一層業(yè)務(wù)處理直接校驗(yàn)后丟到消息隊(duì)列中,在洪峰過(guò)去后慢慢消費(fèi)消息隊(duì)列中的消息,執(zhí)行具體的業(yè)務(wù)。另外執(zhí)行過(guò)程中的耗時(shí)和耗計(jì)算資源的操作,也可以丟到消息隊(duì)列或數(shù)據(jù)庫(kù)中,等到谷時(shí)處理。
  • 捋平毛刺 :有時(shí)候洪峰不一定來(lái)自外界,如果系統(tǒng)內(nèi)部大量定時(shí)任務(wù)在同一時(shí)間執(zhí)行,或與業(yè)務(wù)高峰期重合,很容易在監(jiān)控中看到“毛刺”——短時(shí)間負(fù)載極高。一般解決方案就是錯(cuò)峰執(zhí)行定時(shí)任務(wù),或者分配到其他非核心業(yè)務(wù)系統(tǒng)中,把“毛刺”攤平。比如很多數(shù)據(jù)分析型任務(wù)都放在業(yè)務(wù)低谷期去執(zhí)行,大量定時(shí)任務(wù)在創(chuàng)建時(shí)盡量加一些隨機(jī)性來(lái)分散執(zhí)行時(shí)間。
  • 避免錯(cuò)誤風(fēng)暴帶來(lái)的次生洪峰 :有時(shí)候網(wǎng)絡(luò)抖動(dòng)或短暫宕機(jī),業(yè)務(wù)會(huì)出現(xiàn)各種異?;蝈e(cuò)誤。這時(shí)處理不好很容易帶來(lái)次生災(zāi)害,比如:很多代碼都會(huì)做錯(cuò)誤重試,不加控制的大量重試甚至?xí)?dǎo)致網(wǎng)絡(luò)抖動(dòng)恢復(fù)后的瞬間,積壓的大量請(qǐng)求再次沖垮整個(gè)系統(tǒng);還有一些代碼沒(méi)有做超時(shí)、降級(jí)等處理,可能導(dǎo)致大量的等待耗盡TCP連接,進(jìn)而導(dǎo)致整個(gè)系統(tǒng)被沖垮。解決之道就是做限定次數(shù)、間隔指數(shù)級(jí)增長(zhǎng)的Back-Off重試,設(shè)定超時(shí)、降級(jí)策略。

批量處理術(shù)

5ef6f476-7b61-11ed-8abf-dac502259ad0.png

批量處理同樣可以看成“時(shí)間換時(shí)間”,其原理是減少了重復(fù)的事情,是一種對(duì)執(zhí)行流程的壓縮。以個(gè)別批量操作更長(zhǎng)的耗時(shí)為代價(jià),在整體上換取了更多的時(shí)間。

批量處理的應(yīng)用也非常廣泛,我們還是從前端開(kāi)始講:

  • 打包合并的JS文件、雪碧圖等等,將一批資源集中到一起,一次性傳輸
  • 前端動(dòng)畫使用requestAnimationFrame在UI渲染時(shí)批量處理積壓的變化,而不是有變化立刻更新,在游戲開(kāi)發(fā)中也有類似的應(yīng)用;
  • 前后端中使用隊(duì)列暫存臨時(shí)產(chǎn)生的數(shù)據(jù) ,積壓到一定數(shù)量再批量處理;在不影響可擴(kuò)展性情況下,一個(gè)接口傳輸多種需要的數(shù)據(jù) ,減少大量ajax調(diào)用(GraphQL在這一點(diǎn)就做到了極致);
  • 系統(tǒng)間通信盡量發(fā)送整批數(shù)據(jù) ,比如消息隊(duì)列的發(fā)布訂閱、存取緩存服務(wù)的數(shù)據(jù)、RPC調(diào)用、插入或更新數(shù)據(jù)庫(kù)等等,能批量做盡可能批量做,因?yàn)檫@些系統(tǒng)間通信的I/O時(shí)間開(kāi)銷已經(jīng)很昂貴了;
  • 數(shù)據(jù)積壓到一定程度再落盤 ,操作系統(tǒng)本身的寫文件就是這么做的,Linux的fwrite只是寫入緩沖區(qū)暫存,積壓到一定程度再fsync刷盤。在應(yīng)用層,很多高性能的數(shù)據(jù)庫(kù)和K-V存儲(chǔ)的實(shí)現(xiàn)都體現(xiàn)了這一點(diǎn):一些NoSQL的LSM Tree的第一層就是在內(nèi)存中先積壓到一定大小再往下層合并;Redis的RDB結(jié)合AOF的落盤機(jī)制;Linux系統(tǒng)調(diào)用也提供了批量讀寫多個(gè)緩沖區(qū)文件的系統(tǒng)調(diào)用:readv/writev;
  • 延遲地批量回收資源 ,比如JVM的Survivor Space的S0和S1區(qū)互換、Redis的Key過(guò)期的清除策略。

批量處理如此好用,那么問(wèn)題來(lái)了,每一批放多大最合適呢?

這個(gè)問(wèn)題其實(shí)沒(méi)有定論,有一些個(gè)人經(jīng)驗(yàn)可以分享。

  • 前端把所有文件打包成單個(gè)JS,大部分時(shí)候并不是最優(yōu)解。Webpack提供了很多分塊的機(jī)制,CSS和JS分開(kāi)、JS按業(yè)務(wù)分更小的Chunk結(jié)合懶加載、一些體積大又不用在首屏用的第三方庫(kù)設(shè)置external或單獨(dú)分塊,可能整體性能更高。不一定要一批搞定所有事情,分幾個(gè)小批次反而用戶體驗(yàn)的性能更好。
  • Redis的MGET、MSET來(lái)批量存取數(shù)據(jù)時(shí),每批大小不宜過(guò)大,因?yàn)镽edis主線程只有一個(gè),如果一批太大執(zhí)行期間會(huì)讓其他命令無(wú)法響應(yīng)。經(jīng)驗(yàn)上一批50-100個(gè)Key性能是不錯(cuò)的,但最好在真實(shí)環(huán)境下用真實(shí)大小的數(shù)據(jù)量化度量一下,做Benchmark測(cè)試才能確定一批大小的最優(yōu)值。
  • MySQL、Oracle這類RDBMS,最優(yōu)的批量Insert的大小也視數(shù)據(jù)行的特性而定。我之前在2U8G的Oracle上用一些普遍的業(yè)務(wù)數(shù)據(jù)做過(guò)測(cè)試,批量插入時(shí)每批5000-10000條數(shù)據(jù)性能是最高的,每批過(guò)大會(huì)導(dǎo)致DML的解析耗時(shí)過(guò)長(zhǎng),甚至單個(gè)SQL語(yǔ)句體積超限,單批太多反而得不償失。
  • 消息隊(duì)列的發(fā)布訂閱,每批的消息長(zhǎng)度盡量控制在1MB以內(nèi),有些云服務(wù)商提供的消息隊(duì)列限制了最大長(zhǎng)度,那這個(gè)長(zhǎng)度可能就是性能拐點(diǎn),比如AWS的SQS服務(wù)對(duì)單條消息的限制是256KB。

總之,多大一批可以確保單批響應(yīng)時(shí)間不太長(zhǎng)的同時(shí)讓整體性能最高,是需要在實(shí)際情況下做基準(zhǔn)測(cè)試的,不能一概而論。而批量處理的副作用在于:處理邏輯會(huì)更加復(fù)雜,尤其是一些涉及事務(wù)、并發(fā)的問(wèn)題;需要用數(shù)組或隊(duì)列用來(lái)存放緩沖一批數(shù)據(jù),消耗了額外的存儲(chǔ)空間。

基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能

  • 項(xiàng)目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 視頻教程:https://doc.iocoder.cn/video/

中篇

引言

前面我們總結(jié)了六種普適的性能優(yōu)化方法,包括 索引、壓縮、緩存、預(yù)取、削峰填谷、批量處理 ,簡(jiǎn)單講解了每種技術(shù)手段的原理和實(shí)際應(yīng)用。

在開(kāi)啟最后一篇前,我們先需要搞清楚:

在程序運(yùn)行期間,時(shí)間和空間都耗在哪里了?

時(shí)間都去哪兒了?

人眨一次眼大約100毫秒,而現(xiàn)代1核CPU在一眨眼的功夫就可以執(zhí)行數(shù)億條指令。

現(xiàn)代的CPU已經(jīng)非常厲害了,頻率已經(jīng)達(dá)到了GHz級(jí)別,也就是每秒數(shù)十億個(gè)指令周期。

即使一些CPU指令需要多個(gè)時(shí)鐘周期,但由于有流水線機(jī)制的存在,平均下來(lái)大約每個(gè)時(shí)鐘周期能執(zhí)行1條指令,比如一個(gè)3GHz頻率的CPU核心,每秒大概可以執(zhí)行20億到40億左右的指令數(shù)量。

程序運(yùn)行還需要RAM,也可能用到持久化存儲(chǔ),網(wǎng)絡(luò)等等。隨著新的技術(shù)和工藝的出現(xiàn),這些硬件也越來(lái)越厲害,比如CPU高速緩存的提升、NVMe固態(tài)硬盤相對(duì)SATA盤讀寫速率和延遲的飛躍等等。這些硬件具體有多強(qiáng)呢?

有一個(gè)非常棒的網(wǎng)站“Latency Numbers Every Programmer Should Know”,可以直觀地查看從1990年到現(xiàn)在,高速緩存、內(nèi)存、硬盤、網(wǎng)絡(luò)時(shí)間開(kāi)銷的具體數(shù)值。

https://colin-scott.github.io/personal_website/research/interactive_latency.html

下圖是2020年的截圖,的確是“每個(gè)開(kāi)發(fā)者應(yīng)該知道的數(shù)字”。

5f1c3970-7b61-11ed-8abf-dac502259ad0.png5f2b96ae-7b61-11ed-8abf-dac502259ad0.png

這里有幾個(gè)非常關(guān)鍵的數(shù)據(jù):

  • 存取一次CPU多級(jí)高速緩存的時(shí)間大約1-10納秒級(jí)別;
  • 存取一次主存(RAM)的時(shí)間大概在100納秒級(jí)別;
  • 固態(tài)硬盤的一次隨機(jī)讀寫大約在10微秒到1毫秒這個(gè)數(shù)量級(jí);
  • 網(wǎng)絡(luò)包在局域網(wǎng)傳輸一個(gè)來(lái)回大約是0.5毫秒。

看到不同硬件之間數(shù)量級(jí)的差距,就很容易理解性能優(yōu)化的一些技術(shù)手段了。

比如一次網(wǎng)絡(luò)傳輸?shù)臅r(shí)間,是主存訪問(wèn)的5000倍,明白這點(diǎn)就不難理解寫for循環(huán)發(fā)HTTP請(qǐng)求,為什么會(huì)被扣工資了。

放大到我們?nèi)菀赘兄臅r(shí)間范圍,來(lái)理解5000倍的差距:如果一次主存訪問(wèn)是1天的話,一趟局域網(wǎng)數(shù)據(jù)傳輸就要13.7年。

如果要傳輸更多網(wǎng)絡(luò)數(shù)據(jù),每?jī)蓚€(gè)網(wǎng)絡(luò)幀之間還有固定的間隔(Interpacket Gap),在間隔期間傳輸Idle信號(hào),數(shù)據(jù)鏈路層以此來(lái)區(qū)分兩個(gè)數(shù)據(jù)包,具體數(shù)值在鏈接Wiki中有,這里截取幾個(gè)我們熟悉的網(wǎng)絡(luò)來(lái)感受一下:

  • 百兆以太網(wǎng): 0.96 μs
  • 千兆以太網(wǎng):96 ns
  • 萬(wàn)兆以太網(wǎng):9.6 ns

不過(guò),單純看硬件的上限意義不大,從代碼到機(jī)器指令中間有許多層抽象,僅僅是在TCP連接上發(fā)一個(gè)字節(jié)的數(shù)據(jù)包,從操作系統(tǒng)內(nèi)核到網(wǎng)線,涉及到的基礎(chǔ)設(shè)施級(jí)別的軟硬件不計(jì)其數(shù)。到了應(yīng)用層,單次操作耗時(shí)雖然沒(méi)有非常精確的數(shù)字,但經(jīng)驗(yàn)上的范圍也值得參考:

  • 用Memcached/Redis存取緩存數(shù)據(jù):1-5 ms
  • 執(zhí)行一條簡(jiǎn)單的數(shù)據(jù)庫(kù)查詢或更新操作:5-50ms
  • 在局域網(wǎng)中的TCP連接上收發(fā)一趟數(shù)據(jù)包:1-10ms;廣域網(wǎng)中大約10-200ms,視傳輸距離和網(wǎng)絡(luò)節(jié)點(diǎn)的設(shè)備而定
  • 從用戶態(tài)切換到內(nèi)核態(tài),完成一次系統(tǒng)調(diào)用:100ns - 1 μs,視不同的系統(tǒng)調(diào)用函數(shù)和硬件水平而定,少數(shù)系統(tǒng)調(diào)用可能遠(yuǎn)超此范圍。

空間都去哪兒了?

在計(jì)算機(jī)歷史上,非易失存儲(chǔ)技術(shù)的發(fā)展速度超過(guò)了摩爾定律。除了嵌入式設(shè)備、數(shù)據(jù)庫(kù)系統(tǒng)等等,現(xiàn)在大部分場(chǎng)景已經(jīng)不太需要優(yōu)化持久化存儲(chǔ)的空間占用了,這里主要講的是另一個(gè)相對(duì)稀缺的存儲(chǔ)形式 —— RAM,或者說(shuō)主存/內(nèi)存。

以JVM為例,在堆里面有很多我們創(chuàng)建的對(duì)象(Object)。

  • 每個(gè)Object都有一個(gè)包含Mark和類型指針的Header,占12個(gè)字節(jié)
  • 每個(gè)成員變量,根據(jù)數(shù)據(jù)類型的不同占不同的字節(jié)數(shù),如果是另一個(gè)對(duì)象,其對(duì)象指針占4個(gè)字節(jié)
  • 數(shù)組會(huì)根據(jù)聲明的大小,占用N倍于其類型Size的字節(jié)數(shù)
  • 成員變量之間需要對(duì)齊到4字節(jié),每個(gè)對(duì)象之間需要對(duì)齊到8字節(jié)

如果在32G以上內(nèi)存的機(jī)器上,禁用了對(duì)象指針壓縮,對(duì)象指針會(huì)變成8字節(jié),包括Header中的Klass指針,這也就不難理解為什么堆內(nèi)存超過(guò)32G,JVM的性能直線下降了。

舉個(gè)例子,一個(gè)有8個(gè)int類型成員的對(duì)象,需要占用48個(gè)字節(jié)(12+32+4),如果有十萬(wàn)個(gè)這樣的Object,就需要占用4.58MB的內(nèi)存了。這個(gè)數(shù)字似乎看起來(lái)不大,而實(shí)際上一個(gè)Java服務(wù)的堆內(nèi)存里面,各種各樣的對(duì)象占用的內(nèi)存通常比這個(gè)數(shù)字多得多,大部分內(nèi)存耗在char[]這類數(shù)組或集合型數(shù)據(jù)類型上。

舉個(gè)例子,一個(gè)有8個(gè)int類型成員的對(duì)象,需要占用48個(gè)字節(jié)(12+32+4),如果有十萬(wàn)個(gè)這樣的Object,就需要占用4.58MB的內(nèi)存了。這個(gè)數(shù)字似乎看起來(lái)不大,而實(shí)際上一個(gè)Java服務(wù)的堆內(nèi)存里面,各種各樣的對(duì)象占用的內(nèi)存通常比這個(gè)數(shù)字多得多,大部分內(nèi)存耗在char[]這類數(shù)組或集合型數(shù)據(jù)類型上。

堆內(nèi)存之外,又是另一個(gè)世界了。

從操作系統(tǒng)進(jìn)程的角度去看,也有不少耗內(nèi)存的大戶,不管什么Runtime都逃不開(kāi)這些空間開(kāi)銷:每個(gè)線程需要分配MB級(jí)別的線程棧,運(yùn)行的程序和數(shù)據(jù)會(huì)緩存下來(lái),用到的輸入輸出設(shè)備需要緩沖區(qū)……

代碼“寫出來(lái)”的內(nèi)存占用,僅僅是冰山之上的部分,真正的內(nèi)存占用比“寫出來(lái)”的要更多,到處都存在空間利用率的問(wèn)題。

比如,即使我們?cè)贘ava代碼中只是寫了 response.getWriter().print(“OK”),給瀏覽器返回2字節(jié),網(wǎng)絡(luò)協(xié)議棧的層層封裝,協(xié)議頭部不斷增加的額外數(shù)據(jù),讓最終返回給瀏覽器的字節(jié)數(shù)遠(yuǎn)超原始的2字節(jié),像IP協(xié)議的報(bào)頭部就至少有20個(gè)字節(jié),而數(shù)據(jù)鏈路層的一個(gè)以太網(wǎng)幀頭部至少有18字節(jié)。

如果傳輸?shù)臄?shù)據(jù)過(guò)大,各層協(xié)議還有最大傳輸單元MTU的限制,IPv4一個(gè)報(bào)文最大只能有64K比特,超過(guò)此值需要分拆發(fā)送并在接收端組合,更多額外的報(bào)頭導(dǎo)致空間利用率降低(IPv6則提供了Jumbogram機(jī)制,最大單包4G比特,“浪費(fèi)”就減少了)。

這部分的“浪費(fèi)”有多大呢?下面的鏈接有個(gè)表格,傳輸1460個(gè)字節(jié)的載荷,經(jīng)過(guò)有線到無(wú)線網(wǎng)絡(luò)的轉(zhuǎn)換,至少再添120個(gè)字節(jié),空間利用率<92.4%

https://en.wikipedia.org/wiki/Jumbo_frame

這種現(xiàn)象非常普遍,使用抽象層級(jí)越高的技術(shù)平臺(tái),平臺(tái)提供高級(jí)能力的同時(shí),其底層實(shí)現(xiàn)的“信息密度”通常越低。

像Java的Object Header就是使用JVM的代價(jià),而更進(jìn)一步使用動(dòng)態(tài)類型語(yǔ)言,要為靈活性付出空間的代價(jià)則更大。哈希表的自動(dòng)擴(kuò)容,強(qiáng)大的反射能力等等,背后也付出了空間的代價(jià)。

再比如,二進(jìn)制數(shù)據(jù)交換協(xié)議通常比純文本協(xié)議更加節(jié)約空間。但多數(shù)廠家我們?nèi)匀挥肑SON、XML等純文本協(xié)議,用信息的冗余來(lái)?yè)Q取可讀性。即便是二進(jìn)制的數(shù)據(jù)交互格式,也會(huì)存在信息冗余,只能通過(guò)更好的協(xié)議和壓縮算法,盡量去逼近壓縮的極限 —— 信息熵。

小結(jié)

理解了時(shí)間和空間的消耗在哪后,還不能完全解釋軟件為何傾向于耗盡硬件資源。有一條定律可以解釋,正是它錘爆了摩爾定律。

它就是安迪-比爾定律。

“安迪給什么,比爾拿走什么”。

安迪指的是Intel前CEO安迪·葛洛夫,比爾指的是比爾·蓋茨。

這句話的意思就是:軟件發(fā)展比硬件還快,總能吃得下硬件 。

20年前,在最強(qiáng)的計(jì)算機(jī)也不見(jiàn)得可以玩賽車游戲;

10年前,個(gè)人電腦已經(jīng)可以玩畫質(zhì)還可以的3D賽車游戲了;

現(xiàn)在,自動(dòng)駕駛+5G云駕駛已經(jīng)快成為現(xiàn)實(shí)。

在這背后,是無(wú)數(shù)的硬件技術(shù)飛躍,以及吃掉了這些硬件的各類軟件。

這也是我們每隔兩三年都要換手機(jī)的原因:不是機(jī)器老化變卡了,是嗜血的軟件在作怪。

5f3b39c4-7b61-11ed-8abf-dac502259ad0.png

因此,即使現(xiàn)代的硬件水平已經(jīng)強(qiáng)悍到如此境地,性能優(yōu)化仍然是有必要的。

軟件日益復(fù)雜,抽象層級(jí)越來(lái)越高,就越需要底層基礎(chǔ)設(shè)施被充分優(yōu)化。

對(duì)于大部分開(kāi)發(fā)者而言,高層代碼逐步走向低代碼化、可視化,“一行代碼”能產(chǎn)生的影響也越來(lái)越大,寫出低效代碼則會(huì)吃掉更多的硬件資源。

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能

  • 項(xiàng)目地址:https://github.com/YunaiV/yudao-cloud
  • 視頻教程:https://doc.iocoder.cn/video/

下篇

引言

本篇也是本系列最硬核的一篇,本人技術(shù)水平有限,可能存在疏漏或錯(cuò)誤之處,望斧正。仍然選取了《火影忍者》的配圖和命名方式幫助理解:

  • 八門遁甲 —— 榨干計(jì)算資源
  • 影分身術(shù) —— 水平擴(kuò)容
  • 奧義 —— 分片術(shù)
  • 秘術(shù) —— 無(wú)鎖術(shù)

(注:這些“中二”的前綴僅是用《火影》中的一些術(shù)語(yǔ),形象地描述技術(shù)方案)

八門遁甲 —— 榨干計(jì)算資源

5f65dc56-7b61-11ed-8abf-dac502259ad0.png

讓硬件資源都在處理真正有用的邏輯計(jì)算,而不是做無(wú)關(guān)的事情或空轉(zhuǎn)。

晶體管集成電路、驅(qū)動(dòng)程序、操作系統(tǒng)、直到高級(jí)編程語(yǔ)言的層層抽象,每一層抽象帶來(lái)的更強(qiáng)的通用性、更高的開(kāi)發(fā)效率,多是以損失運(yùn)行效率為代價(jià)的。

但我們可以在用高級(jí)編程語(yǔ)言寫代碼的時(shí)候,在保障可讀性、可維護(hù)性基礎(chǔ)上用運(yùn)行效率更高、更適合運(yùn)行時(shí)環(huán)境的方式去寫,減少額外的性能損耗《Effective XXX》、《More Effective XXX》、《高性能XXX》這類書籍所傳遞的知識(shí)和思想。

落到技術(shù)細(xì)節(jié),下面用四個(gè)小節(jié)來(lái)說(shuō)明如何減少“無(wú)用功”、避免空轉(zhuǎn)、榨干硬件。

聚焦

減少系統(tǒng)調(diào)用與上下文切換,讓CPU聚焦。

可以看看兩個(gè) stackoverflow 上的帖子:

https://stackoverflow .com/questions/21887797/what-is-the-overhead-of-a-context-switchhttps://stackoverflow.com/questions/23599074/system-calls-overhead

大部分互聯(lián)網(wǎng)應(yīng)用服務(wù),耗時(shí)的部分不是計(jì)算,而是I/O。

減少I/O wait, 各司其職,專心干I/O,專心干計(jì)算,epoll批量撈任務(wù),(refer: event driven)

利用DMA減少CPU負(fù)擔(dān) - 零拷貝 NewI/O Redis SingleThread (even 6.0), Node.js

避免不必要的調(diào)度 - Context Switch

CPU親和性,讓CPU更加聚焦

蛻變

用更高效的數(shù)據(jù)結(jié)構(gòu)、算法、第三方組件,讓程序本身蛻變。

從邏輯短路、Map代替List遍歷、減少鎖范圍、這樣的編碼技巧,到應(yīng)用FisherYates、Dijkstra這些經(jīng)典算法,注意每一行代碼細(xì)節(jié),量變會(huì)發(fā)生質(zhì)變。更何況某個(gè)算法就足以讓系統(tǒng)性能產(chǎn)生一兩個(gè)數(shù)量級(jí)的提升。

適應(yīng)

因地制宜,適應(yīng)特定的運(yùn)行環(huán)境

在瀏覽器中主要是優(yōu)化方向是I/O、UI渲染引擎、JS執(zhí)行引擎三個(gè)方面。

I/O越少越好,能用WebSocket的地方就不用Ajax,能用Ajax的地方就不要刷整個(gè)頁(yè)面;

UI渲染方面,減少重排和重繪,比如Vue、React等MVVM框架的虛擬DOM用額外的計(jì)算換取最精簡(jiǎn)的DOM操作;

JS執(zhí)行引擎方面,少用動(dòng)態(tài)性極高的寫法,比如eval、隨意修改對(duì)象或?qū)ο笤偷膶傩浴?/p>

前端的優(yōu)化有個(gè)神器:Light House,在新版本Chrome已經(jīng)嵌到開(kāi)發(fā)者工具中了,可以一鍵生成性能優(yōu)化報(bào)告,按照優(yōu)化建議改就完了。

與瀏覽器環(huán)境頗為相似的Node.js環(huán)境:

https://segmentfault.com/a/1190000007621011#articleHeader11

Java

  • C1 C2 JIT編譯器
  • 棧上分配

Linux

  • 各種參數(shù)優(yōu)化
  • 內(nèi)存分配和GC策略
  • Linux內(nèi)核參數(shù) Brendan Gregg
  • 內(nèi)存區(qū)塊配置(DB,JVM,V8,etc.)

利用語(yǔ)言特性和運(yùn)行時(shí)環(huán)境 - 比如寫出利于JIT的代碼

  • 多靜態(tài)少動(dòng)態(tài) - 舍棄動(dòng)態(tài)特性的靈活性 - hardcode/if-else,強(qiáng)類型,弱類型語(yǔ)言避免類型轉(zhuǎn)換 AOT/JIT vs 解釋器, 匯編,機(jī)器碼 GraalVM

減少內(nèi)存的分配和回收,少對(duì)列表做增加或刪除

對(duì)于RAM有限的嵌入式環(huán)境,有時(shí)候時(shí)間不是問(wèn)題,反而要拿時(shí)間換空間,以節(jié)約RAM的使用。

運(yùn)籌

把眼界放寬,跳出程序和運(yùn)行環(huán)境本身,從整體上進(jìn)行系統(tǒng)性分析最高性價(jià)比的優(yōu)化方案,分析潛在的優(yōu)化切入點(diǎn),以及能夠調(diào)配的資源和技術(shù),運(yùn)籌帷幄。

其中最簡(jiǎn)單易行的幾個(gè)辦法,就是花錢,買更好或更多的硬件基礎(chǔ)設(shè)施,這往往是開(kāi)發(fā)人員容易忽視的,這里提供一些妙招:

  • 服務(wù)器方面,云服務(wù)廠商提供各種類型的實(shí)例,每種類型有不同的屬性側(cè)重,帶寬、CP、磁盤的I/O能力,選適合的而不是更貴的
  • 舍棄虛擬機(jī) - Bare Mental,比如神龍服務(wù)器
  • ARM架構(gòu)CPU的服務(wù)器,同等價(jià)格可以買到更多的服務(wù)器,對(duì)于多數(shù)可以跨平臺(tái)運(yùn)行的服務(wù)端系統(tǒng)來(lái)說(shuō)與x86區(qū)別并不大,ARM服務(wù)器的數(shù)據(jù)中心也是技術(shù)發(fā)展趨勢(shì)使然
  • 如果必須用x86系列的服務(wù)器,AMD也Intel的性價(jià)比更高。

第一點(diǎn)非常重要,軟件性能遵循木桶原理,一定要找到瓶頸在哪個(gè)硬件資源,把錢花在刀刃上。

如果是服務(wù)端帶寬瓶頸導(dǎo)致的性能問(wèn)題,升級(jí)再多核CPU也是沒(méi)有用的。

我有一次性能優(yōu)化案例:把一個(gè)跑復(fù)雜業(yè)務(wù)的Node.js服務(wù)器從AWS的m4類型換成c4類型,內(nèi)存只有原來(lái)的一半,但CPU使用率反而下降了20%,同時(shí)價(jià)格還比之前更便宜,一石二鳥(niǎo)。

這是因?yàn)镹ode.js主線程的計(jì)算任務(wù)只有一個(gè)CPU核心在干,通過(guò)CPU Profile的火焰圖,可以定位到該業(yè)務(wù)的瓶頸在主線程的計(jì)算任務(wù)上,因此提高單核頻率的作用是立竿見(jiàn)影的。而該業(yè)務(wù)對(duì)內(nèi)存的消耗并不多,套用一些定制v8引擎內(nèi)存參數(shù)的方案,起不了任何作用。

畢竟這樣的例子不多,大部分時(shí)候還是要多花錢買更高配的服務(wù)器的,除了這條花錢能直接解決問(wèn)題的辦法,剩下的辦法難度就大了:

  • 利用更底層的特性實(shí)現(xiàn)功能,比如FFI WebAssembly調(diào)用其他語(yǔ)言,Java Agent Instrument,字節(jié)碼生成(BeanCopier, Json Lib),甚至匯編等等
  • 使用硬件提供的更高效的指令
  • 各種提升TLB命中率的機(jī)制,減少內(nèi)存的大頁(yè)表
  • 魔改Runtime,F(xiàn)acebook的PHP,阿里騰訊定制的JDK
  • 網(wǎng)絡(luò)設(shè)備參數(shù),MTU
  • 專用硬件:GPU加速(cuda)、AES硬件卡和高級(jí)指令加速加解密過(guò)程,比如TLS
  • 可編程硬件:地獄級(jí)難度,FPGA硬件設(shè)備加速特定業(yè)務(wù)
  • NUMA
  • 更宏觀的調(diào)度,VM層面的共享vCPU,K8S集群調(diào)度,總體上的優(yōu)化

小結(jié)

有些手段,是憑空換出來(lái)更多的空間和時(shí)間了嗎?

天下沒(méi)有免費(fèi)的午餐,即使那些看起來(lái)空手套白狼的優(yōu)化技術(shù),也需要額外的人力成本來(lái)做,副作用可能就是專家級(jí)的發(fā)際線吧。還好很多復(fù)雜的性能優(yōu)化技術(shù)我也不會(huì),所以我本人發(fā)際線還可以。

這一小節(jié)總結(jié)了一些方向,有些技術(shù)細(xì)節(jié)非常深,這里也無(wú)力展開(kāi)。不過(guò),即使榨干了單機(jī)性能,也可能不足以支撐業(yè)務(wù),這時(shí)候就需要分布式集群出場(chǎng)了,因此后面介紹的3個(gè)技術(shù)方向,都與并行化有關(guān) 。

影分身術(shù) —— 水平擴(kuò)容

本節(jié)的水平擴(kuò)容以及下面一節(jié)的分片,可以算整體的性能提升而不是單點(diǎn)的性能優(yōu)化,會(huì)因?yàn)橐腩~外組件反而降低了處理單個(gè)請(qǐng)求的性能。

但當(dāng)業(yè)務(wù)規(guī)模大到一定程度時(shí),再好的單機(jī)硬件也無(wú)法承受流量的洪峰,就得水平擴(kuò)容了,畢竟”眾人拾柴火焰高”。

在這背后的理論基礎(chǔ)是,硅基半導(dǎo)體已經(jīng)接近物理極限,隨著摩爾定律的減弱,阿姆達(dá)爾定律的作用顯現(xiàn)出來(lái):

https://en.wikipedia.org/wiki/Amdahl%27s_law

水平擴(kuò)容必然引入負(fù)載均衡

5f939f42-7b61-11ed-8abf-dac502259ad0.png
  • 多副本
  • 水平擴(kuò)容的前提是無(wú)狀態(tài)
  • 讀>>寫, 多個(gè)讀實(shí)例副本 (CDN)
  • 自動(dòng)擴(kuò)縮容,根據(jù)常用的或自定義的metrics,判定擴(kuò)縮容的條件,或根據(jù)CRON
  • 負(fù)載均衡策略的選擇

奧義 —— 分片術(shù)

水平擴(kuò)容針對(duì)無(wú)狀態(tài)組件,分片針對(duì)有狀態(tài)組件。二者原理都是提升并行度,但分片的難度更大。

負(fù)載均衡也不再是簡(jiǎn)單的加權(quán)輪詢了,而是進(jìn)化成了各個(gè)分片的協(xié)調(diào)器

60827e82-7b61-11ed-8abf-dac502259ad0.png
  • Java1.7的及之前的 ConcurrentHashMap分段鎖
  • 有狀態(tài)數(shù)據(jù)的分片
  • 如何選擇Partition/Sharding Key
  • 負(fù)載均衡難題
  • 熱點(diǎn)數(shù)據(jù),增強(qiáng)緩存等級(jí),解決分散的緩存帶來(lái)的一致性難題
  • 數(shù)據(jù)冷熱分離,SSD - HDD
  • 分開(kāi)容易合并難
  • 區(qū)塊鏈的優(yōu)化,分區(qū)域

秘術(shù) —— 無(wú)鎖術(shù)

5f939f42-7b61-11ed-8abf-dac502259ad0.png

有些業(yè)務(wù)場(chǎng)景,比如庫(kù)存業(yè)務(wù),按照正常的邏輯去實(shí)現(xiàn),水平擴(kuò)容帶來(lái)的提升非常有限,因?yàn)樾枰i住庫(kù)存,扣減,再解鎖庫(kù)存。

票務(wù)系統(tǒng)也類似,為了避免超賣,需要有一把鎖禁錮了橫向擴(kuò)展的能力。

不管是單機(jī)還是分布式微服務(wù),鎖都是制約并行度的一大因素。比如上篇提到的秒殺場(chǎng)景,庫(kù)存就那么多,系統(tǒng)超賣了可能導(dǎo)致非常大的經(jīng)濟(jì)損失,但用分布式鎖會(huì)導(dǎo)致即使服務(wù)擴(kuò)容了成千上萬(wàn)個(gè)實(shí)例,最終無(wú)數(shù)請(qǐng)求仍然阻塞在分布式鎖這個(gè)串行組件上了,再多水平擴(kuò)展的實(shí)例也無(wú)用武之地。

避免競(jìng)爭(zhēng)Race Condition 是最完美的解決辦法。

上篇說(shuō)的應(yīng)對(duì)秒殺場(chǎng)景,預(yù)取庫(kù)存就是減輕競(jìng)態(tài)條件的例子,雖然取到服務(wù)器內(nèi)存之后仍然有多線程的鎖,但鎖的粒度更細(xì)了,并發(fā)度也就提高了。

  • 線程同步鎖
  • 分布式鎖
  • 數(shù)據(jù)庫(kù)鎖 update select子句
  • 事務(wù)鎖
  • 順序與亂序
  • 樂(lè)觀鎖/無(wú)鎖 CAS Java 1.8之后的ConcurrentHashMap
  • pipeline技術(shù) - CPU流水線 Redis Pipeline 大數(shù)據(jù)分析 并行計(jì)算
  • TCP的緩沖區(qū)排頭阻塞 QUIC HTTP3.0

總結(jié)

以ROI的視角看軟件開(kāi)發(fā),初期人力成本的投入,后期的維護(hù)成本,計(jì)算資源的費(fèi)用等等,選一個(gè)合適的方案而不是一個(gè)性能最高的方案。

本篇結(jié)合個(gè)人經(jīng)驗(yàn)總結(jié)了常見(jiàn)的性能優(yōu)化手段,這些手段只是冰山一角。在初期就設(shè)計(jì)實(shí)現(xiàn)出一個(gè)完美的高性能系統(tǒng)是不可能的,隨著軟件的迭代和體量的增大,利用壓測(cè),各種工具(profiling,vmstat,iostat,netstat),以及監(jiān)控手段,逐步找到系統(tǒng)的瓶頸,因地制宜地選擇優(yōu)化手段才是正道。

有利必有弊,得到一些必然會(huì)失去一些,有一些手段要慎用。Linux性能優(yōu)化大師Brendan Gregg一再?gòu)?qiáng)調(diào)的就是:切忌過(guò)早優(yōu)化、過(guò)度優(yōu)化。

持續(xù)觀測(cè),做80%高投入產(chǎn)出比的優(yōu)化。

除了這些設(shè)計(jì)和實(shí)現(xiàn)時(shí)可能用到的手段,在技術(shù)選型時(shí)選擇高性能的框架和組件也非常重要。

另外,部署基礎(chǔ)設(shè)施的硬件性能也同樣,合適的服務(wù)器和網(wǎng)絡(luò)等基礎(chǔ)設(shè)施往往會(huì)事半功倍,比如云服務(wù)廠商提供的各種字母開(kāi)頭的instance,網(wǎng)絡(luò)設(shè)備帶寬的速度和穩(wěn)定性,磁盤的I/O能力等等。

多數(shù)時(shí)候我們應(yīng)當(dāng)使用更高性能的方案,但有時(shí)候甚至要故意去違背它們。最后,以《Effective Java》第一章的一句話結(jié)束本文吧。

首先要學(xué)會(huì)基本的規(guī)則,然后才能知道什么時(shí)候可以打破規(guī)則。


審核編輯 :李倩


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 服務(wù)器
    +關(guān)注

    關(guān)注

    13

    文章

    9791

    瀏覽量

    87929
  • 系統(tǒng)性能
    +關(guān)注

    關(guān)注

    0

    文章

    8

    瀏覽量

    6516

原文標(biāo)題:系統(tǒng)性能優(yōu)化的十大策略(強(qiáng)烈推薦,建議收藏)

文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    基于 ASP3605 電源芯片的性能優(yōu)化與 ITH 調(diào)試策略

    的 ITH 管腳調(diào)試,深入探討其對(duì)電源性能的多方面影響,并通過(guò)嚴(yán)謹(jǐn)?shù)膶?shí)驗(yàn)驗(yàn)證和理論分析,提出一套系統(tǒng)性優(yōu)化策略,旨在確保電源轉(zhuǎn)換器在不同工作條件下均能達(dá)到穩(wěn)定、高效的
    的頭像 發(fā)表于 07-14 10:13 ?85次閱讀
    基于 ASP3605 電源芯片的<b class='flag-5'>性能</b><b class='flag-5'>優(yōu)化</b>與 ITH 調(diào)試<b class='flag-5'>策略</b>

    Linux系統(tǒng)性能指南

    Linux服務(wù)器運(yùn)行了很多應(yīng)用,在高負(fù)載下,服務(wù)器可能會(huì)出現(xiàn)性能瓶頸,例如CPU利用率過(guò)高、內(nèi)存不足、磁盤I/O瓶頸等,從而導(dǎo)致系統(tǒng)卡頓,服務(wù)無(wú)法正常運(yùn)行等問(wèn)題。所以針對(duì)以上問(wèn)題,可以通過(guò)調(diào)整內(nèi)核參數(shù)和系統(tǒng)的相關(guān)組件,
    的頭像 發(fā)表于 06-23 14:12 ?695次閱讀
    Linux<b class='flag-5'>系統(tǒng)性能</b>指南

    升降速曲線對(duì)直線電機(jī)系統(tǒng)性能影響的研究

    速曲線對(duì)直線電機(jī)系統(tǒng)性能影響的研究.pdf【免責(zé)聲明】本文系網(wǎng)絡(luò)轉(zhuǎn)載,版權(quán)歸原作者所有。本文所用視頻、圖片、文字如涉及作品版權(quán)問(wèn)題,請(qǐng)第一時(shí)間告知,刪除內(nèi)容!
    發(fā)表于 06-17 08:48

    通信設(shè)備EMC整改:從測(cè)試到優(yōu)化系統(tǒng)性解決方案

    深圳南柯電子|通信設(shè)備EMC整改:從測(cè)試到優(yōu)化系統(tǒng)性解決方案
    的頭像 發(fā)表于 06-16 11:10 ?204次閱讀

    鴻蒙5開(kāi)發(fā)寶藏案例分享---應(yīng)用性能優(yōu)化指南

    頓,打造絲滑應(yīng)用! **1. 控制狀態(tài)刷新 ** ? 核心思想 :狀態(tài)變量是UI刷新的觸發(fā)器,濫用會(huì)導(dǎo)致性能劣化。優(yōu)化策略 : 精簡(jiǎn)狀態(tài)變量 : 普通變量別用 <span class
    發(fā)表于 06-12 17:17

    嵌入式系統(tǒng)存儲(chǔ)的軟件優(yōu)化策略

    在嵌入式系統(tǒng)開(kāi)發(fā)領(lǐng)域,存儲(chǔ)器作為信息交互的核心載體,其技術(shù)特性直接影響著系統(tǒng)性能與穩(wěn)定性。然而,有些人在面對(duì)Linux、安卓等復(fù)雜操作系統(tǒng)環(huán)境時(shí),理解其存儲(chǔ)機(jī)制尚存局限,為突破這些技術(shù)瓶頸,飛凌
    發(fā)表于 02-28 14:17

    大語(yǔ)言模型的解碼策略與關(guān)鍵優(yōu)化總結(jié)

    本文系統(tǒng)性地闡述了大型語(yǔ)言模型(LargeLanguageModels,LLMs)中的解碼策略技術(shù)原理及其實(shí)踐應(yīng)用。通過(guò)深入分析各類解碼算法的工作機(jī)制、性能特征和優(yōu)化方法,為研究者和工
    的頭像 發(fā)表于 02-18 12:00 ?581次閱讀
    大語(yǔ)言模型的解碼<b class='flag-5'>策略</b>與關(guān)鍵<b class='flag-5'>優(yōu)化</b>總結(jié)

    前端性能優(yōu)化:提升用戶體驗(yàn)的關(guān)鍵策略

    在互聯(lián)網(wǎng)飛速發(fā)展的今天,用戶對(duì)于網(wǎng)頁(yè)的加載速度和響應(yīng)性能要求越來(lái)越高。前端性能優(yōu)化成為了提升用戶體驗(yàn)、增強(qiáng)網(wǎng)站競(jìng)爭(zhēng)力的關(guān)鍵策略。一個(gè)性能良好
    的頭像 發(fā)表于 01-22 10:08 ?480次閱讀

    如何優(yōu)化總線系統(tǒng)性能

    總線系統(tǒng)是計(jì)算機(jī)和其他電子設(shè)備中用于傳輸數(shù)據(jù)的關(guān)鍵組件。性能優(yōu)化可以提高數(shù)據(jù)傳輸速率、降低延遲,并增強(qiáng)系統(tǒng)的可靠性和擴(kuò)展性。 1. 理解總線系統(tǒng)
    的頭像 發(fā)表于 12-31 09:54 ?674次閱讀

    華為云 X 實(shí)例 CPU 性能測(cè)試詳解與優(yōu)化策略

    分析 ? 3.2 CPU性能瓶頸分析 ? 4. CPU性能優(yōu)化策略 ? 4.1 優(yōu)化CPU性能
    的頭像 發(fā)表于 12-30 14:52 ?673次閱讀
    華為云 X 實(shí)例 CPU <b class='flag-5'>性能</b>測(cè)試詳解與<b class='flag-5'>優(yōu)化</b><b class='flag-5'>策略</b>

    仿真系統(tǒng)性能優(yōu)化技巧

    在現(xiàn)代工業(yè)和科學(xué)研究中,仿真系統(tǒng)扮演著越來(lái)越重要的角色。它們不僅能夠幫助我們預(yù)測(cè)復(fù)雜系統(tǒng)的行為,還能在沒(méi)有實(shí)際物理原型的情況下進(jìn)行實(shí)驗(yàn)和測(cè)試。然而,隨著仿真模型的復(fù)雜度增加,性能優(yōu)化
    的頭像 發(fā)表于 12-19 14:47 ?2372次閱讀

    PCIe延遲對(duì)系統(tǒng)性能的影響

    隨著技術(shù)的發(fā)展,計(jì)算機(jī)系統(tǒng)對(duì)性能的要求越來(lái)越高。PCIe作為連接處理器、內(nèi)存、存儲(chǔ)和其他外圍設(shè)備的關(guān)鍵接口,其性能直接影響到整個(gè)系統(tǒng)的表現(xiàn)。PCIe延遲,作為衡量數(shù)據(jù)傳輸效率的重要指標(biāo)
    的頭像 發(fā)表于 11-26 15:14 ?2308次閱讀

    如何優(yōu)化DCS系統(tǒng)性能

    優(yōu)化DCS(分布式控制系統(tǒng)系統(tǒng)性能是確保工業(yè)自動(dòng)化過(guò)程高效、穩(wěn)定運(yùn)行的關(guān)鍵。以下是一些具體的優(yōu)化措施: 一、硬件
    的頭像 發(fā)表于 11-13 09:19 ?1440次閱讀

    如何優(yōu)化SOC芯片性能

    優(yōu)化SOC(System on Chip,系統(tǒng)級(jí)芯片)芯片性能是一個(gè)復(fù)雜而多維的任務(wù),涉及多個(gè)方面的優(yōu)化策略。以下是一些關(guān)鍵的
    的頭像 發(fā)表于 10-31 15:50 ?1730次閱讀

    如何優(yōu)化FPGA設(shè)計(jì)的性能

    優(yōu)化FPGA(現(xiàn)場(chǎng)可編程門陣列)設(shè)計(jì)的性能是一個(gè)復(fù)雜而多維的任務(wù),涉及多個(gè)方面和步驟。以下是一些關(guān)鍵的優(yōu)化策略: 一、明確性能指標(biāo) 確定需求
    的頭像 發(fā)表于 10-25 09:23 ?955次閱讀