為什麼我心目中的年度車型一定會有他?

在材料與結構上的極致輕量化,不僅帶來更低的能耗,關鍵在於輕量化與高剛性的車身是整車高性能的基礎。與車身科技一樣, ESS II智能安全、安吉星4G LTE移動互聯等產品優勢都是CT6家族全系共享的。此外,三款車在技術與調教上有相同的底蘊,也造就了共通的調性,豪華舒適但不失操控的樂趣。

轉眼又到車市年終盤點的時候了,循例會收到很多平台年度車型的評選邀請,凱迪拉克CT6在我的榜單中從來不會缺席。之所以會選CT6,不是因為其中一款車型,而是在這個級別中, CT6家族中每一款車的產品力在其所在細分市場都處於領先地位。今日,CT6插電式混合動力(plug-in)正式上市,售價55.88-65.88萬元。隨着這款pHEV的加入,凱迪拉克CT6家族變得更加完善,我也覺得是時候與大家聊一下這款我心目中的年度車型。

凱迪拉克CT6 plug-in雖然是CT6家族中的首個新能源車型,但這個級別中本身也有不少插電式混動的車型,為何偏偏要選TA? 其實仔細分析一下豪華插電混動細分市場的車型就很容易明白了,特別是德系选手,這裏就不點名批評了,在汽油車型的基礎上加個電機和電池就叫pHEV了,電機夾在變速箱和發動機之間的串聯式結構,整套傳動系統都是來自供應商,簡單粗暴完全沒有技術含量,無論是發動機還是電機驅動的效率都很低,對能源的利用效率甚至不如普通汽油車型,充其量只能說是大環境下逼出來的產物。

舉例說明,同樣是pHEV的車型,CT6 plug-in與寶馬的iperformance的理念則完全不一樣,凱迪拉克的performance Hybrid對新能源的理解屬於性能趨向,直接打造出一款技術含量最高,專為插電式混動打造的解決方案,其power Split動力分流技術可以說是目前最先進的電驅動科技,CT6 plug-in也是細分市場內的最強單品。而反觀寶馬iperformance,自成都車展大規模發布新車后,收效甚微,因為其多款產品都是由燃油車加一個電機,技術含量相對較低。我們可以理解iperformance更多是為了迎合排放政策,博眼球的戰術,而非精心打造的產品,其戰略意義更明顯。但CT6 plug-in對細分市場向高性能發展的長期引領作用,未來將會越來越明顯。很顯然,新能源行業是得技術者得天下。

相比之下,CT6 plug-in這套系統在結構上具有絕對的領先優勢,其搭載的DHT變速箱其實與本家的HEV有異曲同工之妙,兩種動力耦合的關鍵在於行星齒輪組,從而實現power Split動力分流技術,3排行星齒輪與5組離合器不只是簡單的耦合動力,配合智能電控系統更可以實現四種智能能量輸出狀態(純電力驅動、發動機驅動、混合動力驅動以及智能能量回收),在各種複雜的工況下都有最合適的動力組合去適應,通用是混動領域內首次將這個概念進行量產的廠商。

此外,CT6 plug-in的整套動力系統是圍繞performance Hybrid(性能駕享)豪華新能源研發策略開發的,得益於三組行星齒輪組的動力耦合結構,兩台電機與2.0T SIDI 渦輪增壓直噴發動機可以共同發力驅動車輛,最大扭矩達586Nm,靠着電機瞬間爆發的扭矩,百公里加速只需5.4s,在同級中性能無出其右。對於追求駕控樂趣的人而言,例如我,對如此高性能自然是無抗拒之力的。

除了DHT變速箱先進的結構,還需要強大的控制系統,CT6 plug-in的智能電控系統通過TpIM驅動能量轉換模塊協同電驅單元與發動機的高效工作,帶來更加智能的動力組合路徑,也能實現比傳統燃油車更平順的駕駛體驗。此外,CT6 plug-in還擁有巡航、運動、電量保持三種駕駛模式,讓駕駛者在任何工況下都能享受更多駕駛樂趣。

同樣是源自performance Hybrid這個理念, 強於一般混動車型的還有電池,CT6 plug-in搭載18.4kWh大容量鋰離子電池,能量密度遠超其他混動車型搭載的鎳氫電池,可以提供80Km的純電里程,綜合續航里程能夠達到935km。雖與特斯拉一樣是鋰離子電池,但其BMS電池熱管理及液冷技術,可通過3組冷液循環系統進行散熱,這在全球都是處於領先地位的。其實電池技術和汽油機也有共通之處,越高的性能相對就需要越強的冷卻效果,要知道布加迪的W12發動機可是配了11個散熱器,這套熱管理系統能更好的配合高密度的鋰離子電池,適應高性能的輸出。

誠然,憑藉著這套業界領先的高性能混動系統,CT6 plug-in就足以讓我對其傾心,但CT6的能力卻不止於此。輕量化一直是近幾年汽車工業的一大主題,車身技術一直是我非常看重的技術點,在其他條件相仿的情況下,輕量化做的越好,車子的動態響應自然就會更好。

因此CT6全系都擁有宇航級輕量化車身,這在我的選車之道中無疑是關鍵的加分點。鋁材用量超過57%,11種混合材質輕量化車身,通過獨特的專利技術,首次實現了鋼、鋁兩種不同材質的焊接。在材料與結構上的極致輕量化,不僅帶來更低的能耗,關鍵在於輕量化與高剛性的車身是整車高性能的基礎。

與車身科技一樣, ESS II智能安全、安吉星4G LTE移動互聯等產品優勢都是CT6家族全系共享的。此外,三款車在技術與調教上有相同的底蘊,也造就了共通的調性,豪華舒適但不失操控的樂趣。而我之所以視CT6為年度車型,是因在此基礎上,家族各成員又都有不同的亮點。

其中凱迪拉克CT6 40T作為凱迪拉克的,乃至整個通用車系的旗艦車型,集成了通用的所有科技技術,在同級中無出其右,追求的是技術的巔峰。搭載3.0T雙渦輪增壓發動機不僅擁有405ps和543N·m的傲人數據,配合閉缸技術與自動啟停,兼顧性能與油耗。除了車身材質與結構的高度輕量化, ARS主動式後輪轉向系統和AWD全時四驅系統的搭載,讓身長5米多的大車開起來依然保持靈巧,彎道表現穩健,車身響應积極,讓“大車也能玩操控”變成了現實。360°全方位降噪,MRC主動電磁感應懸挂等技術的搭載則保證了其安靜舒適的豪華本色。

不難發現在CT6 40T上所體驗到的一切都來自於科技,其實每個車型都有自己的側重點,特別是豪華車型都有其獨有的底蘊與內涵。你們知道日本的匠心,德國的精工,但這個世界上最好的手機,最好的電腦,最先進的產品都來自美國。CT6作為新美式豪華的代表,帶給消費者科技與豪華結合的完美體驗。

作為CT6家族中最走量的一款車型,想必關注CT6 28T的人應該是最多的。確實如此,三款車各自扮演的角色不同,28T的品質很大程度上能決定整個家族能走多遠。因此,我們可以看見28T傳承了40T上的諸多產品優勢,標配20多項同級獨有的配置,你完全不用懷疑其豪華程度。另一方面,受到大環境的改變影響,2.0T的大型豪華車正在逐步為大家所接受,特別是生活在擁堵的城市環境中, 想兼顧節能與樂趣卻着實不易。CT6 28T搭載的是為數不多,在大型車上動力表現還能讓我滿意的2.0T發動機。沃德十佳的2.0T SIDI 渦輪增壓直噴發動機可以輸出276ps與400N·m的動力,其表現已經完全超越了3.0L排量的車型。加之與40T共享的輕量化車身與底盤技術,輕快的動力響應,精準的轉向手感,紮實的底盤質感加上更經濟的油耗,配合上終端合理的優惠,CT6 28T更像是一輛能取悅你的車。

現如今路上豪車觸目皆是,汽車對於國人而言早已不只是代步工具,無論是對面子還是品質都有更高的追求。寶馬5系月銷輕鬆過萬,奔馳新E級一上來也是呼風喚雨,其中大家對BBA豪華老字號的追捧起了關鍵性的作用。在我看來,凱迪拉克CT6家族產品力完全不輸5系E級等對手,甚至直逼7系與A8L等車型,這樣的錯位競爭無疑讓CT6更具入手的價值。而就我個人而言,CT6最打動我的是在其豪華配置之下還為駕駛者提供了這個級別罕有的駕駛樂趣,這是非常難能可貴的。同時你不難發現,plug-in創新高的性能駕趣,40T造就的技術巔峰,28T迎合了城市通勤,不同的需求不同的價位你都能找到合適的CT6,這就是為何我的年度車型榜單中必備凱迪拉克CT6的原因。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

不要小看這款常見的家用車!懂得買這款車的人都不簡單…

為什麼日產要推出這樣的一輛車,並且把重點放在舒適性上,其實和它的“大哥”有一定的聯繫。我們都十分熟悉的風神藍鳥,就是由第九代藍鳥U13衍生而來,為了迎合國內市場做了大量的修改,更加顯得豪華,雖然市場表現很好,但隨着時間的推移,時代的變遷,無論是技術層面還是還是設計層面,都已經被市場淘汰。

