您好,登錄后才能下訂單哦!
一、Query-diff測試發現的問題
Query-diff是檢索端常用的測試方法,其思想是使用一組相同的檢索信息分別請求一個系統或模塊的基線版本和待測版本。通常,基線版本和待測版本只存在少量差異(程序功能/配置等)。發送請求后,比較兩個版本返回的檢索結果,從而驗證差異是否對最終計算結果造成了影響。
本case中的被測模塊A由C++編寫,輸出的核心數據為一個單精度浮點數,記為Q。
在A模塊某次升級后執行query-diff測試時,發現Q值存在精度diff,比例約為1%,最大diff在小數萬分位,而該次升級預期是無diff的。
二、深入追查
通常出現diff,首先要明確追查的方向,如果一眼看不出原因,就需要使用排除法來逐個驗證懷疑對象,縮小范圍,減小不必要的精力投入。于是列出了兩大排查方向:環境或程序。
先看環境:
l 在環境現場仔細檢查了新舊環境的配置和詞表,符合預期,排除了環境搭建工具的因素。
l 由于此次升級是前向兼容的,將新舊環境的配置和詞表統一,重新測試,diff復現,排除了配置差異的因素。
環境似乎沒有問題,再來驗證程序:
l 因已做了多組測試,驗證結果沒有改變,排除了隨機策略diff的可能。
l 打印debug日志,檢查了處理過程中的每一步中間結果,均無問題,只在計算Q值的最后環節出現了diff,相繼排除了線程臟數據,進程級cache臟數據,變量類型轉換等風險點。
l 為徹底確認,直接將新舊環境里的程序都替換為新版本,重新測試,如果真是程序所致,應當無diff。然而,diff復現了!明明沒有隨機diff的啊?!!
此時排查到了瓶頸,環境和程序的原因似乎都不對。
冷靜下來重新思考,之前的排查分別把環境的概念解釋為使用的配置和詞表,認為兩者相同,環境就相同。這是片面的,環境的含義還應當包含系統和硬件的編譯環境和運行環境。于是有了新的驗證思路:
l 新舊版本的程序都使用公司的云編譯集群產出,應當沒有問題,不過為防止想當然,還是認真檢查了編譯參數并在本地相同機器重新編譯了新舊版本,確認diff復現,排除編譯因素;
l 將新舊環境拷貝到同一臺機器,重壓請求,diff消失!確認為運行環境因素
運行環境包括操作系統和硬件層面,趁熱打鐵,繼續追查:
l 確認出現diff的兩臺機器操作系統一致,均為centos 4.3,排除了操作系統;
l 硬盤和內存的型號差異造成diff的可能性較小,暫不驗證;
l 新環境所在機器cpu版本Xeon E5645,舊環境所在機器cpu版本 Xeon E5-2620,懷疑cpu型號不同所致,另找了一臺與舊環境cpu一致的機器部署新環境,重新測試,diff消失,目標鎖定cpu。
三、揭開真相
分析cpu,在簡單排除了核數,最大線程數,一二三級緩存的嫌疑后,cpu特性列表中的指令集差異引起了我的注意。
補充知識一:cpu指令集的作用
指令集是存儲在CPU內部,對CPU運算進行指導和優化的硬程序。擁有這些指令集,CPU就可以更高效地運行。為解釋指令集的優化方式,得提到兩種技術:SISD(單指令單數據)和SIMD(單指令多數據)。
以加法指令為例,使用SISD的CPU對加法指令譯碼后,執行部件先訪問內存,取得第一個操作數,之后再一次訪問內存,取得第二個操作數,后才能進行求和運算。而在使用SIMD的CPU中,指令譯碼后幾個執行部件同時訪問內存,一次性獲得所有操作數進行運算。這個特點使SIMD特別適合于數據密集型運算。
Cpu指令集中的SSE系列和AVX用于浮點數運算,而AVX正是兩個cpu的差異之一,嫌疑很大。現在需要找到程序使用AVX進行優化的證據。
可是,在ASQ模塊中并沒有直接優化的代碼邏輯,涉及Q值計算的程序中雖然調用了靜態libA的接口,而libA的代碼也未使用指令集。不過,libA聯編了靜態libB,于是一路往底層追查,查到編譯依賴的第四層,是IDL提供的libX,代碼保密無法查看。
只好向相關RD請教,RD告知libX中確實使用了SSE指令優化,以及Intel提供的數學函數庫MKL,卻沒有用到AVX。
難道又是條走不通的死路?抱著最后一點希望,查詢了MKL在intel官方的介紹發現意外收獲,MKL中引入了AVX優化!
現在還差最后一步,得確認AVX就是diff來源的元兇。很快,在intel的產品手中找到了進一步的證據。
AVX2中的FMA指令,在矩陣乘法、點積、多項式評估等涉及浮點數運算方面的效率和精度相對以往的指令集都有所提升,因為FMA可以將乘法與累加操作一次性完成。官方論壇里也找到了相關技術人員的帖子佐證:
補充知識二:計算機中浮點數存儲方式
float和double在存儲方式上都是遵從IEEE的規范的,float遵從的是IEEE R32.24 ,而double 遵從的是R64.53。
無論是單精度還是雙精度在存儲中都分為三個部分:
1. 符號位(Sign) : 0代表正,1代表為負
2. 指數位(Exponent):用于存儲科學計數法中的指數數據,并且采用移位存儲
3. 尾數部分(Mantissa):尾數部分
其中float的存儲方式如下表所示:
硬件層面上,cpu的浮點運算邏輯都是放在FPU(浮點運算單元)上實現的(無論SSE還是AVX),FPU的默認計算精度是80bit,而SSE和AVX輸出的float精度沒那么高(均為32bit),如果FPU中計算精度存在差異(前提是均大于32bit),計算輸出時截斷為32bit再存入內存,必然會因近似截斷造成結果diff。
由于intel底層算法保密,只能猜測AVX和SSE的優化函數實現時設置的FPU精度有所不同,但精度差異的結論是確定的。
此時真相已浮出水面:AVX的FMA相比SSE精度上多1bit,存在迭代計算時,差異將會累計。而Q值的產生經歷復雜的矩陣運算,這個微小的1bit差異被放大至小數點萬分位。同時,Intel保證了各機器的兼容性,MKL的代碼在不支持AVX的cpu上運行時會被降級為SSE。
補充知識三:使用SSE和AVX優化程序的方法
仍以加法指令為例,對于相關頭文件的引入和編譯指令相關準備此處不進行介紹,可參考相關資料。
基本版:
簡單地循環累加求和。
SSE優化版
SSE寄存器128bit,16字節,一次可以存4個單精度浮點數,可以每4個一組存入寄存器,使用內置加法函數求和,之后再對4個分組和進行相加,最后加上分組剩余的幾項,得到最終結果。
AVX優化版
AVX優化方式與SSE類似,但AVX寄存器使用256bit,32字節,可以存8個單精度浮點數,需要每8個float一組存入寄存器。
現在隨機生成輸入數組,撰寫簡單的測試用例,就可以驗證優化的效果了,以下是三種算法的性能比較,單位為每秒可累加float的數量。結果中,SSE效率提升到普通版的4倍,而AVX是8倍!
四、總結和啟示
問題總結:
l Query-diff兼容性測試時發現模塊A新舊版本計算出的Q值存在diff;
l 排查后,確定精度diff來自程序因運行環境cpu支持的浮點數指令集差異(AVX/SSE)
l 該case中diff占比和絕對值均較小,目前雖不至影響線上服務,但若算法進一步復雜,diff積累至百分位,便會導致策略失效。
l 其他模塊的浮點數運算若用到指令集優化,也需要排查是否相同問題。
解決方案:
l 分配測試資源時,保證新舊環境所在機器cpu一致;
l 執行query-diff前加入環境檢查機制,再次確認硬件無差異;
l 線上部署服務時,也需要確定機器支持AVX指令集,達到性能和精度最優;
l 排查其他模塊是否有類似使用指令集優化的情況,提前規避風險。
啟發和建議:
l 浮點數運算密集型程序可考慮使用SSE/AVX等指令集函數優化性能,通常可顯著提高運行效率(SSE:4倍,AVX:8倍);
l 使用指令集時注意控制迭代使用的次數(即將指令集函數的輸出再次作為指令集函數的輸入),避免精度diff累積到不容忽視的程度;
l 可以將query-diff測試應用到更多的兼容性測試場景中,如比較CPU,操作系統,基礎庫等底層系統和硬件差異對應用程序的影響。
軟件工程離不開硬件的支持,編譯、運行環境的差異都有可能造成服務性能的差別和最終計算結果的差別。此類問題,在開發、測試、上線各個階段都需要特別注意。做一個“軟硬結合”的程序員很重要!
參考資料:
【1】 https://software.intel.com/zh-cn/articles/whats-new-in-intel-mkl
【2】 https://software.intel.com/zh-cn/articles/intel-xeon-processor-e7-88004800-v3-product-family-technical-overview
【3】 https://software.intel.com/en-us/forums/topic/507004
【4】 http://www.cnblogs.com/zyl910/archive/2012/10/22/simdsumfloat.html
百度MTC是業界領先的移動應用測試服務平臺,為廣大開發者在移動應用測試中面臨的成本、技術和效率問題提供解決方案。同時分享行業領先的百度技術,作者來自百度員工和業界領袖等。
>>如有問題,歡迎與我溝通
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。