編碼驗證算法是一種驗證數據序列編碼格式的算法,比較典型的有UTF-8編碼驗證算法。UTF-8驗證算法用於檢查數據序列是否符合UTF-8編碼規範,好比說對經常使用UTF-8編碼的郵件、網頁及應用數據作編碼驗證時,就可使用UTF-8驗證算法。 本文以Go語言開源社區的UTF-8驗證算法優化案例爲例,講解通用的算法分析、優化方法。git
摘自OptimizeLab: https://github.com/OptimizeLab/docs github
做者:zaneChou1
1. Go語言的UTF-8編碼驗證算法
Go語言實現了UTF-8編碼驗證算法用於檢查UTF-8編碼數據,主要基於UTF-8的可變長編碼特色設計了驗證算法,UTF-8編碼使用1到4個字節爲每一個字符編碼,ASCII編碼屬於UTF-8編碼長度爲1個字節的狀況。 UTF-8驗證算法針對這個特色,先取一個字符判斷是否屬於ASCII編碼,再檢查是否屬於其餘類型的UTF-8編碼。代碼以下:golang
// 優化前的UTF-8驗證算法,先取一個字節檢查是否爲ASCII,再驗證字符是否屬於其餘類型的UTF-8編碼 func Valid(p []byte) bool { n := len(p) for i := 0; i < n; { // 驗證byte字符是否屬於ASCII編碼,當ASCII編碼數量不少時,算法運算效率會很慢。 pi := p[i] if pi < RuneSelf { i++ continue } // 驗證byte字符是否屬於其餘類型的UTF8編碼 x := first[pi] if x == xx { return false // Illegal starter byte. } size := int(x & 7) if i+size > n { return false // Short or invalid. } accept := acceptRanges[x>>4] if c := p[i+1]; c < accept.lo || accept.hi < c { return false } else if size == 2 { } else if c := p[i+2]; c < locb || hicb < c { return false } else if size == 3 { } else if c := p[i+3]; c < locb || hicb < c { return false } i += size } return true }
2. 場景及問題分析
經過分析Go語言優化前的UTF-8驗證算法,發現它也用於ASCII字符的檢查。在長篇的英文電子郵件中,連續出現大量的ASCII編碼也是常見的,下圖這篇英文郵件包含1705個ASCII字符。算法
使用上述的UTF-8驗證算法對英文郵件內容進行編碼檢查時,須要進行1705次比較操做,以下圖:編程
因而可知,在須要驗證大量ASCII編碼數據的場景下,優化前的UTF-8編碼驗證算法採用單個字符比較的方式檢查編碼,直到循環檢查完整個數據,算法的運行耗時大,性能有待提高。函數
3. 優化方案及實現
針對UTF-8編碼驗證算法中處理ASCII編碼字符檢查次數多、運行耗時大的問題,能夠利用並行化編程思想,一次同時處理多個ASCII編碼字符的檢查,減小比較的次數,加快驗證速度,提高算法性能。 Go語言的UTF-8驗證算法應用了基於並行化編程思想的算法優化方案,一次同時檢查8個ASCII編碼,大大提高了算法的運行性能。 算法優化先後的代碼對好比下:性能
優化後的代碼分析以下:學習
// 優化後的代碼,基於並行化編程思想,一次檢查8個byte是否爲ASCII字符 func Valid(p []byte) bool { // 每一個輪次同時檢查8個byte是否爲ASCII字符 for len(p) >= 8 { // 使用兩個 uint32 變量 first32 和 second32,分別存儲了4個byte數據 first32 := uint32(p[0]) | uint32(p[1])<<8 | uint32(p[2])<<16 | uint32(p[3])<<24 second32 := uint32(p[4]) | uint32(p[5])<<8 | uint32(p[6])<<16 | uint32(p[7])<<24 // 任一個ASCII字符與0x80的與結果都是0,經過(first32|second32)與0x80808080的與操做實現了8個byte的ASCII字符檢驗。 if (first32|second32)&0x80808080 != 0 { break } p = p[8:] } // 每次驗證一個byte是否爲UTF-8編碼 ...... }
爲了進一步分析並行化的優化技術,使用反彙編的方法獲得了算法優化先後的彙編代碼。優化前的彙編代碼以下:測試
優化後的彙編代碼以下:優化
分析發現,優化前的代碼,使用1個unit8變量存儲數據進行編碼驗證,對應到彙編代碼使用MOVBU
指令取一個B(1個byte)的數據到寄存器R4,一次驗證1個byte數據的編碼。 優化後的代碼,使用2個unit32變量存儲數據進行編碼驗證,對應到彙編代碼使用MOVWU
指令分別取一個W(4個byte)的數據到寄存器R3和R4,一次驗證8個byte數據的編碼。 該優化方法經過在Go語言代碼中使用存儲數據更多的unit32變量類型,增長了彙編指令中寄存器存儲的數據量,在寄存器作比較操做時,實現了更多編碼數據的並行化驗證。
4. 優化結果
使用Go benchmark測試優化先後的算法性能,再用Go benchstat對比優化先後的性能測試結果,整理到以下表格:
測試項 | 測試用例 | 優化前每操做耗時 time/op | 優化後每操做耗時 time/op | 耗時對比 |
---|---|---|---|---|
BenchmarkValidTenASCIIChars-8 | 長度爲10的byte切片 | 15.8 ns/op | 8.00 ns/op | -49.37% |
BenchmarkValidStringTenASCIIChars-8 | 長度爲10的字符串 | 12.8 ns/op | 8.04 ns/op | -37.19% |
[注]-8
表示函數運行時的GOMAXPROCS值,ns/op
表示函數每次執行的平均納秒耗時。
性能測試結果顯示,UTF-8編碼驗證算法優化後,驗證ASCII編碼的平均耗時減少,性能提高明顯最高達49%。
5. 總結
Go語言的UTF-8驗證算法優化案例,從一個具體的場景出發分析算法存在的性能問題,給出了基於並行化編程思想的優化方案,並最終驗證了優化結果,是一個值得學習借鑑的算法優化實踐。 案例中的並行化編程思想不只能夠用於優化數據的編碼驗證,更是能用於優化數據的編碼、解碼、壓縮和存儲等多種場景。
若是對開源或優化技術感興趣,歡迎下方留言或者經過https://github.com/OptimizeLab/docs/issues聯繫咱們。