不得不服!這台全能SUV居然能甩對手一條街

同時,奧迪Q7後排座椅支持前後移動,以身高178cm的體驗者,調節至最後面的後排實測腿部空間可超過2拳,即便調節最前面的腿部空間仍達1拳有餘。奧迪Q7的後備箱空間也不失所望,高達890L容積滿足一切對空間的想象。兼顧科技實用 配置領先同級在配置上,為了更好地體現兩者的差異性,我們不妨拿奧迪Q7和寶馬X5各自的頂配車型對比,從兩車的表格可以明顯看出,奧迪Q7擁有寶馬X5所不具備的感應後備箱,12。

在整個汽車大環境不太景氣的背景下,唯獨SUV銷量始終保持了逆市上揚的態勢,SUV市場份額從2012年的15%,一路攀升到今年的39%,由此可見,國人是有多喜歡SUV的高視野、高底盤以及大空間。然而,隨着消費需求的不斷升級,SUV同質化情況的加劇,用戶已經不滿足於SUV基礎單一的功能性,更需要兼具豪華感、科技感、駕駛性能等高端產品元素。

這也是為何豪華中大型SUV市場能受到極高的用戶關注度,而在這個級別中,最引人矚目想必是寶馬X5,奧迪Q7這兩款標杆之作。眾所周知,寶馬X5號稱是最強公路SUV,那麼奧迪Q7呢?事實上,奧迪Q7不僅是一款全路況SUV,同時還是一款能越野的SUV。因此,比較這兩台車就多了一番趣味性和參考價值。話不多說,還是趕緊看看寶馬X5和奧迪Q7,誰會更符合消費者的審美眼光呢?

外觀各具特色 奧迪Q7更顯豪華

寶馬X5的外觀風格強調力量感,雙腎前臉和天使眼是我們所熟知的寶馬家族化設計標誌,採用大量犀利有力量感的線條來突顯整車的運動感。而奧迪Q7作為奧迪旗下的旗艦車型,外觀方面自然也不敢馬虎,六邊形大嘴,搭配炫酷的LED 矩陣式大燈,兼具時尚和科技感,造型設計趨向於年輕動感。總的來說,同為德系的兩款豪華SUV都有很高的顏值,但寶馬X5更強調其運動身份,設計風格偏向於張揚高調。而奧迪Q7看上去更顯尊貴豪華,氣宇軒昂。

尺寸方面,長寬高達到5086/1968/1716mm,軸距3001mm,但相比寶馬X5,奧迪Q7在長度和軸距上更具優勢。此外,奧迪Q7還是同級別長度和軸距的領先者,舒適、寬敞不在話下。

內飾風格迥異 奧迪Q7科技完勝

兩車內飾的豪華感和品質感都營造得很出色,做工用料均無可挑剔。寶馬X5這套內飾設計相信大家已經非常熟悉,層次感分明,懸浮式中控大屏配合大量的實體按鍵,延續了寶馬以往的設計理念,但看久之後難免會有些審美疲勞。反觀之下,奧迪Q7的內飾則帶來一種新鮮感,煥然一新的內飾設計,瞬間燃爆用戶最敏銳的視覺神經,最大的驚喜莫過於這塊12.3英寸的全液晶儀錶盤,科技感爆棚,無論操作還是反饋的人性化程度都非常高,着實是令人愛不惜手。

正如上面所提到的,奧迪Q7的長度和軸距都要比寶馬X5優秀,實際表現也確實如此。同時,奧迪Q7後排座椅支持前後移動,以身高178cm的體驗者,調節至最後面的後排實測腿部空間可超過2拳,即便調節最前面的腿部空間仍達1拳有餘。奧迪Q7的後備箱空間也不失所望,高達890L容積滿足一切對空間的想象。

兼顧科技實用 配置領先同級

在配置上,為了更好地體現兩者的差異性,我們不妨拿奧迪Q7和寶馬X5各自的頂配車型對比,從兩車的表格可以明顯看出,奧迪Q7擁有寶馬X5所不具備的感應後備箱,12.3英寸全液晶儀錶盤,自動泊車,LED矩陣式大燈等科技配置。 尤其是在操控輔助方面,奧迪Q7旗艦車型所配備的quattro全時四驅系統、後輪主動轉向系統(選裝),同時全車減重300KG,足以提供奧迪Q7良好的操控性,賦予了奧迪Q7全路況駕駛性能。

更強勁的動力 更經濟的油耗

動力系統方面,兩車的銷量主力均為2.0T車型,寶馬X5和奧迪Q7都採用了2.0T發動機和8速手自一體變速箱的動力總成,寶馬X5的最大功率為180Kw,最大扭矩為350Nm,而奧迪Q7的最大功率為185Kw,最大扭矩為370Nm。從兩車的技術參數可見,奧迪Q7相對寶馬X5略勝一籌。而且,在燃油經濟性上,奧迪Q7 8.5L的官方百公里油耗數據,也確實比寶馬X5更有話語權。

如此全能的奧迪Q7會是你明年的目標?

說完了一大段后,誰更適合你,答案不正明擺着嗎?相比寶馬X5,奧迪Q7在許多方面都擁有過硬的實力,隨便一個產品點就能讓你為之傾心。氣場十足的高顏值外觀、由內到外的科技感和品質感,哦!別忘了,還有奧迪Q7最引以為傲的動力性和操控性。當然,或許你還在為操控還是舒適,實用還是逼格而煩惱,那麼綜合來看,除了全能均衡的奧迪Q7,還能有誰?嗯嗯,看到這之後,已經猜到你為明年購車計劃定好目標了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

聖誕節只有土豪能裝逼?最後眾泰又笑了…

此次就調侃到這裏最後祝大家聖誕節快樂。不說了,要去找小喬了。

以上這三款車都非常值得推薦!

此次就調侃到這裏

最後祝大家聖誕節快樂!

不說了,要去找小喬了!

本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※產品缺大量曝光嗎?你需要的是一流包裝設計!

就算你年薪二十萬,教授也只推薦買這些十多萬的車

對不起,真正為玩車的人是玩手動擋的不喜歡日系車。那高爾夫R-Line也是能讓你爽翻的車重只有1。3噸的R-Line卻有着150ps的馬力搭配換擋奇快的7速雙離合秒殺BBA不是夢當然,也有人買車不是為了玩對於顧家好男人來說空間太小,就算車子會飛也沒有用那這個時候,擁有寬裕後排的B級車就是你的首選什麼。

老K是的一位朋友

他是某保險公司的高級銷售

相當的敬業

所謂一分耕耘一分收穫

兢兢業業的老K現在已經是年薪二十萬的有錢淫

收入比叫獸高好幾個檔次呢

有錢了,逼格肯定要跟上

老K立馬去4S給提了一輛BBA

然而

讓老K意想不到的是

養一輛豪車比他想象的吃力

油耗和保險已經夠厲害了

維修保養就更別說

進去一次4S店

坑你個三五千都是洒洒碎

所以老K很後悔當初沒有聽叫獸的話

買一輛十來萬的車

然而這已經太遲了

為了避免大家重走老K的路

叫獸有必要說說

哪怕你年薪二十萬,買一輛十來萬的車就夠了

十來萬的車子其實也可以很有逼格

雖然太愛面子不是好事

但是一個人開什麼車子的確體現了他的品味

就像老K的BBA,起碼在外人看來不是垃圾

然而你真要高逼格的話,十來萬的車確實也有

記得第一次看見博瑞實車的時候

還以為是哪個品牌的新款進口車

走進一看才發現這是大名鼎鼎的博瑞

連都會產生這種錯覺

那對於不太懂車的人來說

博瑞的逼格就不言而喻了

那假如想要強勁的動力呢?

思域神車就能給到本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

※網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

合資SUV都被國產車逼的跌破10萬了…現在是入手的好時機!

為了打破這種障礙,KX3改款之後變得圓潤了很多。作為改款車型,KX3的外觀造型變化不大。主要把前臉LED日間行車燈的位置往下移了之後,變得更加的協調可愛。圓潤的前臉配合大嘴的設計,很具特色。飽滿的車身線條流暢自然。

合資小型SUV雖然價格相對於自主SUV車型而言,總體的價格真的高了不少。雖則價格高那麼多,但總每一個月的銷量來看合資小型SUV確是不低。歸根到底既是合資SUV的顏值很高,而且它質量相對更加可靠也是很多消費者看重的地方。但消費者買車肯定想着能省一分是一分的,最近看到目前很火的幾款小型SUV市場上的終端優惠不低,不知道你看到之後有沒有想買車的想法呢?