2006年,日產推出了一款介於天籟與藍鳥之間的全新中高級轎車,英文名為BLUEBIRD SYLpHY,當時的新車指導價為:14.38萬-21.48,主打2.0L車型,其實日產推出這款車型的目的明確,就是為了替代BLUEBIRD藍鳥。

外觀造型飽滿圓潤,鍍鉻裝飾的中網突顯檔次,前大燈的設計與風雅有相似之處,整車設計給人感覺大方得體,非常流暢,這也是軒逸經典款至今仍在出售的原因之一。

堪稱沙發級別的座椅是軒逸的殺手鐧,座椅寬大、厚實、飽滿,一坐進去軒逸的後排,就像是坐在家裡的沙發一樣,整個人陷入其中,坐墊非常軟,同時還有着非常寬裕的腿部空間,大家要知道,那時候可是2006年,能夠有這麼優秀的舒適性,是非常難得的。

為什麼日產要推出這樣的一輛車,並且把重點放在舒適性上,其實和它的“大哥”有一定的聯繫。我們都十分熟悉的風神藍鳥,就是由第九代藍鳥U13衍生而來,為了迎合國內市場做了大量的修改,更加顯得豪華,雖然市場表現很好,但隨着時間的推移,時代的變遷,無論是技術層面還是還是設計層面,都已經被市場淘汰。

日產需要一輛能夠霸佔家用轎車市場的車型,就這樣,軒逸應運而生,外觀大氣、乘坐舒適、空間寬裕,動力方面使用了MR20DE的2.0L發動機和第三代XTRONIC CVT無極變速箱,側重於追求低油耗和低噪音,這些所有的一切,都是為了舒適性,做出高級感。

最終日產的確憑藉著它在市場中站穩腳跟,聽到日產軒逸就會想起那舒適居家的溫馨感,其實我們可以看得出來,當初軒逸的成功是必然的,因為市場就是需要這樣的產品,同時我們也看到日產似乎對於“藍鳥”這個名字念念不忘。

日產通過LANNIA 藍鳥很全面的詮釋了V-motion家族式設計風格,告訴我們什麼才叫“驚艷”,由一開始的BLUEBIRD,到後來的BLUEBIRD SYLpHY,然後BLUEBIRD消失,剩下SYLpHY,最後LANNIA 藍鳥出現。

第一代藍鳥

“藍鳥”這兩個字包含着很多歷史,很多情懷,還有我們小時候的生活場景,它註定不會消失,這些歷史的沉澱,我們都應該好好記住。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

從7萬到25萬,這些車為了衝量竟然最高降3.7萬!

6L自動擋版本綜合油耗在5毛錢以內。別克君威指導價:17。89-27。99萬降價幅度:3。7萬推薦車型:2015款1。6T領先技術型君威作為別克的一款中高檔轎車,定位介於英朗和君越之間,屬於一款20萬級別的中型車,在乘坐空間,配置,做工用料上,都比代步車上了一個層次,而這款車的優惠幅度也是同級別車型中最大的,最高達到了3。

許多經銷商們為了年底衝量,在7萬-25萬這個區間的車型,許多都放出了最大降價的優惠,從小型車,緊湊型車再到中級車,SUV,羅列了幾款降價幅度很大的好品質車型,供大家參考參考。

雪鐵龍愛麗舍

指導價:8.38-12.08萬元

降價幅度:1.5萬元

車型:2016款1.6L自動豪華型

愛麗舍是法國雪鐵龍推出的一款家用轎車,自2002年誕生以來,用戶好口碑一直不斷積累着,為了針對中國人的審美觀,新款愛麗舍擁有更加動感的車身線條和造型設計,廠家似乎正通過這款車和消費者說:其實我們要造好造型還是不差於別人的。

愛麗舍內飾的整體風格簡潔,清新,高雅,且空調出風口設計得很大,製造出風最好的效率,每個區域分隔清晰,操作非常方便,車型上推薦買自動豪華版,因為配置豐富帶ESp,真皮座椅,倒車影像,安全側氣囊等,優惠完車身價大概在10萬元左右。動力則全系搭載1.6L自然吸氣發動機,匹配5擋手動和6擋手自一體變速器。

車主評價:動力充足,配置夠用,外形沉穩時尚,底盤舒服有韌性,剎車感覺非常線性。

大眾高爾夫

指導價:12.19-23.99萬

降價幅度:2.8萬

推薦車型:2016款 1.6L自動時尚型

從1974年開始,大眾高爾夫就誕生了,直至今年,已經在全球市場推出七代車型,也是大眾最暢銷的車型之一,在中國,兩廂的高爾夫非常受人們歡迎,比起六代,七代高爾夫在外觀造型上更加犀利運動。

內飾風格同樣也是大眾風格,簡約實用,對於家用代步來說,車型版本選擇1.6L自然吸氣排量就夠了,如果不需要天窗的可以選自動時尚型,匹配6擋手自一體,配置安全側氣囊,ESp車身穩定控制,上坡輔助,自動駐車等等,而如果是喜歡追尋動力的年輕人,可以考慮1.4T渦輪增壓發動機版本,匹配7擋雙離合變速器,更有操控和駕駛的樂趣。

車主評價:外觀好看,舒適度滿意,轉向精準,隔音做得蠻好,油耗低,1.6L自動擋版本綜合油耗在5毛錢以內。

別克君威

指導價:17.89-27.99萬

降價幅度:3.7萬

推薦車型:2015款1.6T領先技術型

君威作為別克的一款中高檔轎車,定位介於英朗和君越之間,屬於一款20萬級別的中型車,在乘坐空間,配置,做工用料上,都比代步車上了一個層次,而這款車的優惠幅度也是同級別車型中最大的,最高達到了3.7萬元。

推薦的這款1.6T領先技術型,匹配6速手自一體變速箱,算是各方面相對均衡的一個版本,動力夠用,配置齊全,帶有胎壓監測,ESp,上坡輔助,電動天窗,自動空調等等,再加上美系車蠻好的隔音效果,優質的底盤響應,用料十分厚道,總有一種讓人們覺得這個價格買得值的感覺。

車主口碑:隔音好,外觀公認好看,車身穩重,安全感強烈。

現代ix35

指導價:14.98-22.28萬

降價幅度:2.8萬元

推薦車型:2015款 2.0L自動兩驅智能型國IV

現代ix35的外形設計由德國法蘭克福研發中心完成,帶有明顯歐式風格,時尚前衛,動感強韌,韓系車都喜歡將錢花在大家都看得到的地方,同樣的價格,假如這台車外形更好看的話,中國老百姓就喜歡,就覺得值,同樣車內空間的拓展上做得非常好,後備箱也一樣寬大。

內飾方面,營造了一種溫馨家用的風格,做工細膩手感順滑,並採用大面積的銀色裝飾,方向盤採用真皮縫製,配置非常貼心,配備有安全側氣囊,無鑰匙進入與啟動,ESp,上坡輔助,電動天窗等等,同等價位里這個配置挺高分的了,搭載有2.0L和2.4L自然吸氣發動機,匹配6速手動和6速手自一體變速器。

車主評價:同等級對比后,內部空間大,配置高,性價比強。

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

javascript 面向對象學習(三)——this,bind、apply 和 call

this 是 js 里繞不開的話題,也是非常容易混淆的概念,今天試着把它理一理。

this 在非嚴格模式下,總是指向一個對象,在嚴格模式下可以是任意值,本文僅考慮非嚴格模式。記住它總是指向一個對象對於理解它的意義很重要。this 在實際使用中,大致分為以下幾種情況:

1. 函數作為對象的方法調用時,this 指向調用該函數的對象

var obj = {
    name: 'jack',
    getName: function() {
        console.log(this === obj) // true
        console.log(this.name)  // jack
    }
}
obj.getName()

這個應該很好理解,不多說了。

2. 函數作為普通函數被調用時,this 指向全局對象。在瀏覽器中,全局對象是window。

var name = 'global'
function getName() {
    console.log(this === window) // true
    console.log(this.name) // global
}
getName()

我的理解是上面的代碼可以改寫為

window.name = 'global'
window.getName = function() {
    console.log(this === window) // true
    console.log(this.name) // global
}
window.getName()

這樣其實與情況1是一樣的,相當於函數作為對象的方法調用,只不過這裏的對象是全局對象。

《Javascript 設計模式與開發實踐》一書中有個例子如下:

window.name = 'globalName';
var myObject = {
    name: 'seven',
    getName: function(){
        return this.name
    } 
}

var getName = myObject.getName
console.log(getName())  // globalName

getName 是定義在myObject 對象中的方法,在調用getName 方法時,打印出的卻是全局對象的name,而不是myObject對象的name,這再次證明了 this 並非指向函數被聲明時的環境對象,而是指向函數被調用時的環境對象

3. 函數作為構造函數調用時,指向構造出的新對象

function Person(name) {
    this.name = name  
}

var jack = new Person('Jack')
console.log(jack.name) // Jack
var rose = new Person('Rose')
console.log(rose.name) // Rose

這裏創建了兩個不同名字的對象,打印出的name也是不一樣的,說明構造函數的 this 會根據創建對象的不同而變化。需要注意的是,如果構造函數里返回了一個Object類型的對象,那麼this會指向這個對象,而不是利用構造函數創建出的對象。我們在構造函數一章里也提到過,new 操作符所做的最後一步就是返回新對象,而如果我們顯式地返回一個對象,就會覆蓋這步操作,this也就不再指向新對象。

4. 函數作為事件處理函數調用時,指向觸發事件的元素