大家都能看到,這幾款車型的起步價大多數在10萬以下,很好地切入合資緊湊SUV的區間。對於定價不算很高,因此1萬多的優惠將相當具有吸引力了。

上汽通用雪佛蘭 創酷

售價:9.99-14.99萬元

作為合資小型SUV的先行者,創酷雖然不算很熱,但銷量一直都不錯,但受限於之前沒有改款,顏值有些落後與對手,使得整體熱度有所降低。如今新款更加出色的造型與較低的售價使得性價比很高。

全新創酷採用家族式造型設計變得更加的年輕時尚,分體格柵的造型使得前臉更具立體感。U型LED日間行車燈造型犀利,視覺效果出眾。飽滿車身造型與立體感很強的車尾造型很出色;輪眉凸起的線條多了一些硬派氣息。

相比老款,新款創酷的內飾實在是太時尚了;環繞式中控台黑棕的配色顯得大膽活潑;高配車型多媒體區域還有7英寸大屏,鋼琴烤漆工藝質感十足。相比它的外觀與內飾變化很大,由於是改款車型,它的空間仍然表現一般,勉強夠用。

創酷全系搭載的是一台1.4T發動機,143ps表現非常出色。日常使用與6AT變速箱搭配良好,換擋邏輯做得不錯。懸挂的韌性相當出色,但卻不會做得太硬影響舒適性。當然缺點還有有點,低速時換擋會有點頓挫。

北京現代 ix25

售價:11.98-18.68萬元

ix25作為現代最小型的SUV車型,它主打的仍是它時尚亮眼的外觀造型,同時還有比同級SUV較大的身材與超過的性價比。而且如今市場上也有不少優惠,屬於比較值得購買的一款SUV車型。

ix25的外觀整體感很出色,雖說第一眼不算很驚艷。但採用流體雕塑感念的前臉還是相當耐看的,多邊形的進氣格柵採用橫向鍍鉻條,質感十足。微微上揚的腰線配合懸浮式車頂造型整體呈現很硬朗陽剛。車尾則設計得比較飽滿敦實,卻不會太過張揚。

ix25的內飾造型則相對平淡一些,規整的中控台採用飛翼式設計,呈現簡潔的氣息。灰黑色的內飾搭配總體比較順眼,總的來說它的比較出色的就是均衡的空間與豐富的配置了。

ix25的動力總成非常豐富,能滿足不同消費者的需求。走量的1.6L車型125ps輸出中規中矩,日常代步足夠了。總的來說2.0L的車型會更好一些;6AT變速箱在平時行駛時換擋也相當出色。更高的1.6T車型動力是不錯,但性價比差了點。總的來說,ix25轉向輕盈,底盤舒適,是一台很好開的小型SUV。

東風悅達起亞 KX3

售價:11.28-17.78萬元

起亞相比現代而已,它們的風格不太相似。起亞的外觀設計更加激進一些,比較符合年輕人,但這樣受眾就比較窄了。為了打破這種障礙,KX3改款之後變得圓潤了很多。

作為改款車型,KX3的外觀造型變化不大。主要把前臉LED日間行車燈的位置往下移了之後,變得更加的協調可愛。圓潤的前臉配合大嘴的設計,很具特色。飽滿的車身線條流暢自然。車尾則採用簡潔的設計,耐看美觀。

全新KX3的內飾變化不大,設計感十足的內飾造型很有特色。層次感很強的中控台採用鋼琴烤漆工藝與紅色裝飾點綴,很吸人眼球。而且它的配置也空間都比較出色,很適合那些對配置比較注重的消費者。

全新KX3隻搭載了1.6L/1.6T發動機,2.0L車型不再推出。1.6L車型動力輸出一般,整體來說畢竟輕盈;對於日常代步的消費者已經足夠了。1.6T發動機動力更直接一些,而且這套雙離合變速箱做得相當出色,能媲美大眾的雙離合了。偏沉的懸挂需要適應一下,懸挂的調校則以家用舒適為目的。

上汽通用別克 昂科拉

售價:13.99-18.99萬元

昂科拉也迎來了改款,作為跟創酷差不多時間進入國內的小型SUV車型。當時市場空白,它可謂是佔了先機,但由於改款腳步較慢,性價比一般使得它銷量降低了不少。

新款昂科拉的外觀造型終於跟上潮流的,變得更加時尚年輕的造型很吸引年輕人。全新的家族式造型設計與別克其他車型有了延續,飽滿的前臉與鍍鉻裝飾檔次感十足。微微隆起的輪轂線條也使得它多了一些野性。車尾採用了新式樣的尾燈,視覺效果更出色。

作為比創酷高一個檔次的車型,昂科拉的內飾質感很強。簡潔的內飾造型是別克最新的家族式造型設計,很時尚年輕。而且厚道的用料與出色的做工也在細節上一一體現,唯一的弱點就是後排空間比較一般。

新款昂科拉也是全系搭載的是一台1.4T發動機,動力與創酷相差不大。而且也沒有採用雙離合變速箱,6AT保證了日常使用的平順性。1.4T動力出色,起步相當輕鬆。懸挂支撐性很高,具有較高的穩定性。但較硬的懸挂帶來的不好的影響就是舒適性一般,而且高速噪音偏高。

作為時下最火熱的幾款小型SUV車型,今天所推薦的這幾款價格有高有低。創酷自從改款之後,外觀造型終於不落後於競爭對手了;而且不到10萬的價格如今再加上優惠性價比很出眾。ix25則是一款實力比較均衡的車型,流體雕塑感念造型很出色;豐富的配置與寬敞的空間也是一大亮點。

起亞的KX3與ix25是兩種不同的路,它更加講求個性;老款太激進使得受眾較窄;新款變得笑容可掬與消費者更親近了。而且它降低的售價與豐富的配置也是它吸引年輕人的優勢。最後的昂科拉,雖則價格相對高一些,但它出色的外觀造型與檔次感很高的內飾質感都是它的底氣,如今加上優惠就更值得入手了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

這些事情SUV可以輕鬆做到,轎車完全不行

、所說SUV有着以上優點,但是上下車對老人不友好以及盲區大、油耗大這些缺點還是存在的,關鍵還是根據自身需求出發選擇SUV還是轎車。

、

所說SUV有着以上優點,但是上下車對老人不友好以及盲區大、油耗大這些缺點還是存在的,關鍵還是根據自身需求出發選擇SUV還是轎車。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

Redis 持久化

RDB

簡介

RDB持久化方式是通過快照(snapshotting)完成的,當符合一定條件時,redis會自動將內存中所有數據以二進制方式生成一份副本並存儲在硬盤上。當redis重啟時,並且AOF持久化未開啟時,redis會讀取RDB持久化生成的二進制文件(默認名稱dump.rdb,可通過設置dbfilename修改)進行數據恢復,對於持久化信息可以用過命令“info Persistence”查看。

save

該命令會由worker thread 執行,因此會阻塞 redis 的 worker ,期間不會響應任何其他客戶端發來的請求,直到RDB快照文件執行完畢,所以慎用。

測試

info persistence 
# rdb_last_save_time:1570126868
save
info persistence
# rdb_last_save_time:1570126928

bgsave

bgsave“後台保存”。與save 最大的差異為它並不是由 worker thread 執行的,而是由redis fork 齣子進程,交由子進程來完成持久化操作。

redis在fork子進程這個時間段內 redis是阻塞的(此段時間不會響應客戶端請求),當子進程創建完成以後redis才會響應客戶端請求。

並且redis不會在控制台显示完成信息,只會寫入日誌。

流程

客戶端執行bgsave命令,redis主進程收到指令並判斷此時是否在執行bgrewriteaof(AOF文件重新過程,後續會講解),如果此時正好在執行則bgsave直接返回,不fork子進程,如果沒有執行bgrewriteaof重寫AOF文件,則進入下一個階段;

主進程調用fork方法創建子進程,在創建子進程過程中redis主進程阻塞,所以不能響應客戶端請求;

子進程創建完成以後,bgsave命令返回Background saving started,此時標志著redis可以響應客戶端請求了;

子經常根據主進程的內存副本創建臨時快照文件,當快照文件完成以後對原快照文件進行替換;

子進程發送信號給redis主進程完成快照操作,主進程更新統計信息(info Persistence可查看),子進程退出;

測試

bgsave
Background saving started  # 子進程創建成功,它會去 完成持久化操作

查看日誌

config get logfile
 11) "logfile"
 12) "/var/log/redis/redis-server.log"
cat "/var/log/redis/redis-server.log"
2497:M 04 Oct 2019 10:26:46.764 * Background saving started by pid 28960   # 開始後台 持久化
2497:M 04 Oct 2020 10:26:46.772 * Background saving terminated with success   # 後台持久化完成 

RDB相關配置

查看配置的方法:

1 查看配置文件

2 在redis 中查看當前 redis 的配置

CONFIG get *    # 獲取所有的配置
CONFIG get dir   # 獲取 快照文件 保存的 位置
CONFIG get dbfilename   # 獲取 快照文件 的文件名

快照文件位置

配置文件中 dir 指定

快照文件名

配置文件中 dbfilename

是否壓縮

配置文件中 rdbcompression default:yes

設置存儲至本地數據庫時是否壓縮數據,默認為yes,採用LZF壓縮

yes 會耗費一定的CPU資源,默認是 yes 。

no 會使存儲的文件變大(巨大)

是否校驗

配置文件中 yesrdbchecksumy default: yes

yes 會消耗一部分CPU資源,但是數據相對安全不容易損壞

no 可以節約讀寫性過程約10%時間消耗,但是存儲一定的數據損壞風險

後台持久化報錯

配置文件中 stop-writes-on-bgsave-error default: yes

後台存儲過程中如果出現錯誤線程,是否停止保存操作,默認是停止的

no 則忽略錯誤繼續。

快照檢查

配置文件中 rdbchecksum default: yes
在寫入文件和讀取文件時是否開啟rdb文件檢查,檢查是否有無損壞,如果在啟動是檢查發現損壞,則停止啟動。

RDB觸發

自動觸發

配置文件中 save default: yes

save second changes
# 查看默認的配置
config get save
"900 1 300 10 60 10000"
# 900秒內 1次鍵更新了就觸發持久化 或 300秒內 10次更新 持久化 60 秒內 10000次更新 觸發持久化
# 該持久化是 bgsave

手動觸發

手動執行 save 或 bgsave

其他觸發

以下幾種種情況下會觸發執行快照操作,並且默認的使用bgsave

主從複製時,從庫全量複製同步主庫數據,此時主庫會執行bgsave命令進行快照;

客戶端執行數據庫清空命令FLUSHALL時候,觸發快照;

客戶端執行shutdown關閉redis時,觸發快照,也可以 使用 nosave 參數顯式聲明不保存快照

shutdown nosave

故障恢復

當redis意外崩潰或者關閉再次啟動時,此時AOF持久化未開啟時(默認未開啟),將使用RDB快照文件恢複數據。

# 查看日誌
cat /var/log/redis/redis-server.log
30448:M 04 Oct 2019 10:09:37.145 # Server initialized
30448:M 04 Oct 2019 10:09:37.145 * DB loaded from disk: 0.000 seconds   # 從快照恢復
30448:M 04 Oct 2019 10:09:37.146 * Ready to accept connections

缺點

RDB方式無論是執行指令還是利用配置,無法做到實時持久化,具體較大的可能性丟失數據

bgsave指令每次運行要執行fork操作創建子進程,要犧牲掉一些性能

Redis的眾多版本中未進行RDB文件格式的版本統一,有可能出現個版本服務之間數據格式無法兼容現象

存儲數據量較大,效率較低——基於快照思想,每次讀寫都是全部數據,當數據量巨大時,效率非常低
大數據量下的IO性能較低

AOF

簡介

日誌形式的AOF,將命令追加到文件。AOF可以將Redis執行的每一條寫命令追加到磁盤文件(appendonly.aof)中,在redis啟動時候優先選擇從AOF文件恢複數據。因為要頻繁的將每一個操作記錄到文件中,所以開啟AOF持久化會對性能有一定的影響,但是大部分情況下這個影響是可以接受的。

與RDB持久化相比,AOF持久化數據丟失更少,其消耗內存更少(RDB方式執行bgsve會有內存拷貝)。

AOF持久化過程

redisAOF持久化過程可分為以下階段:

追加寫入

redis將每一條寫命令以redis通訊協議添加至緩衝區aof_buf,這樣的好處在於在大量寫請求情況下,採用緩衝區暫存一部分命令隨後根據策略一次性寫入磁盤,這樣可以減少磁盤的I/O次數,提高性能。

同步三種策略

當寫命令寫入aof_buf緩衝區后,redis會將緩衝區的命令寫入到文件,redis提供了三種同步策略,由配置參數appendfsync決定,下面是每個策略所對應的含義:

no

不使用fsync方法同步,而是交給操作系統write函數去執行同步操作,在linux操作系統中大約每30秒執行一次 sync(man 2 sync 查看)。這種情況下,緩衝區數據同步不可控,並且在大量的寫操作下,aof_buf緩衝區會堆積會越來越嚴重,一旦redis出現故障,數據丟失嚴重,整體不可控。

always

表示每次有寫操作都調用fsync方法強制內核將數據寫入到aof文件。這種情況下由於每次寫命令都寫到了文件中, 雖然數據比較安全,但是因為每次寫操作都會同步到AOF文件中,所以在性能上會有影響,同時由於頻繁的IO操作,硬盤的使用壽命會降低。

everysec

數據將使用調用操作系統write寫入文件,並使用fsync每秒一次從內核刷新到磁盤。 這是折中的方案,兼顧性能和數據安全,所以redis默認推薦使用該配置。

文件重寫

當開啟AOF時,隨着時間推移,AOF文件會越來越大,redis提出一種重寫的策略來緩解數據存儲和恢復壓力。

重寫策略

重複或無效的命令不寫入文件

過期的數據不再寫入文件

多條命令合併寫入(當多個命令能合併一條命令時候會對其優化合併作為一個命令寫入,例如“RPUSH list1 a RPUSH list1 b” 合併為“RPUSH list1 a b” )

觸發條件

AOF文件觸發條件可分為手動觸發和自動觸發:

手動觸發:客戶端執行bgrewriteaof命令。

自動觸發:自動觸發通過以下兩個配置協作生效:

auto-aof-rewrite-min-size

AOF文件最小重寫大小,只有當AOF文件大小大於該值時候才可能重寫,4.0默認配置64mb。

auto-aof-rewrite-percentage

當前AOF文件大小和最後一次重寫后的大小之間的比率等於或者等於指定的增長百分比,如100代表當前AOF文件是上次重寫的兩倍時候才重寫。 

redis在AOF功能開啟的情況下,會維持以下三個變量

aof_current_size :記錄當前AOF文件大小

aof_rewrite_base_size:記錄最後一次AOF重寫之後,AOF文件大小

aof_rewrite_perc:增長百分比變量

每次當serverCron(服務器周期性操作函數)函數執行時,它會檢查以下條件是否全部滿足,如果全部滿足的話,就觸發自動的AOF重寫操作:

沒有BGSAVE命令(RDB持久化)/AOF持久化在執行;

沒有BGREWRITEAOF在進行;

當前AOF文件大小要大於server.aof_rewrite_min_size的值;

當前AOF文件大小和最後一次重寫后的大小之間的比率等於或者大於指定的增長百分比(auto-aof-rewrite-percentage參數)

重寫過程

  AOF文件重寫過程與RDB快照bgsave工作過程有點相似,都是通過fork子進程,由子進程完成相應的操作,同樣的在fork子進程簡短的時間內,redis是阻塞的,以下圖文說明其重寫過程:

開始bgrewriteaof,判斷當前有沒有bgsave命令(RDB持久化)/bgrewriteaof在執行,倘若有,則這些命令執行完成以後在執行。

主進程fork齣子進程,在這一個短暫的時間內,redis是阻塞的。

當完成重寫后,將重寫后的aof 文件合併到原有aof緩存文件中。並刪除臨時創建的重寫后的aof文件

AOF實現本質

AOF實現本質是基於redis通訊協議,將命令以純文本的方式寫入到文件中。

redis協議:

首先Redis是以行來劃分,每行以\r\n行結束。每一行都有一個消息頭,消息頭共分為5種分別如下:

(+) 表示一個正確的狀態信息,具體信息是當前行+後面的字符。

(-) 表示一個錯誤信息,具體信息是當前行-後面的字符。

(*) 表示消息體總共有多少行,不包括當前行,*後面是具體的行數。

($) 表示下一行數據長度,不包括換行符長度\r\n,$後面則是對應的長度的數據。

(:) 表示返回一個數值,:後面是相應的数字節符。

我們可以直接查看AOF文件中的格式,如下圖:

數據恢復