document.getElementById("myBtn").addEventListener("click", function(e){
    console.log(this === e.currentTarget) // true
});

5. 箭頭函數

由於箭頭函數沒有this,它的 this 是繼承父執行上下文裏面的 this。執行上下文後面再討論,現在只要知道簡單對象(非函數)是沒有執行上下文的。

var obj = {
    name:  'obj',
    getName: function() {
console.log(this) // 執行上下文里的 this
return (()=>{ console.log(this.name) }) } } var fn = obj.getName() fn() // obj

按照情況2來處理的話,this 指向全局對象,應該輸出 undefined,結果並不是。與普通函數不同,箭頭函數的 this 是在函數被聲明時決定的,而不是函數被調用時。在這裏,父執行上下文是 getName 函數,也就繼承了 getName 的 this,即 obj。

利用 bind、apply、call 改變 this 指向

bind、apply、call 都是定義在 Function 原型對象上的方法,所有函數對象都能繼承這個方法,三者都能用來改變 this 指向,我們來看看它們的聯繫與區別。

function fn() {
    console.log(this.name)
}

// bind
var bindfn = fn.bind({name: 'bind'})
bindfn() // bind // apply
fn.apply({name: 'apply'}) // apply // call
fn.call({name: 'call'}) // call

我們定義了一個函數fn,然後分別調用了它的 bind、apply、call 方法,並傳入一個對象參數,通過打印出的內容可以看到 this 被綁定到了參數對象上。bind 似乎有些不同,多了一步 bindfn() 調用,這是因為 bind 方法返回的是一個函數,不會立即執行,而調用 apply 和 call 方法會立即執行。

下面再來看一下 fn 函數存在參數的情況:

function fn(a, b, c) {
    console.log(a, b, c)
}

var bindfn = fn.bind(null, 'bind');
bindfn('A', 'B', 'C');           // bind A B

fn.apply(null, ['apply', 'A']) // apply A undefined

fn.call(null, 'call', 'A');  // bind A undefined

bindfn 打印出的結果是fn調用bind方法時的傳遞的參數加上bindfn傳遞的參數,參數 ‘C’ 被捨棄掉了。調用 apply 和 call 方法打印出的則是傳遞給它們的參數,不一樣的是,apply 的參數是一個數組(或類數組),call 則是把參數依次傳入函數。這時候再看它們的定義應該會好理解很多:

bind() 方法創建一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定為 bind() 的第一個參數,而其餘參數將作為新函數的參數,供調用時使用。

apply() 方法調用一個具有給定 this 值的函數,以及作為一個數組(或類數組對象)提供的參數。

call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。

我們可以利用它們來借用其他對象的方法。已知函數的參數列表 arguments 是一個類數組對象,比如上例中函數 fn 的參數 a, b, c,因為它不是一個真正的數組,不能調用數組方法,這時借用 apply/call 方法(bind 也可以,就是用得比較少)將 this 指向 arguments 就能借用數組方法:

(function(){
    Array.prototype.push.call(arguments, 'c')
    console.log(arguments) // ['a', 'b', 'c']
})('a','b')

值得一提的是,push 方法並不是只有數組才能調用,一個對象只要滿足1.可讀寫 length 屬性;2.對象本身可存取屬性. 就可以利用 call / apply 調用 push 方法。

 

參考:

《Javascript 設計模式與開發實踐》

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this

http://www.imooc.com/article/80117

https://blog.csdn.net/weixin_42519137/article/details/88053339

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

【其他文章推薦】

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

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

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

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

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

宇宙第一IDE是誰?

更多精彩文章,盡在碼農翻身

微服務把我坑了

如何降低程序員的工資?

程序員,你得選准跑路的時間!

兩年,我學會了所有的編程語言!

一直CRUD,一直996,我煩透了,我要轉型

字節碼萬歲!

上帝託夢給我說:一切皆文件

Javascript: 一個屌絲的逆襲

Node.js :我只需要一個店小二

我是一個線程

TCP/IP之大明郵差

一個故事講完Https

CPU 阿甘

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

git push 錯誤,回滾 push操作

作者:
故事我忘了

個人微信公眾號:
程序猿的月光寶盒

目錄

  • 0.記一次使用git push后,覆蓋了同事代碼的糗事
  • 1.還原案發現場的準備工作
    • 1.1 新建分支
      • 注意:
    • 1.2. 分支提交到遠程Git倉庫
  • 2.糗事發生契機
    • 2.1 假設文件是這個html文件,然後你上傳到遠程分支
    • 2.2 這時我用另一電腦修改這個文件,並提交到遠程,故意模仿他人操作,如圖,在遠程分支上Linux已經更新過
    • 2.3 本地文件也做不一樣的修改,假設自己再不知情的情況下做push操作必然會引起版本衝突
    • 2.4 此時本地我已經做了版本合併,所以,再次pull
  • 3. 正事來了.回滾吧
    • 3.1 將win給回滾調,留下Linux的代碼
      • 步驟
        • 1. 在目標分支上copy revision number:
        • 2. 右擊項目依次選中:git->Repository->Reset HEAD
  • 4.提交
    • git reset soft,hard,mixed之區別深解
      • 3.再次push
  • 5:驗證 上一步的強制push git push -f

0.記一次使用git push后,覆蓋了同事代碼的糗事

前言:

​ 都在WebStorm中操作,Idea或者PyCharm同理

​ 為了高度還原尷尬現場,這裡在原有項目上新建分支,然後都在分支上操作,一方面怕自己搞炸了,一方面真實環境就是如此

1.還原案發現場的準備工作

1.1 新建分支

注意:

這裏創建的分支僅僅在本地倉庫

1.2. 分支提交到遠程Git倉庫

遠程查看確認,確實有,說明分支已經創建

2.糗事發生契機

​ 這時候別人可能會和你改同一文件

2.1 假設文件是這個html文件,然後你上傳到遠程分支

注意這時候都是在剛創建的那個分支操作

​ 可以看到遠程分支已經有了

2.2 這時我用另一電腦修改這個文件,並提交到遠程,故意模仿他人操作,如圖,在遠程分支上Linux已經更新過

2.3 本地文件也做不一樣的修改,假設自己再不知情的情況下做push操作必然會引起版本衝突

Remote changes need to be merged before pushing

推送前需要合併遠程更改

​ 這時你點了合併

​ 上圖,把你的和他的都合併提交,但是出現如下警告

Push has been cancelled, because there were conflicts during update. Check that conflicts were resolved correctly, and invoke push again.

Push已被取消,因為在更新期間有衝突。檢查衝突是否已正確解決,並再次調用pull。

2.4 此時本地我已經做了版本合併,所以,再次pull

3. 正事來了.回滾吧

​ 現在,你被告知Linux的為正確的修改,並且你上一步的提交影響到他了,要回滾pushLinux操作的階段

3.1 將win給回滾調,留下Linux的代碼

步驟

1. 在目標分支上copy revision number

2. 右擊項目依次選中:git->Repository->Reset HEAD

Reset Type選Hard,To Commit 寫剛複製的版本號,

然後點擊Reset按鈕

這時候,代碼已經回到了老的版本,這個時候不能提交代碼,提交也是會衝突的。

4.提交

1.可以使用命令強制提交

  git push -f

或者

2.使用Idea,(我使用的是這個方法)

  在最新的commit上複製版本號

使用mixed類型,將上面複製的版本號粘貼進來:

git reset soft,hard,mixed之區別深解

git reset soft,hard,mixed之區別深解

又出來這個提示

3.再次push

​ 此時 代碼是最新的正確的,

​ 也就是Linux操作的正確修改

5:驗證 上一步的強制push git push -f

1.說明

  將程序從錯誤的復原,回滾到win操作

2.步驟

  按照上面的步驟進行操作。

  在後面提交的時候,直接強制提交,

則效果是:

可以看到一開始的

對應遠程的文件

至此就恢復以及修改了,Linux端只要pull一下就行了,就是最新代碼

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

【其他文章推薦】

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

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

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

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

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

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

深度學習-神經網絡

目錄

  • 深度學習-神經網絡
    • 摘要
    • 神經網絡
      • 淺層神經網絡
      • 深層神經網絡
      • 激活函數
      • 反向傳播
        • 損失(loss)
    • 卷積神經網絡
      • 局部感受野
      • 卷積核
      • 共享權值
      • 池化
    • 遷移學習
      • 對抗網絡

深度學習-神經網絡

摘要

機器學習人工智能的核心,而深度學習又是機器學習的核心。三者關係可用如下圖來表示。

人工神經網絡(Artificial Neural Networks,簡寫為ANNs)也簡稱為神經網絡(NNs)或稱作連接模型(Connection Model),它是一種模仿動物神經網絡行為特徵,進行分佈式并行信息處理的算法數學模型。

首先認識一下人腦的神經元之間的聯繫

神經網絡仿照人腦的神經元結構之間的聯繫,當某個神經元的軸突電信號強度達到一定程度時,就會觸發將信號傳遞到下一個神經元。在傳遞的過程中加上一些對數據處理的操作,從而達到處理傳遞信息的目的。上面的信號其實就是數值或者多維矩陣。

神經網絡

神經網絡又分為兩種,一種是淺層神經網絡,另一種是深層神經網絡,顧名思義可知,深層神經網絡要比淺層神經網絡複雜。

淺層神經網絡

深層神經網絡

上面兩圖中的圓圈則代表神經元,連線則代表上一層神經元對下一層神經元的信號傳遞