之前已經提到當AOF開啟時候,redis數據恢復優先選用AOF進行數據恢復,以下使用停止redis來模擬redis故障,然後在重寫啟動進行恢復。

AOF配置參數

auto-aof-rewrite-min-size

AOF文件最小重寫大小,只有當AOF文件大小大於該值時候才可能重寫,4.0默認配置64mb。

auto-aof-rewrite-percentage

當前AOF文件大小和最後一次重寫后的大小之間的比率等於或者等於指定的增長百分比,如100代表當前AOF文件是上次重寫的兩倍時候才重寫。默認 100

appendfsync

上文中提到的三種策略,默認 everysec

always | everysec | no

aof-load-truncated yes

當redis突然運行崩潰時,會出現aof文件被截斷的情況,Redis可以在發生這種情況時退出並加載錯誤,以下選項控制此行為。
如果aof-load-truncated設置為yes,則加載截斷的AOF文件,Redis服務器啟動發出日誌以通知用戶該事件。
如果該選項設置為no,則服務將中止並显示錯誤並停止啟動。當該選項設置為no時,用戶需要在重啟之前使用redis-check-aof實用程序修復AOF文件在進行啟動。

appendonly no

yes開啟AOF,no關閉AOF 默認 no

appendfilename

指定AOF文件名,4.0無法通過config set 設置,只能通過修改配置文件設置。默認 appendonly.aof

RDB-AOF混合持久化

簡介

redis4.0添加了新的持久化方式混合持久化。

混合持久化默認是關閉的,它採用一種rdb + aof 的方式來實現,文件頭rdb 一個全量的快照,格式為二進制,後面是 aof 格式。這樣恢復會先查看開頭是否為REDIS,rdb開頭必然是REDIS。然後從快照恢復,之後從aof恢復。

這樣做的好處是可以結合 rdb 和 aof 的優點, 快速加載同時避免丟失過多的數據

缺點是 aof 裏面的 rdb 部分就是壓縮格式不再是 aof 格式,可讀性差。

開啟混合持久化

5 版本的redis 默認的開啟了aof-use-rdb-preamble 但是 appendonly 默認是 no 也就是說,只要開啟了aof 默認的會使用混合持久化。如果要使用單純的aof 則需要手動的 將 aof-use-rdb-preamble 設為 no (好坑)

4.0版本的混合持久化默認關閉的,通過aof-use-rdb-preamble配置參數控制,yes則表示開啟,no表示禁用,默認是禁用的,可通過config set修改。

混合持久化過程

創建子進程

生成全量的rdb快照,放在appendonly.aof 文件頭部。然後,接下來將aof緩衝區的增量命令以aof方式寫入文件尾

每次恢複數據時,首先從RDB部分恢復,然後執行aof。

數據恢復

開啟了混合持久化時,啟動redis依然優先加載aof文件,aof文件加載可能有兩種情況如下:

aof文件開頭是rdb的格式, 先加載 rdb內容再加載剩餘的 aof。

aof文件開頭不是rdb的格式,直接以aof格式加載整個文件。

三種持久化方案對比

RDB

優點

RDB 是一個非常緊湊(compact)的文件,體積小,傳輸速度快,適合災備。

RDB 可以最大化 Redis 的性能:父進程在保存 RDB 文件時唯一要做的就是 fork 出一個子進程,然後這個子進程就會處理接下來的所有保存工作,父進程無須執行任何磁盤 I/O 操作。

RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快很多。

缺點

RDB 丟數據將會丟掉兩次持久化之間的所有數據~

當redis中數據集比較大時候,RDB由於RDB方式需要對數據進行完成拷貝並生成快照文件,fork的子進程會耗CPU,並且數據越大,RDB快照生成會越耗時。

RDB文件是特定的格式,閱讀性差,由於格式固定,並且多版本之間存在不兼容情況。很難受~

AOF

優點

數據更完整,秒級數據丟失(取決於設置fsync策略)

兼容性較高,由於是基於redis通訊協議而形成的命令追加方式,無論何種版本的redis都兼容,再者aof文件是明文的,可閱讀性較好。

缺點

數據文件體積較大,即使有重寫機制,但是在相同的數據集情況下,AOF文件通常比RDB文件大。

相對RDB方式,AOF速度慢於RDB,並且在數據量大時候,恢復速度AOF速度也是慢於RDB。

由於頻繁地將命令同步到文件中,AOF持久化對性能的影響相對RDB較大,但是對於我們來說是可以接受的。

混合持久化

優點

混合持久化結合了RDB持久化 和 AOF 持久化的優點, 由於絕大部分都是RDB格式,加載速度快,同時結合AOF,增量的數據以AOF方式保存了,數據更少的丟失。

缺點

由於前部分是RDB格式,閱讀性較差,兼容性差,一旦開啟了混合持久化,4.0 版本之前的redis 都不認識這種aof文件~ 。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

現在的編程和二十年前有什麼不同?

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

Hive中row_number()、dense_rank()、rank()的區別

摘要

本文對Hive中常用的三個排序函數row_number()、dense_rank()、rank()的特性進行類比和總結,並通過筆者親自動手寫的一個小實驗,直觀展現這三個函數的特點。

三個排序函數的共同點與區別

函數 共同點 不同點
row_number() 用於特定場景下實現排序需求;
均從1開始排序
無重複排名(相同排名的按序排名)
dense_rank() 有相同排名,但不會跳過佔用的排名
rank() 有相同排名,但會跳過佔用的排名

實驗示例

set mapreduce.job.queuename=QueueA;

use STUDENT_DB;