淺層神經網絡相比,深層神經網絡具有更複雜的模型結構,並且層次結構較多,神經網絡採用全連接的方式將神經元之間的信號進行傳遞,淺層神經網絡則是非全連接,或者單層全連接。

  • 全連接:是上一層的每個神經元都連接到下一層所有的神經元
  • 非全連接:一個神經元連接一個或者下一層的其中幾個神經元

每個神經元鏈接下一層多個神經元,由於不同神經元對該神經元的信號的Value不同,所以通過設置權重(Weight)的方式來降低或提高該神經元傳遞過來的信號。

其關係滿足 y = Wx+ B

  • w:weight權重,對該神經元的重視(需求)程度,也就是上面所說的數據處理階段,一般總weight(w1+w2+w3)為1
  • x:傳遞過來的信號量的值
  • b:為偏移量,對於在線性分類問題,偏移量是不可或缺的。

上圖如果沒有偏移量的話,該直線就會過原點。

顯然分類效果就不會好,通過加上一個大於0的偏移量b使得分類器往左平移。

  • y:是加權后的值,激活函數的參數x就是所有y的和

激活函數

激活函數是神經網絡模型中上一層神經元到下一層神經元數值的處理,上一層神經元通過線性函數(加權求和)得到的數值y,所有的y也是具有線性函數特性的,激活函數就是將這些數值非線性化,把y當作x帶入到激活函數中。

  • 線性函數關係為 y = Wx + B
  • 非線性函數即不是一條直線的函數,例如冪函數,指數函數,對數函數等

激活函數又類似於高等數學中的符號函數(sgn),(sgn不是激活函數)

  • x>0,則輸出f(x)=1,傳給下一個神經元
  • x<0,則輸出f(x)=-1,傳給下一個神經元
  • x=0,則輸出f(x)=0,傳給下一個神經元

經典的激活函數是Sigmoid 函數,其函數關係為f(x) = 1/(1-e^x)

  • x較大,則輸出f(x)=1,傳給下一個神經元
  • x較小,則輸出f(x)=0,傳給下一個神經元
  • 所有的值均壓縮在-1~1之間

反向傳播

反向傳播的基本思想就是在執行完所有層后,通過計算輸出層期望值之間的誤差來逆向調整權重參數,從而使損失減小,誤差變小。

損失(loss)

在程序運行過程中,模型生成好之後,會進行模型準確性評估,計算它的損失值,損失值越小,模型就越好。損失函數是計算損失函數的方法,也有一下幾種。

  • 0-1損失函數(0-1 lossfunction):
  • 平方損失函數(quadraticloss function)
  • 絕對損失函數(absoluteloss function)
  • 對數損失函數(logarithmicloss function)或對數似然損失函數(log-likelihood loss function)

卷積神經網絡

卷積神經網絡(Convolutional Neural Network,CNN)是一種前饋型的神經網絡,其在大型圖像處理方面有出色的表現,目前已經被大範圍使用到圖像分類、定位等領域中。相比於其他神經網絡結構,卷積神經網絡需要的參數相對較少,使的其能夠廣泛應用。

首先看下卷積網絡結構流程圖

回顧深層神經網絡,因為每個層都是全連接的,假設有一個64×64×3的圖像(64×64是二維平面上的像素值,3為第三維RGB的值),每個節點的權重是不同的,則下一層的每個神經元將會計算64×64×3個節點,若下一層有n個節點的神經元則計算量還需乘n,訓練起來較為複雜。

卷積神經網絡主要有三個特點

  • 局部感受野(Local Receptive Fields)
  • 共享權值(Shared Weights)
  • 池化(Pooling)

局部感受野

在動物器官中,大腦皮層有不同的感受區域,如聽覺區,視覺區等,每塊區域都有自己特殊的神經元,當有聽覺信號時會傳到大腦皮層的聽覺區的神經元,因此每個神經元對應的感受區域叫感受野.
那麼在神經網絡中,不同的卷積核會對應不同的感受區域,在卷積時彼此之間無聯繫。

卷積神經網絡的卷積層每個節點與上一層某個區域通過卷積核連接,而與這塊區域以外的區域無連接。

從右邊圖可見,每個圓圈代表一個卷積核,每個卷積核只需要關注自己的局部感受區域。

卷積核

卷積核到底是什麼?那就來觀察一下每個卷積核局部作用域。

上圖是取一個卷積和其對應的局部感受野,中間的那一層就是卷積核,3×3的卷積核對源數據中左上角的3×3的矩陣進行點乘,就得到了一個數值,叫做該區域的卷積層的值。然而,這隻是源數據中的部分區域,源數據是7×7的矩陣,所以卷積核再通過移動映射的方式多次卷積,如下圖。
該圖中在卷積層的值與源數據之間還應有個3×3的卷積核,未畫出。

通過上圖可以看出,如果一個7×7×1像素的圖片通過一個3×3×1的卷積核卷積,則被卷積后的值為5×5×1 !這裏注意一點,還是以圖片為例,若是圖片像素是三維的(彩色圖片),則像素為7×7×3的圖片必須使用三維的卷積核,並且第三維度也是3。

  • stride:步長
  • padding:擴展像素,填充像素
  • in:輸入的模式數,
  • out:輸出的模式數
  • W H:卷積核寬度長度

共享權值

每個卷積核連接8×8個節點也就64個權重值,然而還有8×8×2個卷積核呢!這裏出現了共享權值,共享權值就是每個卷積核對自己區域的權重比值都是相同的,所以僅需要給出64個權重比值即可,極大的減少了計算量。

池化

池化層也叫採樣層,作用是降低數據維度,主要有兩個池化方式

  • 最大池化
  • 平均池化

以最大池化為例,有4個2×2的矩陣,則取每個矩陣中的最大值作為該矩陣的特徵。

另一個就是平均池化,就是取矩陣中的平均值來作為該矩陣的特徵

遷移學習

對抗網絡

G生成的圖像被D識別為真圖像loss越小,D對於G生成的圖像的判別正確率越高越好

繼續更新,點個關注哦

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

【其他文章推薦】

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

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

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

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

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

Logstash下字段以及嵌套Json字段類型轉換

 

前言

從filebeat傳輸到Logstash的數據,某個字段需要由string類型裝換成float類型。但是不管怎麼改logstash的配置文件都不生效,其實官方文檔都有,但是具體細節方面的東西就得自己不斷的實踐驗證最後達到自己想要的目標了。整整一天,都在弄這一個,中間實在想放棄了。但是就如張靚穎的“終於等到你,還好沒放棄”,最後在某一篇博文得到了啟發,才解決。

 

這裏類型轉換分兩個類型:

1)字段是單純的字段,也就是直接在_source下的

2)字段是在json里的,在_source下還有嵌套一層json里的字段

 

一、單一字段

可以從下面的圖中看出,字段就在頂層機構_source下,這種情況下的Logstash配置文件設置如下:

filter {
     mutate {
     convert => { "request_time" => "float" }
     convert => { "upstream_response_time" => "float" }
     }
}

 

 

 

 

二、嵌套Json下的字段

如果需要轉換的字段是在非頂級結構下,是在一個JSON里,因為在filebeat做decode的時候指定了,如我需要轉換的字段是在jsonn的json字段里:

processors:
 - decode_json_fields:
    fields: ["message"]    #要進行解析的字段
    process_array: false   #數組是否解碼,默認值:false
    max_depth: 3           #解碼深度,默認值:1
    target: "jsonn"          #json內容解析到指定的字段,如果為空(“”),則解析到頂級結構下
    overwrite_keys: false  #如果解析出的json結構中某個字段在原始的event(在filebeat中傳輸的一條數據為一個event)中也存在,是否覆蓋

 

 

 

這種情況下的Logstash配置文件設置如下:

filter {
    mutate {
      convert => { "[jsonn][request_time]" => "float" }
      convert => { "[jsonn][upstream_response_time]" => "float" }
   }
}

 

注意:

[jsonn][request_time] 不是[jsonn].[request_time],也不是jsonn.request_time沒有點.

 

受啟發的鏈接:

https://stackoverflow.com/questions/30369148/logstash-remove-deep-field-from-json-file

 

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

【其他文章推薦】

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

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

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

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

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

學習ASP.NET Core(10)-全局日誌與xUnit系統測試

上一篇我們介紹了數據塑形,HATEOAS和內容協商,並在制器方法中完成了對應功能的添加;本章我們將介紹日誌和測試相關的概念,並添加對應的功能

一、全局日誌

在第一章介紹項目結構時,有提到.NET Core啟動時默認加載了日誌服務,且在appsetting.json文件配置了一些日誌的設置,根據設置的日誌等級的不同可以進行不同級別的信息的显示,但它無法做到輸出固定格式的log信息至本地磁盤或是數據庫,所以需要我們自己手動實現,而我們可以藉助日誌框架實現。

ps:在第7章節中我們記錄的是數據處理層方法調用的日誌信息,這裏記錄的則是ASP.NET Core WebAPI層級的日誌信息,兩者有所差異

1、引入日誌框架

.NET程序中常用的日誌框架有log4net,serilog 和Nlog,這裏我們使用Serilog來實現相關功能,在BlogSystem.Core層使用NuGet安裝Serilog.AspNetCore,同時還需要搜索Serilog.Skins安裝希望支持的功能,這裏我們希望添加對文件和控制台的輸出,所以選擇安裝的是Serilog.Skins.File和Serilog.Skins.Console

需要注意的是Serilog是不受appsetting.json的日誌設置影響的,且它可以根據命名空間重寫記錄級別。還有一點需要注意的是需要手動對Serilog對象進行資源的釋放,否則在系統運行期間,無法打開日誌文件。

2、系統添加

在BlogSystem.Core項目中添加一個Logs文件夾,並在Program類中進行Serilog對象的添加和使用,如下:

3、全局添加

1、這個時候其實系統已經使用Serilog替換了系統自帶的log對象,如下圖,Serilog會根據相關信息進行高亮显示:

2、這個時候問題就來了,我們怎麼才能進行全局的添加呢,總不能一個方法一個方法的添加吧?還記得之前我們介紹AOP時提到的過濾器Filter嗎?ASP.NET Core中一共有五類過濾器,分別是:

  • 授權過濾器Authorization Filter:優先級最高,用於確定用戶是否獲得授權。如果請求未被授權,則授權過濾器會使管道短路;
  • 資源過濾器Resource Filter:授權后運行,會在Authorization之後,Model Binding之前執行,可以實現類似緩存的功能;
  • 方法過濾器Action Filter:在控制器的Action方法執行之前和之後被調用,可以更改傳遞給操作的參數或更改從操作返回的結果;
  • 異常過濾器Exception Filter:當Action方法執行過程中出現了未處理的異常,將會進入這個過濾器進行統一處理;
  • 結果過濾器Result Filter:執行操作結果之前和之後運行,僅在action方法成功執行后才運行;

過濾器的具體執行順序如下:

3、這裏我們可以藉助異常過濾器實現全局日誌功能的添加;在在BlogSystem.Core項目添加一個Filters文件夾,添加一個名為ExceptionFilter的類,繼承IExceptionFilter接口,這裡是參考老張的哲學的簡化版本,實現如下:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Serilog;
using System;

namespace BlogSystem.Core.Filters
{
    public class ExceptionsFilter : IExceptionFilter
    {
        private readonly ILogger<ExceptionsFilter> _logger;

        public ExceptionsFilter(ILogger<ExceptionsFilter> logger)
        {
            _logger = logger;
        }

        public void OnException(ExceptionContext context)
        {
            try
            {
                //錯誤信息
                var msg = context.Exception.Message;
                //錯誤堆棧信息
                var stackTraceMsg = context.Exception.StackTrace;
                //返回信息
                context.Result = new InternalServerErrorObjectResult(new { msg, stackTraceMsg });
                //記錄錯誤日誌
                _logger.LogError(WriteLog(context.Exception));
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
            finally
            {
                //記得釋放,否則運行時無法打開日誌文件
                Log.CloseAndFlush();
            }

        }

        //返回500錯誤
        public class InternalServerErrorObjectResult : ObjectResult
        {
            public InternalServerErrorObjectResult(object value) : base(value)
            {
                StatusCode = StatusCodes.Status500InternalServerError;
            }
        }

        //自定義格式內容
        public string WriteLog(Exception ex)
        {
            return $"【異常信息】:{ex.Message} \r\n 【異常類型】:{ex.GetType().Name} \r\n【堆棧調用】:{ex.StackTrace}";
        }
    }
}

4、在Startup類的ConfigureServices方法中進行異常處理過濾器的註冊,如下:

5、我們在控制器方法中拋出一個異常,分別查看效果如下,如果覺得信息太多,可調整日誌記錄級別:

二、系統測試

這裏我們從測試的類別出發,了解下測試相關的內容,並添加相關的測試(介紹內容大部分來自微軟官方文檔,為了更易理解,從個人習慣的角度進行了修改,如有形容不當之處,可在評論區指出)

1、測試說明及分類

1、自動測試是確保軟件應用程序按照作者期望執行操作的一種絕佳方式。軟件應用有多種類型的測試,包括單元測試、集成測試、Web測試、負載測試和其他測試。單元測試用於測試個人軟件的組件或方法,並不包括如數據庫、文件系統和網絡資源類的基礎結構測試。

當然我們可以使用編寫測試的最佳方法,如測試驅動開發(TDD)所指的先編寫單元測試,再編寫該單元測試要檢查的代碼,就好比先編寫書籍的大綱,再編寫書籍。其主要目的是為了幫助開發人員編寫更簡單,更具可讀性的高效代碼。兩者區別如下(來自Edison Zhou)

2、以深度(測試的細緻程度)和廣度(測試的覆蓋程度)區分, 測試分類如下(此處內容來自solenovex):

Unit Test 單元測試:它可以測試一個類或者一個類的某個功能,但其覆蓋程度較低;

Integration Test 集成測試:它的細緻程度沒有單元測試高,但是有較好的覆蓋程度,它可以測試功能的組合,以及像數據庫或文件系統這樣的外部資源;

Subcutaneous Test 皮下測試 :其作用區域為UI層的下一層,有較好的覆蓋程度,但是深度欠佳;

UI測試:直接從UI層進行測試,覆蓋程度很高,但是深度欠佳

3、在編寫單元測試時,盡量不要引入基礎結構依賴項,這些依賴項會降低測試速度,使測試更加脆弱,我們應當將其保留供集成測試使用。可以通過遵循显示依賴項原則和使用依賴項注入避免應用程序中的這些依賴項,還可以將單元測試保留在單獨的項目中與集成測試相分離,以確保單元測試項目沒有引用或依賴於基礎結構包。

總結下常用的單元測試和集成測試,單元測試會與外部資源隔離,以保證結果的一致性;而集成測試會依賴外部資源,且覆蓋面更廣。

2、測試的目的及特徵

1、為什麼需要測試?我們從以單元測試為例從4個方面進行說明:

  • 時間人力成本:進行功能測試時,通常涉及打開應用程序,執行一系列需要遵循的步驟來驗證預期的行為,這意味着測試人員需要了解這些步驟或聯繫熟悉該步驟的人來獲取結果。對於細微的更改或者是較大的更改,都需要重複上述過程,而單元測試只需要按一下按鈕即可運行,無需測試人員了解整個系統,測試結果也取決於測試運行程序而非測試人員。
  • 防止錯誤回歸:程序更改後有時會出現舊功能異常的問題,所以測試時不僅要測試新功能還要確保舊功能的正常運行。而單元測試可以確保在更改一行代碼后重新運行整套測試,確保新代碼不會破壞現有的功能。
  • 可執行性:在給定某個輸入的情況下,特定方法的作用或行為可能不會很明顯。比如,輸入或傳遞空白字符串、null后,該方法會有怎樣的行為?而當我們使用一套命名正確的單元測試,並清楚的解釋給定的輸入和預期輸出,那麼它將可以驗證其有效性。
  • 減少代碼耦合:當代碼緊密耦合時,會難以進行單元測試,所以以創建單元測試為目的時,會在一定程度上要求我們注意代碼的解耦

2、優質的測試需要符合哪些特徵,同樣以單元測試為例:

  • 快速:成熟的項目會進行數千次的單元測試,所以應當花費非常少的時間來運行單元測試,一般來說在幾毫秒
  • 獨立:單元測試應當是獨立的,可以單獨運行,不依賴文件系統或數據庫等外部因素
  • 可重複:單元測試的結果應當保持一致,即運行期間不進行更改,返回的結果應該相同
  • 自檢查:測試應當在沒有人工交互的情況下,自動檢測是否通過
  • 及時:編寫單元測試不應該花費過多的時間,如果花費時間較長,應當考慮另外一種更易測試的設計

在具體的執行時,我們應當遵循一些最佳實踐規則,具體請參考微軟官方文檔單元測試最佳做法

3、xUnit框架介紹

常用的單元測試框架有MSTestxUnitNUnit,這裏我們以xUnit為例進行相關的說明

3.1、測試操作

首先我們要明確如何編寫測試代碼,一般來說,測試分為三個主要操作:

  • Arrange:意為安排或準備,這裏可以根據需求進行對象的創建或相關的設置;
  • Act:意為操作,這裏可以執行獲取生產代碼返回的結果或者是設置屬性;
  • Assert:意為斷言,這裏可以用來判斷某些項是否按預期進行,即測試通過還是失敗

3.2、Assert類型

Assert時通常會對不同類型的返回值進行判斷,而在xUnit中是支持多種返回值類型的,常用的類型如下:

boolean:針對方法返回值為bool的結果,可以判斷結果是true或false

string:針對方法返回值為string的結果,可以判斷結果是否相等,是否以某字符串開頭或結尾,是否包含某些字符,並支持正則表達式

數值型:針對方法返回值為數值的結果,可以判斷數值是否相等,數值是否在某個區間內,數值是否為null或非null

Collection:針對方法返回值為集合的結果,可以針對集合內所有元素或至少一個元素判斷其是否包含某某字符,兩個集合是否相等

ObjectType:針對方法返回值為某種類型的情況,可以判斷是否為預期的類型,一個類是否繼承於另一個類,兩個類是否為同一實例

Raised event:針對事件是否執行的情況,可以判斷方法內部是否執行了預期的事件

3.3、常用特性

在xUnit中還有一些常用的特性,可作用於方法或類,如下:

[Fact]:用來標註該方法為測試方法

[Trait(“Name”,”Value”)]:用來對測試方法進行分組,支持標註多個不同的組名

[Fact(Skip=”忽略說明…”)]:用來修飾需要忽略測試的方法

3.4 、性能相關

在測試時我們應當注意性能上的問題,針對一個對象供多個方法使用的情況,我們可以使用共享上下文