--創建學生分數表
DROP TABLE IF EXISTS STUDENT_DB.SCORE_TABLE1;
CREATE TABLE IF NOT EXISTS STUDENT_DB.SCORE_TABLE1
(
    ID          STRING COMMENT '唯一ID',
    NAME        STRING COMMENT '姓名',
    SCORE       INT    COMMENT '分數',
    CLASS_NUM   STRING COMMENT '班級編號'
)
COMMENT '學生分數表'
PARTITIONED BY (pt_dt STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\27'
STORED AS ORCFILE;

--向學生分數表插入數據
INSERT OVERWRITE TABLE STUDENT_DB.SCORE_TABLE1 PARTITION(pt_dt='2019-12-12') VALUES
('1', '小明', 89, '1班'),
('2', '小紅', 90, '1班'),
('3', '小軍', 90, '1班'),
('4', '小胖', 91, '1班'),
('5', '小李', 87, '1班'),
('6', '小郭', 99, '1班');

--創建學生分數排序結果表
DROP TABLE IF EXISTS STUDENT_DB.SCORE_RANK_TABLE1;
CREATE TABLE IF NOT EXISTS STUDENT_DB.SCORE_RANK_TABLE1
(
    ID          STRING COMMENT '唯一ID',
    NAME        STRING COMMENT '姓名',
    SCORE       INT    COMMENT '分數',
    CLASS_NUM   STRING COMMENT '班級編號',
    ROW_NUMBERS STRING COMMENT 'ROW_NUMBER排序結果',
    DENSE_RANKS STRING COMMENT 'DENSE_RANKS排序結果',
    RANKS       STRING COMMENT 'RANKS排序結果'
)
COMMENT '學生分數排序結果表'
PARTITIONED BY (pt_dt STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\27'
STORED AS ORCFILE;

INSERT OVERWRITE TABLE STUDENT_DB.SCORE_RANK_TABLE1 PARTITION(pt_dt='2019-12-12')
SELECT ID,
       NAME,
       SCORE,
       CLASS_NUM,
       ROW_NUMBER() OVER(PARTITION BY CLASS_NUM ORDER BY SCORE DESC) AS ROW_NUMBERS,
       DENSE_RANK() OVER(PARTITION BY CLASS_NUM ORDER BY SCORE DESC) AS DENSE_RANKS,
       RANK() OVER(PARTITION BY CLASS_NUM ORDER BY SCORE DESC) AS RANKS
FROM STUDENT_DB.SCORE_RANK_TABLE1
WHERE pt_dt='2019-12-12';

SELECT ID,
       NAME,
       SCORE,
       CLASS_NUM,
       ROW_NUMBERS,
       DENSE_RANKS,
       RANKS,
       pt_dt
FROM STUDENT_DB.SCORE_RANK_TABLE1
WHERE pt_dt='2019-12-12';

實驗結果

SCORE_RANK_TABLE1

ID NAME SCORE CLASS_NUM ROW_NUMBERS DENSE_RANKS RANKS pt_dtpt_dt
6 小郭 99 1班 1 1 1 2019-12-12
4 小胖 91 1班 2 2 2 2019-12-12
3 小軍 90 1班 3 3 3 2019-12-12
2 小紅 90 1班 4 3 3 2019-12-12
1 小明 89 1班 5 4 5 2019-12-12
5 小李 87 1班 6 5 6 2019-12-12

如上表所示,1班的小軍和小紅分數均為90,當我們使用ROW_NUMBERS()進行排序時,他們的排名不會並列,而是分別有一個排名。

當我們使用DENSE_RANK()進行排序時,他們的排名會並列,且後續記錄的排名會以當前並列排名為基礎+1,即不會跳過被佔用的位置。

當我們使用RANK()進行排名時,他們的排名會並列,且後續記錄的排名會跳過被佔用的排名數,而不會順延下去。

總結

在實際開發過程中,可根據場景的需要去選擇具體的排序函數。一個較為常見的場景是根據某個字段partition by之後在該範圍內order by進行排序,然後取首條記錄,這時候row_number()基本可以滿足需求。

除此之外,排序函數均較耗性能,特別是如果對大數據量進行全局排序時,一定要考慮性能問題,非必要情況下,避免對大數據量進行全局排序。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

@Autowired 註解詳解

前言

我們平時使用 Spring 時,想要 依賴注入 時使用最多的是 @Autowired 註解了,本文主要講解 Spring 是如何處理該註解並實現 依賴注入 的功能的。

正文

首先我們看一個測試用例:

User 實體類:

public class User {

    private Long id;
    private String name;

	// 省略 get 和 set 方法
}

測試類:

public class AnnotationDependencyInjectTest {

    /**
     * @Autowired 字段注入
     */
    @Autowired
    private User user;

    private City city;

    /**
     * @Autowired 方法注入
     */
    @Autowired
    public void initCity(City city) {
        this.city = city;
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(AnnotationDependencyInjectTest.class);
        context.refresh();
        AnnotationDependencyInjectTest bean = context.getBean(AnnotationDependencyInjectTest.class);
        // @Autowired 字段注入
        System.out.println(bean.user);
        // @Autowired 方法注入
        System.out.println(bean.city);
        UserHolder userHolder = context.getBean(UserHolder.class);
        // @Autowired 構造器注入
        System.out.println(userHolder.getUser());
        context.close();
    }

    @Bean
    public User user() {
        User user = new User();
        user.setId(1L);
        user.setName("leisurexi");
        return user;
    }

    @Bean
    public City city() {
        City city = new City();
        city.setId(1L);
        city.setName("北京");
        return city;
    }
    
    /**
     * @Autowired 構造函數注入
     */
    static class UserHolder {

        private User user;

        @Autowired
        public UserHolder(User user) {
            this.user = user;
        }

        public User getUser() {
            return user;
        }

        public void setUser(User user) {
            this.user = user;
        }
    }

}

上面分別展示了 @Autowired 註解的字段注入和方法注入,下面我們開始分析 Spring 是如何實現的。

首先使 @Autowired 註解生效的一個關鍵類是 AutowiredAnnotationBeanPostProcessor,該類實現了 InstantiationAwareBeanPostProcessorAdapter 抽象類;該抽象類就是一個適配器的作用提供了接口方法的默認實現,InstantiationAwareBeanPostProcessorAdapter 又實現了 SmartInstantiationAwareBeanPostProcessor 接口,同時實現該接口的 determineCandidateConstructors() 方法可以指定 bean 的候選構造函數;然後 SmartInstantiationAwareBeanPostProcessor 接口又繼承了 InstantiationAwareBeanPostProcessor 接口,該接口提供了 bean 實例化前後的生命周期回調以及屬性賦值前的後置處理方法,@Autowired 註解的屬性注入就是通過重寫該接口的 postProcessProperties() 實現的。這兩個接口都在 在 Spring IoC createBean 方法詳解 一文中有介紹過。下面我們看一下 AutowiredAnnotationBeanProcessor 的繼承關係圖:

關於 AutowiredAnnotationBeanPostProcessor 這個後置處理器是怎麼加入到 beanFactory 中的,我們在 Spring IoC component-scan 節點詳解 一文中介紹過主要是通過 AnnotationConfigUtils#registerAnnotationConfigProcessors() 實現的。

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
    BeanDefinitionRegistry registry, @Nullable Object source) {
    
    // 忽略其它代碼
    
    // 註冊用於處理@Autowired、@Value、@Inject註解的後置處理器
    if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new
            RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def,
                                           AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
    }
    // 忽略其它代碼
    
}

屬性和方法注入

AutowiredAnnotationBeanPostProcessor 中跟屬性注入有關的方法有兩個:postProcessMergedBeanDefinition 和 postProcessPropertyValues。

前者是 MergedBeanDefinitionPostProcessor 接口中的方法,定義如下:

public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor {

	/**
	 * 對指定bean的BeanDefinition合併后的處理方法回調
	 */
	void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);

	/**
	 * @since 5.1
	 * 通知已重新設置指定beanName的BeanDefinition,如果實現該方法應該清除受影響的bean的所有元數據
	 */
	default void resetBeanDefinition(String beanName) {
	}

}

後者是 InstantiationAwareBeanPostProcessor 接口中的方法,定義如下:

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {

	/**
	 * Bean 實例化前調用,返回非 {@code null} IoC 容器不會對 Bean 進行實例化 並且後續的生命周期回調方	  *	法不會調用,返回 {@code null} 則進行 IoC 容器對 Bean 的實例化
	 */
	@Nullable
	default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
		return null;
	}

	/**
	 * Bean 實例化之後,屬性填充之前調用,返回 {@code true} 則進行默認的屬性填充步驟,返回 {@code 		 * false} 會跳過屬性填充階段。
	 */
	default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
		return true;
	}

	/**
	 * Bean 實例化后屬性賦值前調用,PropertyValues 是已經封裝好的設置的屬性值,返回 {@code null} 繼續
	 * 使用現有屬性,否則會替換 PropertyValues。
	 * @since 5.1版本新加的和底下的方法一樣
	 */
	@Nullable
	default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
			throws BeansException {
		return null;
	}

	/**
	 * 跟上面方法一樣的功能,只不過是5.1以前版本所使用的
	 * 返回 {@code null} 會跳過屬性填充階段
	 */
	@Deprecated
	@Nullable
	default PropertyValues postProcessPropertyValues(
			PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {

		return pvs;
	}

}

關於這兩個方法的調用時機,可以查看 Spring IoC createBean 方法詳解。

AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition

首先執行的是 postProcessMergedBeanDefinition() 。

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    // 尋找需要注入的字段或方法,並封裝成 InjectionMetadata,見下文詳解
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    // 檢查元數據中的註解信息
    metadata.checkConfigMembers(beanDefinition);
}

InjectionMetadata 就是注入的元信息描述,主要字段如下:

public class InjectionMetadata {
    
    // 需要依賴注入的目標類
    private final Class<?> targetClass;
    // 注入元素的集合
    private final Collection<InjectedElement> injectedElements;
    
    // 忽略其它代碼
    
}

InjectedElement 就是注入的元素,主要字段如下:

public abstract static class InjectedElement {
    
    // 注入的屬性或方法
    protected final Member member;
    // 需要注入的是否是字段
    protected final boolean isField;
    
}

AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata

private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
    // Fall back to class name as cache key, for backwards compatibility with custom callers.
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    // Quick check on the concurrent map first, with minimal locking.
    // 首先從緩存中獲取
    InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
    // 判斷是否需要刷新,即metadata為null或者metadata中存儲的targetClass和當前clazz不等
    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
        // 這裏相當於是一個double check,防止多線程出現的併發問題
        synchronized (this.injectionMetadataCache) {
            metadata = this.injectionMetadataCache.get(cacheKey);
            if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                if (metadata != null) {
                    metadata.clear(pvs);
                }
                // 構建注入元信息,見下文詳解
                metadata = buildAutowiringMetadata(clazz);
                // 放入緩存中
                this.injectionMetadataCache.put(cacheKey, metadata);
            }
        }
    }
    // 返回注入元信息
    return metadata;
}

AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
        return InjectionMetadata.EMPTY;
    }
    // 判斷當前類或其字段或其方法是否標註了autowiredAnnotationTypes中的註解,沒有的話直接返回空的
    List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
    Class<?> targetClass = clazz;

    do {
        final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
        // 遍歷targetClass中的字段
        ReflectionUtils.doWithLocalFields(targetClass, field -> {
            // 獲取field上的@Autowired註解信息
            MergedAnnotation<?> ann = findAutowiredAnnotation(field);
            if (ann != null) {
                // 如果字段是靜態類型是不會進行注入的
                if (Modifier.isStatic(field.getModifiers())) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation is not supported on static fields: " + field);
                    }
                    return;
                }
                // 獲取@Autowired註解中的required屬性
                boolean required = determineRequiredStatus(ann);
                // 將裝成AutowiredFieldElement添加進currElements
                currElements.add(new AutowiredFieldElement(field, required));
            }
        });
        // 遍歷targetClass中的方法
        ReflectionUtils.doWithLocalMethods(targetClass, method -> {
            // 找到橋接方法
            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
            // 判斷方法的可見性,如果不可見則直接返回
            if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                return;
            }
            // 獲取method上的@Autowired註解信息
            MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
            if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                // 如果是靜態方法是不會進行注入的
                if (Modifier.isStatic(method.getModifiers())) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation is not supported on static methods: " + method);
                    }
                    return;
                }
                // 方法注入沒有參數就違背了初衷,就是在脫褲子放屁
                if (method.getParameterCount() == 0) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation should only be used on methods with parameters: " + method);
                    }
                }
                // 獲取@Autowired註解中的required屬性
                boolean required = determineRequiredStatus(ann);
                // 將方法和目標類型封裝成屬性描述符
                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                // 封裝成AutowiredMethodElement添加進currElements
                currElements.add(new AutowiredMethodElement(method, required, pd));
            }
        });
        // 將currElements整個添加進elements
        elements.addAll(0, currElements);
        // 獲取targetClass的父類,進行下一次循環
        targetClass = targetClass.getSuperclass();
    }
    // 當targetClass為空或者targetClass等於Object.class時會退出循環
    while (targetClass != null && targetClass != Object.class);
    // 將elements和clazz封裝成InjectionMetadata返回
    return InjectionMetadata.forElements(elements, clazz);
}

上面代碼中的 findAutowiredAnnotation() 就是在遍歷 autowiredAnnotationTypes 屬性,看字段或者方法上的註解是否存在於 autowiredAnnotationTypes 中,或者其派生註解,找到第一個就返回,不會再繼續遍歷了。

public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
		implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
    
    private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4);
    
    public AutowiredAnnotationBeanPostProcessor() {
        this.autowiredAnnotationTypes.add(Autowired.class);
        this.autowiredAnnotationTypes.add(Value.class);
        try {
            this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
                                              ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
            logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
        }
        catch (ClassNotFoundException ex) {
            // JSR-330 API not available - simply skip.
        }
	}
    
}

從 AutowiredAnnotationBeanPostProcessor 類的構造函數中,我們可以發現 autowiredAnnotationTypes 默認添加了 @Autowired、@Value 以及 @Inject (在 JSR-330 的jar包存在於當前環境時)。

至此,使用 @Autowired 修飾的字段和方法已經封裝成 InjectionMetadata 並放在 injectionMetadataCache 緩存中,便於後續使用。

AutowireAnnotationBeanPostProcessor#postProcessProperties

postProcessMergedBeanDefinition() 調用后 bean 就會進行實例化接着調用 postProcessProperties() 。

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    // 獲取緩存中的 InjectionMetadata
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        // 進行屬性的注入
        metadata.inject(bean, beanName, pvs);
    }
    catch (BeanCreationException ex) {
        throw ex;
    }
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
    // 返回注入的屬性
    return pvs;
}

// InjectMetadata.java
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // 獲取檢查后的元素
    Collection<InjectedElement> checkedElements = this.checkedElements;
    // 如果checkedElements不為空就使用checkedElements,否則使用injectedElements
    Collection<InjectedElement> elementsToIterate =
        (checkedElements != null ? checkedElements : this.injectedElements);
    if (!elementsToIterate.isEmpty()) {
        // 遍歷elementsToIterate
        for (InjectedElement element : elementsToIterate) {
            if (logger.isTraceEnabled()) {
                logger.trace("Processing injected element of bean '" + beanName + "': " + element);
            }
            // AutowiredFieldElement、AutowiredMethodElement這兩個類繼承InjectionMetadata.InjectedElement,各自重寫了inject方法
            element.inject(target, beanName, pvs);
        }
    }
}

AutowiredFieldElement#inject

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // 強轉成Field類型
    Field field = (Field) this.member;
    Object value;
    if (this.cached) {
        // 如果緩存過,直接使用緩存的值,一般第一次注入都是false
        value = resolvedCachedArgument(beanName, this.cachedFieldValue);
    }
    else {
        // 構建依賴描述符
        DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
        desc.setContainingClass(bean.getClass());
        Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
        Assert.state(beanFactory != null, "No BeanFactory available");
        // 獲取類型轉換器
        TypeConverter typeConverter = beanFactory.getTypeConverter();
        try {
            // 進行依賴解決,獲取符合條件的bean
            value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
        }
        catch (BeansException ex) {
            throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
        }
        // 加鎖
        synchronized (this) {
            // 如果沒有被緩存
            if (!this.cached) {
                // 找到了需要的bean || 該字段是必要的
                if (value != null || this.required) {
                    // 將依賴描述符賦值給cachedFieldValue
                    this.cachedFieldValue = desc;
                    // 註冊bean的依賴關係,用於檢測是否循環依賴
                    registerDependentBeans(beanName, autowiredBeanNames);
                    // 如果符合條件的bean只有一個
                    if (autowiredBeanNames.size() == 1) {
                        String autowiredBeanName = autowiredBeanNames.iterator().next();
                        // beanFactory含有名為autowiredBeanName的bean && 類型是匹配的
                        if (beanFactory.containsBean(autowiredBeanName) &&
                            beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
                            // 將該屬性解析到的bean的信息封裝成ShortcutDependencyDescriptor
                            // 之後可以通過調用resolveShortcut()來間接調beanFactory.getBean()快速獲取bean
                            this.cachedFieldValue = new ShortcutDependencyDescriptor(
                                desc, autowiredBeanName, field.getType());
                        }
                    }
                }
                else {
                    this.cachedFieldValue = null;
                }
                // 緩存標識設置為true
                this.cached = true;
            }
        }
    }
    // 如果找到了符合的bean,設置字段可訪問,利用反射設置值
    if (value != null) {
        ReflectionUtils.makeAccessible(field);
        field.set(bean, value);
    }
}

上面代碼中的 beanFactory.resolveDependency() 在 Spring IoC createBean 方法詳解 一文中有介紹過,這裏不再贅述;同樣 registerDependentBeans() 最終會調用 DefaultSingletonBeanRegistry.registerDependentBean() ,該方法在 Spring IoC getBean 方法詳解 一文中有介紹過,這裏也不再贅述。

AutowiredMethodElement#inject

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // 檢查是否需要跳過
    if (checkPropertySkipping(pvs)) {
        return;
    }
    // 強轉成Method類型
    Method method = (Method) this.member;
    Object[] arguments;
    if (this.cached) {
        // Shortcut for avoiding synchronization...
        // 如果緩存過,直接調用beanFactory.resolveDependency()返回符合的bean
        arguments = resolveCachedArguments(beanName);
    }
    else {
        // 獲取參數數量
        int argumentCount = method.getParameterCount();
        arguments = new Object[argumentCount];
        // 創建依賴描述符數組
        DependencyDescriptor[] descriptors = new DependencyDescriptor[argumentCount];
        // 記錄用於自動注入bean的名稱集合
        Set<String> autowiredBeans = new LinkedHashSet<>(argumentCount);
        Assert.state(beanFactory != null, "No BeanFactory available");
        // 獲取類型轉換器
        TypeConverter typeConverter = beanFactory.getTypeConverter();
        // 遍歷參數
        for (int i = 0; i < arguments.length; i++) {
            // 將方法和參數的下標構建成MethodParameter,這裏面主要記錄了參數的下標和類型
            MethodParameter methodParam = new MethodParameter(method, i);
            // 將MethodParameter構建成DependencyDescriptor
            DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
            currDesc.setContainingClass(bean.getClass());
            descriptors[i] = currDesc;
            try {
                // 進行依賴解決,找到符合條件的bean
                Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
                if (arg == null && !this.required) {
                    arguments = null;
                    break;
                }
                arguments[i] = arg;
            }
            catch (BeansException ex) {
                throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
            }
        }
        // 這裏跟字段注入差不多,就是註冊bean的依賴關係,並且緩存每個參數的ShortcutDependencyDescriptor
        synchronized (this) {
            if (!this.cached) {
                if (arguments != null) {
                    DependencyDescriptor[] cachedMethodArguments = Arrays.copyOf(descriptors, arguments.length);
                    registerDependentBeans(beanName, autowiredBeans);
                    if (autowiredBeans.size() == argumentCount) {
                        Iterator<String> it = autowiredBeans.iterator();
                        Class<?>[] paramTypes = method.getParameterTypes();
                        for (int i = 0; i < paramTypes.length; i++) {
                            String autowiredBeanName = it.next();
                            if (beanFactory.containsBean(autowiredBeanName) &&
                                beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
                                cachedMethodArguments[i] = new ShortcutDependencyDescriptor(descriptors[i], autowiredBeanName, paramTypes[i]);
                            }
                        }
                    }
                    this.cachedMethodArguments = cachedMethodArguments;
                }
                else {
                    this.cachedMethodArguments = null;
                }
                this.cached = true;
            }
        }
    }
    // 找到了符合條件的bean
    if (arguments != null) {
        try {
            // 設置方法可訪問,利用反射進行方法調用,傳入參數
            ReflectionUtils.makeAccessible(method);
            method.invoke(bean, arguments);
        }
        catch (InvocationTargetException ex) {
            throw ex.getTargetException();
        }
    }
}