  • 針對一個對象供同一類中的多個方法使用時,可以將該對象提取出來,使用IClassFixture 對象將其注入到構造函數中
  • 針對一個對象供多個測試類使用的情況,可以使用ICollectionFixture 對象和[CollectionDefinition(“…”)]定義該對象

需要注意在使用IClassFixtureICollectionFixture對象時應當避免多個測試方法之間相互影響的情況

3.5、數據驅動測試

在進行測試方法時,通常我們會指定輸入值和輸出值,如希望多測試幾種情況,我們可以定義多個測試方法,但這顯然不是一個最佳的實現;在合理的情況下,我們可以將參數和數據分離,如何實現?

  • 方法一:使用[Theory]替換[Fact],將輸入輸出參數提取為方法參數,並使用多個[InlineData(“輸入參數”,”輸出參數)]來標註方法
  • 方法二:使用[Theory]替換[Fact],針對測試方法新增一個測試數據類,該類包含一個靜態屬性IEumerable<object[]>,將數據封裝為一個list后賦值給該屬性,並使用[MemberData(nameof(數據類的屬性),MemberType=typeof(數據類))]標註測試方法即可;
  • 方法三:使用外部數據如數據庫數據/Excel數據/txt數據等,其實現原理與方法二相同,只是多了一個數據獲取封裝為list的步驟;
  • 方法四:自定義一個Attribute,繼承自DataAttribute,實現其對應的方法,使用yield返回object類型的數組;使用時只需要在測試方法上方添加[Theory][自定義Attribute]即可

4、測試項目添加

4.1、添加測試項目

首先我們右鍵項目解決方案選擇添加一個項目,輸入選擇xUnit後進行添加,項目命名為BlogSystem.Core.Test,如下:

項目添加完成后我們需要添加對測試項目的引用,在解決方案中右擊依賴項選擇添加BlogSystem.Core;這裏我們預期對Controller進行測試,但後續有可能會添加其他項目的測試,所以我們建立一個Controller_Test文件夾保證項目結構相對清晰。

4.2、添加測試方法

在BlogSystem.Core.Test項目的Controller_Test文件夾下新建一個命名為UserController_Should的方法;在微軟的《單元測試的最佳做法》文檔中有提到,測試命名應該包括三個部分:①被測試方法的名稱②測試的方案③方案預期行為;實際使用時也可以對照測試的方法進行命名,這裏我們先不考慮最佳命名原則,僅對照測試方法進行命名,如下:

using Xunit;

namespace BlogSystem.Core.Test.Controller_Test
{
    public class UserController_Should
    {
        [Fact]
        public void Register_Test()
        {
            
        }
    }
}

4.3、方案選擇

1、在進行測試時,我們可以根據實際情況使用以下方案來進行測試:

  • 方案一:直接new一個Controller對象,調用其Action方法直接進行測試;適用於Controller沒有其他依賴項的情況;
  • 方案二:當有多個依賴項時,可以藉助工具來模擬實例化時的依賴項,如Moq就是一個很好的工具;當然這需要一定的學習成本;
  • 方案三:模擬Http請求的方式來調用API進行測試;NuGet中的Microsoft.AspNetCore.TestHost就支持這類情況;
  • 方案四:自定義方法實例化所有依賴項;將測試過程種需要用到的對象放到容器中並加載,其實現較為複雜;

這裏我們以測試UserController為例,其構造函數包含了接口服務實例和HttpContext對象實例,Action方法內部又有數據庫連接操作,從嚴格意義上來講測試這類方法已經脫離了單元測試的範疇,屬於集成測試,但這類測試一定程度上可以節省我們大量的重複勞動。這裏我們選擇方案三進行相關的測試。

2、如何使用TestHost對象?先來看看它的工作流程,首先它會創建一個IHostBuilder對象,並用它創建一個TestServer對象,TestServer對象可以創建HttpClient對象,該對象支持發送及響應請求,如下圖所示(來自solenovex):

在嘗試使用該對象的過程中我們會發現一個問題,創建IHostBuilder對象時需要指明類似Startup的配置項,因為這裡是測試環境,所以實際上會與BlogSystem.Core中的配置類StartUp存在一定的差異,因而這裏我們需要為測試新建立一個Startup配置類。

4.4、方法實現

1、我們在測試項目中添加名為TestServerFixture 的類和名為TestStartup的類,TestServerFixture 用來創建HttpClient對象並做一些準備工作,TestStartup類為配置類。然後使用Nuget安裝Microsoft.AspNetCore.TestHost;TestServerFixture 和TestStartup實現如下:

using Autofac.Extensions.DependencyInjection;
using BlogSystem.Core.Helpers;
using BlogSystem.Model;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Hosting;
using System;
using System.Net.Http;

namespace BlogSystem.Core.Test
{
    public static class TestServerFixture
    {
        public static IHostBuilder GetTestHost()
        {
            return Host.CreateDefaultBuilder()
           .UseServiceProviderFactory(new AutofacServiceProviderFactory())//使用autofac作為DI容器
           .ConfigureWebHostDefaults(webBuilder =>
           {
               webBuilder.UseTestServer()//建立TestServer——測試的關鍵
               .UseEnvironment("Development")
               .UseStartup<TestStartup>();
           });
        }

        //生成帶token的httpclient
        public static HttpClient GetTestClientWithToken(this IHost host)
        {
            var client = host.GetTestClient();
            client.DefaultRequestHeaders.Add("Authorization", $"Bearer {GenerateJwtToken()}");//把token加到Header中
            return client;
        }

        //生成JwtToken
        public static string GenerateJwtToken()
        {
            TokenModelJwt tokenModel = new TokenModelJwt { UserId = userData.Id, Level = userData.Level.ToString() };
            var token = JwtHelper.JwtEncrypt(tokenModel);
            return token;
        }

        //測試用戶的數據
        private static readonly User userData = new User
        {
            Account = "jordan",
            Id = new Guid("9CF2DAB5-B9DC-4910-98D8-CBB9D54E3D7B"),
            Level = Level.普通用戶
        };

    }
}
using Autofac;
using Autofac.Extras.DynamicProxy;
using BlogSystem.Common.Helpers;
using BlogSystem.Common.Helpers.SortHelper;
using BlogSystem.Core.AOP;
using BlogSystem.Core.Filters;
using BlogSystem.Core.Helpers;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace BlogSystem.Core.Test
{
    public class TestStartup
    {
        private readonly IConfiguration _configuration;

        public TestStartup(IConfiguration configuration)
        {
            _configuration = GetConfig(null);
            //傳遞Configuration對象
            JwtHelper.GetConfiguration(_configuration);
        }

        public void ConfigureServices(IServiceCollection services)
        {
            //控制器服務註冊
            services.AddControllers(setup =>
            {
                setup.ReturnHttpNotAcceptable = true;//開啟不存在請求格式則返回406狀態碼的選項
                var jsonOutputFormatter = setup.OutputFormatters.OfType<SystemTextJsonOutputFormatter>()?.FirstOrDefault();//不為空則繼續執行
                jsonOutputFormatter?.SupportedMediaTypes.Add("application/vnd.company.hateoas+json");
                setup.Filters.Add(typeof(ExceptionsFilter));//添加異常過濾器
            }).AddXmlDataContractSerializerFormatters()//開啟輸出輸入支持XML格式

            //jwt授權服務註冊
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(x =>
            {
                x.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true, //驗證密鑰
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_configuration["JwtTokenManagement:secret"])),

                    ValidateIssuer = true, //驗證發行人
                    ValidIssuer = _configuration["JwtTokenManagement:issuer"],

                    ValidateAudience = true, //驗證訂閱人
                    ValidAudience = _configuration["JwtTokenManagement:audience"],

                    RequireExpirationTime = true, //驗證過期時間
                    ValidateLifetime = true, //驗證生命周期
                    ClockSkew = TimeSpan.Zero, //緩衝過期時間,即使配置了過期時間,也要考慮過期時間+緩衝時間
                };
            });

            //註冊HttpContext存取器服務
            services.AddHttpContextAccessor();

            //自定義判斷屬性隱射關係
            services.AddTransient<IPropertyMappingService, PropertyMappingService>();

            services.AddTransient<IPropertyCheckService, PropertyCheckService>();
        }

        //configureContainer訪問AutoFac容器生成器
        public void ConfigureContainer(ContainerBuilder builder)
        {
            //獲取程序集並註冊,採用每次請求都創建一個新的對象的模式
            var assemblyBll = Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory, "BlogSystem.BLL.dll"));
            var assemblyDal = Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory, "BlogSystem.DAL.dll"));

            builder.RegisterAssemblyTypes(assemblyDal).AsImplementedInterfaces().InstancePerDependency();

            //註冊攔截器
            builder.RegisterType<LogAop>();
            //對目標類型啟用動態代理,並注入自定義攔截器攔截BLL
            builder.RegisterAssemblyTypes(assemblyBll).AsImplementedInterfaces().InstancePerDependency()
           .EnableInterfaceInterceptors().InterceptedBy(typeof(LogAop));
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler(builder =>
                {
                    builder.Run(async context =>
                    {
                        context.Response.StatusCode = 500;
                        await context.Response.WriteAsync("Unexpected Error!");
                    });
                });
            }

            app.UseRouting();

           //添加認證中間件
            app.UseAuthentication();

            //添加授權中間件
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

        private IConfiguration GetConfig(string environmentName)
        {
            var path = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;

            IConfigurationBuilder builder = new ConfigurationBuilder().SetBasePath(path)
               .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);

            if (!string.IsNullOrWhiteSpace(environmentName))
            {
                builder = builder.AddJsonFile($"appsettings.{environmentName}.json", optional: true);
            }

            builder = builder.AddEnvironmentVariables();

            return builder.Build();
        }
    }
}