構造器注入

構造器注入就是通過調用 determineCandidateConstructors() 來返回合適的構造器。

public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName) throws BeanCreationException {

    // Quick check on the concurrent map first, with minimal locking.
    // 首先從緩存中獲取
    Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
    // 緩存為空
    if (candidateConstructors == null) {
        // Fully synchronized resolution now...
        // 這裏相當於double check
        synchronized (this.candidateConstructorsCache) {
            candidateConstructors = this.candidateConstructorsCache.get(beanClass);
            if (candidateConstructors == null) {
                Constructor<?>[] rawCandidates;
                try {
                    // 獲取beanClass的所有構造函數
                    rawCandidates = beanClass.getDeclaredConstructors();
                }
                catch (Throwable ex) {
                    throw new BeanCreationException(beanName, "Resolution of declared constructors on bean Class [" + beanClass.getName() +"] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
                }
                // 存放標註了@Autowired註解的構造器
                List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
                // 存放標註了@Autowired註解,並且required為true的構造器
                Constructor<?> requiredConstructor = null;
                Constructor<?> defaultConstructor = null;
                for (Constructor<?> candidate : rawCandidates) {
                    // 獲取構造器上的@Autowired註解信息
                    MergedAnnotation<?> ann = findAutowiredAnnotation(candidate);
                    if (ann == null) {
                        // 如果沒有從候選者找到註解,則嘗試解析beanClass的原始類(針對CGLIB代理)
                        Class<?> userClass = ClassUtils.getUserClass(beanClass);
                        if (userClass != beanClass) {
                            try {
                                Constructor<?> superCtor =
userClass.getDeclaredConstructor(candidate.getParameterTypes());
                                ann = findAutowiredAnnotation(superCtor);
                            }
                            catch (NoSuchMethodException ex) {
                                // Simply proceed, no equivalent superclass constructor found...
                            }
                        }
                    }
                    if (ann != null) {
                        // 如果requiredConstructor不為空,代表有多個標註了@Autowired且required為true的構造器,此時Spring不知道選擇哪個拋出異常
                        if (requiredConstructor != null) {
                            throw new BeanCreationException(beanName, "Invalid autowire-marked constructor: " + candidate +". Found constructor with 'required' Autowired annotation already: " + requiredConstructor);
                        }
                        // 獲取@Autowired註解的reuired屬性的值
                        boolean required = determineRequiredStatus(ann);
                        if (required) {
                            // 如果當前候選者是@Autowired(required = true),則之前不能存在其他使用@Autowire註解的構造函數,否則拋異常
                            if (!candidates.isEmpty()) {
                                throw new BeanCreationException(beanName,"Invalid autowire-marked constructors: " + candidates +". Found constructor with 'required' Autowired annotation: " + candidate);
                            }
                            // required為true將當前構造器賦值給requiredConstructor
                            requiredConstructor = candidate;
                        }
                        // 將當前構造器加入進候選構造器中
                        candidates.add(candidate);
                    }
                    // 沒有標註了@Autowired註解且參數長度為0,賦值為默認構造器
                    else if (candidate.getParameterCount() == 0) {
                        defaultConstructor = candidate;
                    }
                }
                // 有標註了@Autowired註解的構造器
                if (!candidates.isEmpty()) {
                    // Add default constructor to list of optional constructors, as fallback.
                    // 沒有標註了@Autowired且required為true的構造器
                    if (requiredConstructor == null) {
                        // 默認構造器不為空
                        if (defaultConstructor != null) {
                            // 將默認構造器加入進候選構造器中
                            candidates.add(defaultConstructor);
                        }
                    }
                    // 將候選者賦值給candidateConstructors
                    candidateConstructors = candidates.toArray(new Constructor<?>[0]);
                }
                // 只有1個構造器 && 參數長度大於0(非默認構造器),只能用它當做候選者了
                else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
                    candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
                }
                // 只有1個構造器 && 參數長度大於0,只能用它當做候選者了
                else if (nonSyntheticConstructors == 2 && primaryConstructor != null &&
                         defaultConstructor != null && !primaryConstructor.equals(defaultConstructor)) {
                    candidateConstructors = new Constructor<?>[] {primaryConstructor, defaultConstructor};
                }
                else if (nonSyntheticConstructors == 1 && primaryConstructor != null) {
                    candidateConstructors = new Constructor<?>[] {primaryConstructor};
                }
                // 返回一個空的Constructor
                else {
                    candidateConstructors = new Constructor<?>[0];
                }
                // 緩存候選的構造器
                this.candidateConstructorsCache.put(beanClass, candidateConstructors);
            }
        }
    }
    // 如果候選構造器長度大於0,直接返回,否則返回null
    return (candidateConstructors.length > 0 ? candidateConstructors : null);
}

關於 SmartInstantiationAwareBeanPostProcessor 接口的調用時機,在 Spring IoC createBean 方法詳解 一文中有介紹過,這裏就不再贅述了。

總結

本文主要介紹了 Spring 對 @Autowired 註解的主要處理過程,結合前面的 Spring IoC getBean 方法詳解 和 Spring IoC createBean 方法詳解 文章一起看才能更好的理解。

最後,我模仿 Spring 寫了一個精簡版,代碼會持續更新。地址:https://github.com/leisurexi/tiny-spring。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※網頁設計最專業,超強功能平台可客製化

痞子衡嵌入式:鏈接函數到8字節對齊地址或可進一步提升i.MXRT1xxx內核執行性能

  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是i.MXRT上進一步提升代碼執行性能的經驗。

  今天跟大家聊的這個話題還是跟痞子衡最近這段時間參与的一個基於i.MXRT1170的大項目有關,痞子衡在做其中的開機動畫功能,之前寫過一篇文章 《降低刷新率是定位LCD花屏显示問題的第一大法》 介紹了開機動畫功能的實現以及LCD显示注意事項,在此功能上,痞子衡想進一步測試從芯片上電到LCD屏显示第一幅完整圖像的時間,這個時間我們暫且稱為1st UI時間,該時間的長短對項目有重要意義。

  痞子衡分別測試了代碼在XIP執行下和在TCM里執行下的1st UI時間,得到的結果竟然是XIP執行比TCM執行還要快50ms,這是怎麼回事?這完全顛覆了我們的理解,i.MXRT上TCM是與內核同頻的,Flash速度遠低於TCM。如果是XIP執行,即使有I-Cache加速,也最多與TCM執行一樣快,怎麼可能做到比TCM執行快這麼多。於是痞子衡便開始深挖這個奇怪的現象,然後發現了進一步提升代碼執行性能的秘密。

一、引出計時差異問題

  痞子衡的開機動畫程序是基於 \SDK_2.x.x_MIMXRT1170-EVK\boards\evkmimxrt1170\jpeg_examples\sd_jpeg 例程的,只是去了SD卡和libjpeg庫相關代碼。工程有兩個build,一個是TCM里執行(即debug),另一個是XIP執行(即flexspi_nor_debug)。

  項目板上的Flash型號是MX25UW51345G,痞子衡將其配成Octal mode, DDR, 166MHz用於啟動。項目板上還有兩個LED燈,痞子衡在LED燈上飛了兩根線,連同POR引腳一起連上示波器,用於精確測量1st UI各部分時間組成。

  示波器通道1連接POR引腳,表明1st UI時間起點;通道2連接LED1 GPIO,表明ROM啟動時間(進入用戶APP的時間點);通道3連接LED2 GPIO,做兩次電平變化,分別是1st圖像幀開始和結束的時間點。翻轉LED GPIO代碼位置如下:

void light_led(uint32_t ledIdx, uint8_t ledVal);

void SystemInit (void) {
    // 將LED1置1,標示ROM啟動時間
    light_led(1, 1);

    SCB->CPACR |= ((3UL << 10*2) | (3UL << 11*2));

    // ...
}

void APP_InitDisplay(void)
{
    // ...

    g_dc.ops->enableLayer(&g_dc, 0);

    // 將LED2置1,標示1st圖像幀開始時間點
    light_led(2, 1);
}

int main(void)
{
    BOARD_ConfigMPU();
    BOARD_InitBootPins();
    BOARD_BootClockRUN();
    BOARD_ResetDisplayMix();

    APP_InitDisplay();

    while (1)
	{
	    // ...
	}
}

static void APP_BufferSwitchOffCallback(void *param, void *switchOffBuffer)
{
    s_newFrameShown = true;

    // 將LED2置0,標示1st圖像幀結束時間點
    light_led(2, 0);
}

  上圖是痞子衡抓到的波形(30Hz,XIP),痞子衡一共做了四次測試,分別是30Hz LCD刷新率下的XIP/TCM以及60Hz LCD刷新率下的XIP/TCM,結果如下錶所示。表中的Init Time一欄表示的是開機動畫程序代碼執行時間(從SystemInit()函數開始執行到APP_InitDisplay()函數結束的時間),可以看到TCM執行比XIP執行慢近50ms,這便是奇怪問題所在。

代碼位置 LCD刷新率 POR Time Boot Time Init Time Launch Time
XIP 30Hz 3.414ms 10.082ms 34.167ms + 153ms 32.358ms
TCM 30Hz 3.414ms 10.854ms 33.852ms + 203ms 32.384ms
XIP 60Hz 3.414ms 9.972ms 18.142ms + 153ms 16.166ms
TCM 60Hz 3.414ms 10.92ms 17.92ms + 203ms 16.104ms

二、定位計時差異問題

  對於開機動畫代碼,XIP執行比TCM執行快這個結果,痞子衡是不相信的,於是痞子衡便用二分法逐步查找,發現時間差異是BOARD_InitLcdPanel()函數里的DelayMs()調用引起的,這些人為插入的延時是LCD屏控制器手冊里的要求,總延時時間應該是153ms,但是這個函數的執行在XIP下(153ms)和TCM里(203ms)時間不同。

static void BOARD_InitLcdPanel(void)
{
    // ...

#if (DEMO_PANEL ==  DEMO_PANEL_TM103XDKP13)
    // ...

    /* Power LCD on */    
    GPIO_PinWrite(LCD_RESET_GPIO, LCD_RESET_GPIO_PIN, 1);
    DelayMs(2);
    GPIO_PinWrite(LCD_RESET_GPIO, LCD_RESET_GPIO_PIN, 0);
    DelayMs(5);
    GPIO_PinWrite(LCD_RESET_GPIO, LCD_RESET_GPIO_PIN, 1);
    DelayMs(6);
    GPIO_PinWrite(LCD_STBYB_GPIO, LCD_STBYB_GPIO_PIN, 1);
    DelayMs(140);
#endif
    // ...
}

  所以現在的問題就是為何在TCM里執行DelayMs(153)需要203ms,而XIP執行下是精確的。讓我們進一步查看DelayMs()函數的原型,這個函數其實調用的是SDK_DelayAtLeastUs()函數,SDK_DelayAtLeastUs()函數從命名上看就很有意思,AtLeast即保證軟延時一定能滿足用戶設置的時間,但也可能超過這個時間。為何是AtLeast設計,其實這裏就涉及到Cortex-M7內核一個很重要的特性 – 指令雙發射,軟件延時的本質是靠CPU執行指令來消耗時間,但是CPU拿指令到底是單發射還是雙發射有一定的不確定性,因此無法做到精確,如果以全雙發射來計算,就能得出最小延時時間。

#define DelayMs                  VIDEO_DelayMs

#if defined(__ICCARM__)
static void DelayLoop(uint32_t count)
{
    __ASM volatile("    MOV    R0, %0" : : "r"(count));
    __ASM volatile(
        "loop:                          \n"
        "    SUBS   R0, R0, #1          \n"
        "    CMP    R0, #0              \n"
        "    BNE    loop                \n");
}
#endif

void SDK_DelayAtLeastUs(uint32_t delay_us, uint32_t coreClock_Hz)
{
    assert(0U != delay_us);
    uint64_t count = USEC_TO_COUNT(delay_us, coreClock_Hz);
    assert(count <= UINT32_MAX);

#if (__CORTEX_M == 7)
    count = count / 3U * 2U;
#else
    count = count / 4;
#endif
    DelayLoop(count);
}

void VIDEO_DelayMs(uint32_t ms)
{
    SDK_DelayAtLeastUs(ms * 1000U, SystemCoreClock);
}

  分析到現在,問題已經轉化成為何XIP下執行指令雙發射概率比TCM里執行指令雙發射概率更大,關於這個現象並沒有在ARM官方文檔里查找到相關信息,DelayLoop()循環里只是3條指令,XIP下執行肯定是在Cache line里,這跟在TCM里執行並沒有什麼區別。讓我們再去看看兩個工程的map文件,找到DelayLoop()函數鏈接地址,這個函數在兩個測試工程下鏈接地址對齊不一樣,這意味着測試條件不完全相同,或許這是一個解決問題的線索。

  XIP執行工程(flexspi_nor_debug),DelayLoop()函數地址8字節對齊:

*******************************************************************************
*** ENTRY LIST
***

Entry                       Address   Size  Type      Object
-----                       -------   ----  ----      ------
DelayLoop               0x3000'3169    0xa  Code  Lc  fsl_common.o [1]

  TCM執行工程(debug工程),DelayLoop()函數地址4字節對齊:

*******************************************************************************
*** ENTRY LIST
***

Entry                       Address   Size  Type      Object
-----                       -------   ----  ----      ------
DelayLoop                    0x314d    0xa  Code  Lc  fsl_common.o [1]

三、找到計時差異本質

  前面找到DelayLoop()函數鏈接地址差異是一個線索,那我們就針對這個線索做測試,不再讓鏈接器自動分配DelayLoop()函數地址,改為在鏈接文件里指定地址去鏈接,下面代碼是IAR環境下的示例,我們使用debug工程(即在TCM執行)來做測試。

  C源文件中在DelayLoop()函數定義前加#pragma location = “.myFunc”,即將該函數定義為.myFunc的段,然後在鏈接文件icf中用place at語句指定.myFunc段到固定地址m_text_func_start處開始鏈接:

#if defined(__ICCARM__)
#pragma location = ".myFunc"
static void DelayLoop(uint32_t count)
{
    // ...
}
#endif
define symbol m_text_func_start        = 0x00004000;

place at address mem: m_text_func_start     { readonly section .myFunc };

define symbol m_text_start             = 0x00002400;
define symbol m_text_end               = 0x0003FFFF;

place in TEXT_region                        { readonly };

  根據鏈接起始地址m_text_func_start的不同,我們得到了不同的結果,如下錶所示。至此真相大白,造成DelayMs()函數執行時間不同的根本原因不是XIP/TCM執行差異,而是鏈接地址對齊差異,8字節對齊的函數更容易觸發CM7指令雙發射,相比4字節對齊的函數在性能上能提升24.8% 。

m_text_func_start值 鏈接地址對齊 函數調用語句 實際執行時間
0x00004000 8n字節 DelayMs(100) 100ms
0x00004002 2字節,未能鏈接 N/A N/A
0x00004004 4字節 DelayMs(100) 133ms
0x00004008 8字節 DelayMs(100) 100ms

  現在我們得到了一個有趣的結論,Cortex-M7上將函數鏈接到8字節對齊的地址有利於指令雙發射,這就是進一步提升代碼執行性能的秘密。

  至此,i.MXRT上進一步提升代碼執行性能的經驗痞子衡便介紹完畢了,掌聲在哪裡~~~

歡迎訂閱

文章會同時發布到我的 博客園主頁、CSDN主頁、微信公眾號 平台上。

微信搜索”痞子衡嵌入式“或者掃描下面二維碼,就可以在手機上第一時間看了哦。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?