2、這裏對UserController中的註冊、登錄、獲取用戶信息方法進行測試,實際上這裏的斷言並不嚴謹,會產生什麼後果?請繼續往下看

using BlogSystem.Model.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace BlogSystem.Core.Test.Controller_Test
{
    public class UserController_Should
    {
        const string _mediaType = "application/json";
        readonly Encoding _encoding = Encoding.UTF8;


        /// <summary>
        /// 用戶註冊
        /// </summary>
        [Fact]
        public async Task Register_Test()
        {
            // 1、Arrange
            var data = new RegisterViewModel { Account = "test", Password = "123456", RequirePassword = "123456" };

            StringContent content = new StringContent(JsonConvert.SerializeObject(data), _encoding, _mediaType);

            using var host = await TestServerFixture.GetTestHost().StartAsync();//啟動TestServer

            // 2、Act
            var response = await host.GetTestClient().PostAsync($"http://localhost:5000/api/user/register", content);

            var result = await response.Content.ReadAsStringAsync();

            // 3、Assert
            Assert.DoesNotContain("用戶已存在", result);
        }

        /// <summary>
        /// 用戶登錄
        /// </summary>
        [Fact]
        public async Task Login_Test()
        {
            var data = new LoginViewModel { Account = "jordan", Password = "123456" };

            StringContent content = new StringContent(JsonConvert.SerializeObject(data), _encoding, _mediaType);

            var host = await TestServerFixture.GetTestHost().StartAsync();//啟動TestServer

            var response = await host.GetTestClientWithToken().PostAsync($"http://localhost:5000/api/user/Login", content);

            var result = await response.Content.ReadAsStringAsync();

            Assert.DoesNotContain("賬號或密碼錯誤!", result);
        }

        /// <summary>
        /// 獲取用戶信息
        /// </summary>
        [Fact]
        public async Task UserInfo_Test()
        {
            string id = "jordan";

            using var host = await TestServerFixture.GetTestHost().StartAsync();//啟動TestServer

            var client = host.GetTestClient();

            var response = await client.GetAsync($"http://localhost:5000/api/user/{id}");

            var result = response.StatusCode;

            Assert.True(Equals(HttpStatusCode.OK, result)|| Equals(HttpStatusCode.NotFound, result));
        }
    }
}

4.5、異常及解決

1、添加完上述的測試方法后,我們使用打開Visual Studio自帶的測試資源管理器,點擊運行所有測試,發現提示錯誤無法加載BLL?在原先的BlogSystem.Core的StartUp類中我們是加載BLL和DAL項目的dll來達到解耦的目的,所以做了一個將dll輸出到Core項目bin文件夾的動作,但是在測試項目的TestStarup類中,我們是無法加載到BLL和DAL的。我嘗試將BLL和DAL同時輸出到兩個路徑下,但未找到對應的方法,所以這裏我採用了最簡單的解決方法,測試項目添加了對DAL和BLL的引用。再次運行,如下圖,似乎成功了??

2、我們在測試方法內部打上斷點,右擊測試方法,選擇調試測試,結果發現response參數為空,只應Assert不嚴謹導致看上去沒有問題;在各種查找后,我終於找到了解決辦法,在TestStarup類的ConfigureServices方法內部service.AddControllers方法最後加上這麼一句話即可解決 .AddApplicationPart(Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory, "BlogSystem.Core.dll")))

3、再次運行測試方法,成功!但是又發現了另外一個問題,這裏我們只是測試,但是數據庫中卻出現了我們測試添加的test賬號,如何解決?我們可以使用Microsoft.EntityFrameworkCore.InMemory庫 ,它支持使用內存數據庫進行測試,這裏暫未添加,有興趣的朋友可以自行研究。

本章完~

本人知識點有限,若文中有錯誤的地方請及時指正,方便大家更好的學習和交流。

本文部分內容參考了網絡上的視頻內容和文章,僅為學習和交流,視頻地址如下:

老張的哲學,系列教程一目錄:.netcore+vue 前後端分離

我想吃晚飯,ASP.NET Core搭建多層網站架構【12-xUnit單元測試之集成測試】

solenovex,使用 xUnit.NET 對 .NET Core 項目進行單元測試

solenovex,ASP.NET Core Web API 集成測試

微軟官方文檔,.NET Core 和 .NET Standard 中的單元測試

Edison Zhou,.NET單元測試的藝術

聲明

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

「譯」靜態單賦值小冊 – 1. 介紹

有一本小冊子Static Single Assignment Book寫的很好,內容又較少,試着翻譯一下,意譯較多(說人話),不是嚴肅的翻譯,感興趣的可以看看。頻率可能是周更。。anyway,stay tuned~

在日常編程中,名字是一個很有用的東西。這本書想傳遞的關鍵內容是對於每個不同的東西給它一個獨一無二的名字可以消除很多不確定性以及不精確性。

舉個例子,如果你無意中聽到一段對話中有’Homer’這個詞,沒有上下文的情況下你不知道他說的是Homer Simpson(辛普森)還是古希臘詩人荷馬還是你認識的某個叫Homer的人。但是只要你聽到對話提及Springfield (辛普森一家)而不是Smyrna(希臘詩歌),你就能知道他們說的是辛普森一家這個電視劇。不過話又說回來,如果每個人都有一個獨一無二的名字,那麼就不可能混淆電視劇角色和古希臘文學人物,這個問題都不會成立。

這本書主要討論靜態單賦值(Static Single Assignment Form,SSA)形式,它是一種變量的命名約定。術語static說明SSA與屬性和代碼分析相關,術語single說明SSA強制變量名具有唯一性。術語assignment表示變量的定義。舉個例子,在下面的代碼中:

x = y + 1;

變量x被賦予表達式(y+1)的值。這是一個定義,或者對於x來說是賦值語句。編譯器工程師會說上面的賦值語句將值(y+1)儲存到左值x中。

1.1 SSA定義

關於SSA最簡單,限制最少的定義如下:

“如果每個變量在程序中有且只有一個賦值語句,那麼該程序是SSA形式”

但是實際上SSA還有很多變體,有更多的限制。這些變體可能使變量定義和使用與圖論的一些特性有關,或者封裝一些特定的控制流/數據流信息。每個SSA變體都有特設的性質。基本的SSA變體將會在第二章討論,本書的第三部分還會討論更多這部分的內容。

所有SSA變體,包括上面最簡單的定義都有一個最基本屬性就是引用透明性(referential transparency),所謂引用透明性是指程序中的每個變量只有一個定義,變量的值和它所在程序的位置無關。我們可能根據分支的條件完善對於某個變量的認識。舉個例子,不用看代碼我們就知道下面if語句后緊跟着的then/else條件塊中x的值

if (x == 0)

因為x的值在這個if語句中是沒有改變的。函數式編程語言寫的程序是引用透明的,引用透明性對於形式化方法和數學推理很有用,因為表達式的值只依賴它的子表達式而不依賴求值的順序或者表達式的副作用,或者其它表達式。對於一個引用透明的程序,考慮下面的代碼片段:

x = 1;
y = x + 1;
x = 2;
z = x + 1;

一個naive(而且不正確)的分析器可能認為y和z的值相等,因為他們的定義是一樣的,都是(x+1),然而x的值取決於當前代碼位置是在第二個賦值的前面還是後面,即變量的值取決於上下文。當編譯器將這個代碼段轉換為SSA形式時,它會具有引用透明性。轉換的過程會為一個變量的多次定義使用不同的名字(譯註:x1和x2)。使用SSA形式后,只有當x1和x2相等時y和z才相等。

x1 = 1;
y = x1 + 1;
x2 = 2;
z = x2 + 1;

1.2 SSA的非形式化語義

在前一節中,我們看到了如何通過簡單的重命名將代碼轉換為SSA形式。賦值語句左邊被定義的變量叫做target,在SSA中,每個target都有唯一的名字。反過來,賦值語句右邊可以多次使用target,在這裏它們叫做source。貫穿本書,SSA的target名字定義都是變量名再加一個下標這種形式。一般來說這是不重要的實現細節,雖然它對於編譯器debug來說很有用。

φ函數是SSA最重要的一個概念,它很特別,又叫做偽賦值函數(pseudo-assignment function)。有些人也叫它notational fiction。ɸ函數的用途是合併來自不同路徑的值,一般出現在控制流的合併點。

考慮下面的代碼示例和它對應的控制流圖(Control Flow Graph,CFG)表示:

在if不同分支中,y有不同的定義。y的不同定義最終在print那個地方交匯。當編譯器將該代碼轉換為SSA形式時,y的不同定義被命名為y1和y2。print既可以使用y1也可以使用y2,這取決於if的條件。在這種情況下,需要用φ函數引入新的變量y3,它的參數是y1和y2。因此SSA版本的上述程序如下:

就放置位置來說,φ函數一般是放到控制流交匯點,即CFG中有多個前驅基本塊的那個基本塊頭部。如果有n條路徑可以進入基本塊b,那麼在基本塊b頭部的φ函數有n個參數。φ函數會動態的選擇正確的參數。φ函數根據n個參數,創建新的變量名,這個名字是唯一的,因為它要保證SSA的基本性質。因此,在上面的例子中,如果控制流從基本塊A流向下面的基本塊,那麼y3使用φ函數選擇y1作為它的值,反之φ函數使用y2作為它的值。注意CFG圖φ函數的參數y1和y2前面還加了基本塊的標籤,這種形式是比較多餘的,在本書的後面部分,這個基本塊標籤能不加就不加,除非沒了它會引起歧義。

這裏還要強調一下,如果基本塊頭部有多個φ函數,這些φ函數是并行的,即,它們是同時執行,不需要順序執行。這一點是很重要的,因為在經過一些優化,比如複寫傳播(copy propagation)后φ函數的target可能是其它φ函數的source。在SSA解構階段,φ函數會被消除(譯註:就是編譯器不需要SSA形式,想將它轉換為其它IR,這就叫SSA解構),在解構階段使用常規的複製操作序列化,這點會在17.6小結描述。這個小細節對於寄存器分配后的代碼來說是相當重要的。

嚴格來說,φ函數不能被軟件直接執行,因為進入φ函數的控制流沒有被顯式的編碼進φ函數的參數。這是可以接受的,因為φ函數通常只用於程序的靜態分析。然而,有很多擴展使得φ函數可以執行,如 φif 或者γ函數(參見第12章),它有一個額外的參數,告訴φ函數選擇那個值。關於這個會在第12章,第16章和第18章討論。

接下來我們再展示一個例子,它說明了一個循環控制流解構的SSA形式。下面是非SSA形式的程序和SSA形式的控制流圖:

SSA代碼在循環頭部新增了兩個φ函數。它們合併循環前的值定義和循環中的值的定義。

要注意不要混淆SSA和自動并行化優化中的(動態)靜態賦值這兩個概念。SSA不會阻止在程序執行的時候對一個變量的多次定義,比如,上面的SSA代碼中,變量y3和x3在循環體內,每次循環都會重定義它們。

SSA構造的詳細描述會在第3章給出,現在只需要明白下面的內容:

  1. 如果程序的交匯點的某個變量有多個定義,那麼會在交匯點插入φ函數
  2. 整數下標用於重命名原來程序中的變量x和y

1.3 與傳統數據流分析的比較

在未來的第11章我們會提到,SSA主要的一個優點是它對數據流分析(data-flow analysis)很友好。數據流分析在程序編譯的時候收集信息,為未來的代碼優化做準備。在程序運行時,這些信息會在變量間流動。靜態分析通過在控制流圖中傳播這些信息,得以捕獲關於數據流的一些事實(fact)。這種方式在傳統的數據流分析中很常見。

通常,如果程序是一種功能性的(functional)或者稀疏(sparse)的表示,如SSA形式,那麼數據流信息能程序中更高效的傳播。當程序被轉換為SSA形式時,變量在定義點被重命名。對於一個確鑿的數據流問題,比如常量傳播,它表現為一個程序點的集合,在這些程序點數據流事實可能改變。因此可以直接關聯數據流事實和變量名字,而不是在每個程序點為所有變量維護各自的數據流事實的集合,下圖展示了一個非零值分析(non-zero value analysis)

對於程序中的每個變量,分析的目標是靜態確定哪些變量在運行時包含0值(即null)。在這裏0就表示變量為null,0打一把叉表示不為null,T表示可能為null。上圖(a)表示傳統的數據流分析,我們會在六個基本塊的入口點和出處都計算一次變量x和y信息。而在上圖(b)的基於SSA的數據流分析中,我們只需要在變量定義處計算一下,然後就能獲得六個數據流事實。

對於其它的數據流問題,屬性也可能在變量定義之外發生改變,這些問題只要插入一些φ函數就能放入稀疏數據流分析的框架中,第11章會有一個例子討論這個。總的來說,目前這個例子說明了SSA能給分析算法帶來的關鍵好處是:

  1. 數據流信息直接從定義語句處傳播到使用它的地方,即通過def-use鏈,這個鏈條由SSA命名方式隱示給出。相反,傳統的數據流分析需要將信息傳遍整個程序,即便在很多地方這些信息都沒改變,或者不相關。
  2. 基於SSA的數據流分析更簡潔。在示例中,比起傳統方式,基於SSA的分析只有很少的數據流事實。

這本書的第二部分給出了一個完整的基於SSA數據流分析的描述。

1.4 此情此景此SSA

歷史背景。在整個20世紀80年代,優化編譯器技術越來越成熟,各種中間表示被提出,它們包含了數據依賴,使得數據流分析在這些中間表示上很容易進行。在這些中間表示背後的設計理念是顯式/隱式包含變量定義和使用的關係,即def-use鏈條,使得數據流信息能有效的傳播。程序依賴圖(program dependence graph)和程序依賴網(program dependence web)均屬此類IR。第12章還會討論這些風格的IR的更多細節。

靜態單賦值是由IBM Research開發的一種IR,並在20世紀80年代末的幾篇研究論文中公開發表。SSA由於其符合直覺的性質和直觀的構造算法得到了廣泛的應用。SSA給出了一個標準化的變量def-use鏈,簡化了很多數據流分析技術。

當前狀況。當前主流的商業編譯器和開源編譯器,包括GCC,LLVM,HotSpot Java虛擬機,V8 JavaScript引擎都將SSA作為程序分析中的關鍵表示。由於在SSA執行優化速度快而且高效,那些即時編譯器(JIT)會在一些高級地、與平台無關的表示(如Java字節碼,CLI字節碼,LLVM bitcode)上廣泛使用SSA。
SSA最初是為了簡化高級程序表示的變形而開發而創建的,因為其良好的特性,能夠簡化算法和減少計算複雜性。今天,SSA形式甚至被用於最後的代碼生成階段(見第四部分),即後端。好幾個工業編譯器和學術編譯器,既有靜態,也有just-in-time,都在它們的後端使用SSA,如LLVM,HotSpot,LAO,libFirm,Mono。很多使用SSA的編譯器在編譯快要結束時,即寄存器分配前才解構SSA。最近的研究甚至能在寄存器分配期間也使用SSA,SSA形式會保持到非常非常後面的機器代碼生成過程才會被解構。

SSA與高級語言。到目前為止,我們展示了在低級代碼上使用SSA形式做分析的優勢。有趣的是,在高級代碼上如果強制遵循某些準則也可能具有SSA的性質。根據SISAL語言的定義,程序自動具備引用透明性,因為變量不允許多次賦值。其它語言也能有SSA的性質,比如Java的變量加個final或者C#的變量加個const/readonly。

強制寫出具有SSA性質的高級語言程序主要好處是這些程序能具備不變形,這簡化了併發編程。豬肚的數據能在多個線程中自由的共享,沒有任何數據依賴問題。數據依賴對於多核處理器來說是一個大問題。

在函數式編程語言中,引用透明是語言的基本特性。因此函數式編程隱式具有SSA性質。第6章會介紹SSA和函數式編程。

1.5 餘下本章

本章引入了SSA的符號表示,本書的剩下部分就SSA的各個方面詳細討論。本書的終極目標是:

  1. 清晰的描述SSA能為程序分析帶來哪些好處
  2. 消除那些阻止人們使用SSA的謬誤

本節還剩下一些內容,它們與下一章的一些主題相關。

1.5.1 SSA的好處

SSA對於變量命名有嚴格要求,每個變量的名字都是獨一無二的。賦值語言和控制流交匯點會引入新的變量名。這些簡化了表達變量def-use關係的數據結構實現和變量存活範圍。本書第二部分關注基於SSA的數據流恩熙,使用SSA主要有三個好處:

編譯時受益。如果程序是SSA形式,很多編譯器優化可以高效的進行,因為引用透明性意味着數據流信息直接與變量關聯,而不是每個程序點的變量。關於這一點我們已經在1.3的非零值分析中演示過了。

編譯器開發受益。SSA使得程序分析和轉換能更容易表達。這意味着編譯器工程師能更高產,可以寫更多的pass,並且能debug更多的pass(譯註:smile)。舉個例子,基於SSA的GCC4.x的死代碼優化比非GCC3.x的非SSA死代碼優化實現總代碼少了40%。

程序運行時受益。理論上,能基於SSA實現的分析和優化也能基於其它非SSA形式。前一點提到過,基於SSA的實現代碼更少,因此很多基於SSA的編譯器優化也能更高效進行,關於這一點的示例是一類控制流不敏感分析(control-flow insensitive analysis),具體參見論文Using static single assignment form to improve flowinsensitive pointer analysis

1.5.2 SSA謬論

一些人認為SSA很複雜很繁瑣,不能高效表達程序。這本書的目的就是讓讀者免去這些擔憂。下面的表單展示了關於SSA常見的謬論,以及破除謬論的章節。

謬論 破除謬論
SSA讓變量數爆炸 第二章會回顧SSA的主要變體,一些變體引入的變量數比原始SSA形式少很多
SSA的性質難以維持 第三章和第五章討論了一些修復SSA性質的簡單技術(因為一些優化可能重寫中間表示,導致SSA性質被破壞)
SSA的性質難以維持 第三章和第十七章展示了高效且效果顯著的SSA解構算法的複製操作

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

【其他文章推薦】

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

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

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

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

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