測試)
音視頻【免費下載鏈接】vlcVLC media player - plays everything, runs anywhere. Code here: https://code.videolan.org/videolan/vlc項目地址https://gitcode.com/gh_mirrors/vl/vlc點擊查看免費下載本篇技術(shù)指南以 VLC 倉庫test/checkasm/ext/docs/writing-tests.md為核心系統(tǒng)講解如何為 checkasmVideoLAN 自研的匯編 / SIMD 優(yōu)化實現(xiàn)測試框架編寫高質(zhì)量測試。checkasm 是 VLC 項目中驗證 DSP、編解碼等優(yōu)化函數(shù)正確性與性能的關(guān)鍵基礎(chǔ)設(shè)施其完整源碼、頭文件與自測用例均位于 test/checkasm/ext。讀完本文你將掌握 checkasm 的基本測試工作流、現(xiàn)代/傳統(tǒng)兩套 API 命名、緩沖區(qū)分配與初始化、隨機參數(shù)生成、多配置循環(huán)測試、浮點容差比較、越界寫檢測、MMX 特例處理以及如何用固定種子、函數(shù)過濾和多種導(dǎo)出格式組織可復(fù)現(xiàn)的基準(zhǔn)測試。基本測試結(jié)構(gòu)八步工作流checkasm 的核心思想是在同一個測試進(jìn)程里同時調(diào)用參考實現(xiàn)通常是純 C 版本和被測實現(xiàn)通常是匯編 / SIMD 優(yōu)化版本對比輸出是否一致并可選地對新實現(xiàn)做基準(zhǔn)計時。在深入高級模式之前先熟悉基本測試結(jié)構(gòu)完整可運行示例見 getting-started.md 的 Quick Start 章節(jié)。一個典型測試的工作流包含 8 個步驟分配對齊的緩沖區(qū)存放測試數(shù)據(jù)用checkasm_declare()聲明被測函數(shù)的函數(shù)簽名用checkasm_check_func()檢查該函數(shù)是否應(yīng)當(dāng)被測試決定要不要跳過初始化測試輸入、清空輸出緩沖區(qū)分別調(diào)用參考實現(xiàn)checkasm_call_ref()與新實現(xiàn)checkasm_call_new()用checkasm_check2d()或類似函數(shù)比較兩者輸出用checkasm_bench_new()對新實現(xiàn)做基準(zhǔn)測試可選用checkasm_report()匯報結(jié)果可選。其中第 3 步至關(guān)重要checkasm_check_func()會依據(jù)命令行傳入的過濾模式--function/--test、當(dāng)前 CPU 特性集合等條件決定當(dāng)前這個函數(shù)變體是否值得測試。從 test.h 的宏定義可以看出它會同時把傳入的函數(shù)指針記錄到全局的checkasm_key_new并把查找到的參考實現(xiàn)鍵值寫入checkasm_key_ref供后續(xù)的checkasm_call_ref()/checkasm_call_new()使用#define checkasm_check_func(func, ...) \ (checkasm_key_ref \ checkasm_check_key((checkasm_key_new (CheckasmKey) (func)), __VA_ARGS__))checkasm_call_ref()與checkasm_call_new()都是宏前者展開為checkasm_call(checkasm_func_ref, ...)只做信號保護(hù)后者展開為checkasm_call_checked(checkasm_func_new, ...)除信號保護(hù)外還會檢查棧破壞、寄存器被 clobber、寄存器寬度不匹配、ABI 違規(guī)等常見匯編錯誤詳見 test.h。API 命名約定現(xiàn)代 API 與傳統(tǒng)短別名checkasm 同時支持兩套命名風(fēng)格可以在同一個測試文件中混用?,F(xiàn)代 API推薦所有函數(shù)使用checkasm_前綴checkasm_declare(void, uint8_t *dst, const uint8_t *src, int len); checkasm_check_func(dsp-func, func_name) checkasm_call_ref(dst_c, src, len); checkasm_call_new(dst_a, src, len); checkasm_check1d(uint8_t, dst_c, dst_a, len, dst); checkasm_bench_new(dst_a, src, len); checkasm_report(func_name); checkasm_fail() checkasm_alternate(buf0, buf1) // ...傳統(tǒng)/短別名 API為方便與向后兼容checkasm 提供了更短的別名declare_func(void, uint8_t *dst, const uint8_t *src, int len); check_func(dsp-func, func_name) call_ref(dst_c, src, len); call_new(dst_a, src, len); // checkasm_check1d() 沒有短別名 bench_new(dst_a, src, len); report(func_name); fail() alternate(buf0, buf1)這些別名在 test.h 的 Short-hand Aliases 分組中集中定義例如#define call_ref checkasm_call_ref、#define declare_func checkasm_declare、#define bench_new checkasm_bench_new等。值得注意的是緩沖比較函數(shù)checkasm_check1d()/checkasm_check2d()系列沒有對應(yīng)的短別名只有checkasm_check、checkasm_check_padded這類以checkasm_開頭的別名這是刻意的設(shè)計。兩種風(fēng)格完全等價且可混用。但為了文檔與新增代碼的一致性和可讀性推薦統(tǒng)一使用帶checkasm_前綴的現(xiàn)代命名。最佳實踐緩沖區(qū)分配對齊是硬要求測試優(yōu)化實現(xiàn)時緩沖區(qū)必須按平臺要求對齊很多 SIMD 指令要求 16/32/64 字節(jié)對齊。CHECKASM_ALIGN()負(fù)責(zé)聲明帶平臺相關(guān)對齊屬性的變量BUF_RECT()更進(jìn)一步聲明一塊四周帶 padding、自動計算 stride 的矩形緩沖。// 簡單數(shù)組 CHECKASM_ALIGN(uint8_t buf[1024]); // 2D 緩沖自動加 padding 并計算 stride // - 定義 dst 為指向 64x32 可用區(qū)域的指針以及 dst_stride字節(jié)為單位 BUF_RECT(uint8_t, dst, 64, 32);重要CHECKASM_ALIGN()必須對每個緩沖區(qū)單獨使用// 正確 CHECKASM_ALIGN(uint8_t buf1[32]); CHECKASM_ALIGN(uint8_t buf2[32]); // 錯誤 - 只有 buf1 會被對齊 CHECKASM_ALIGN(uint8_t buf1[32], buf2[32]);從 utils.h 的實現(xiàn)看CHECKASM_ALIGN(x)在 MSVC 下展開為__declspec(align(CHECKASM_ALIGNMENT)) x在 GCC/Clang 下展開為x __attribute__((aligned(CHECKASM_ALIGNMENT)))——它修飾的是緊跟其后的單個聲明這正是逐緩沖對齊要求的根源。BUF_RECT()宏會一次性生成一系列變量name_buf含 padding 的完整數(shù)組、name_stride以字節(jié)計的 stride、name_buf_h含 padding 的總行數(shù)以及指向可用區(qū)域起點的指針name??捎脜^(qū)域四周預(yù)留了足夠 padding為后面的越界寫檢測checkasm_check_rect_padded()打基礎(chǔ)#define BUF_RECT(type, name, w, h) \ DECL_CHECK_FUNC(*checkasm_check_impl_##name##_type, type) \ checkasm_check_impl_##type; \ CHECKASM_ALIGN(type name##_buf[((h) 32) * (CHECKASM_ROUND(w, 64) 64) 64]); \ const int name##_buf_w CHECKASM_ROUND(w, 64) 64; \ const int name##_buf_h (h) 32; \ ptrdiff_t name##_stride sizeof(type) * name##_buf_w; \ ... type *name name##_buf name##_buf_w * 16 64緩沖區(qū)初始化隨機、病態(tài)與常量三種策略checkasm 提供三類緩沖區(qū)填充函數(shù)完整清單見 utils.h 的 memory 分組// 隨機數(shù)據(jù)均勻分布 RANDOMIZE_BUF(buf); checkasm_randomize(buf, sizeof(buf)); // 等價于上面的宏 checkasm_randomize_mask8 (buf8, width, 0x0F); // 把取值范圍約束到掩碼內(nèi) checkasm_randomize_mask16(buf16, width, 1023); // 隨機 10-bit 值 // 病態(tài)測試模式邊界值、交替位、隨機字節(jié)的混合 INITIALIZE_BUF(buf); checkasm_init(buf, sizeof(buf)); // 等價于上面的宏 checkasm_init_mask16(buf16, width, (1 12) - 1); // 約束到 12-bit 范圍 // 清空為常量值 CLEAR_BUF(buf); checkasm_clear(buf, sizeof(buf)); // 等價于上面的宏 checkasm_clear16(buf16, width, 0x1234);checkasm_init()以及宏INITIALIZE_BUF()填充的是病態(tài)測試模式混合了極小值、極大值、交替位模式與隨機字節(jié)專門用于觸發(fā)潛在 bugcheckasm_clear()目前填充常量0xAA讓測試失敗時能從 hexdump 中一眼看出差異區(qū)域。對于BUF_RECT()創(chuàng)建的 2D 緩沖有對應(yīng)的矩形版本宏它們作用于含 padding 的整塊數(shù)組name_buf從而保證 padding 區(qū)域也有確定內(nèi)容可供比對BUF_RECT(uint8_t, src, 64, 32); INITIALIZE_BUF_RECT(src); // 用病態(tài)測試字節(jié)初始化 RANDOMIZE_BUF_RECT(tmp); // 填充隨機數(shù)據(jù) CLEAR_BUF_RECT(dst); // 清空為常量數(shù)據(jù)當(dāng)前為 0xAA多配置循環(huán)測試很多 DSP 函數(shù)按塊尺寸block size分派不同實現(xiàn)。應(yīng)當(dāng)在多個配置例如各種 2 的冪塊尺寸下循環(huán)測試保證覆蓋全面BUF_RECT(uint8_t, src, 128, 128); BUF_RECT(uint8_t, dst_c, 128, 128); BUF_RECT(uint8_t, dst_a, 128, 128); checkasm_declare(void, uint8_t *dst, ptrdiff_t dst_stride, const uint8_t *src, ptrdiff_t src_stride, int w, int h); // 測試各種 2 的冪塊尺寸從 4x4 到 128x128 for (int h 4; h 128; h 1) { for (int w 4; w 128; w 1) { if (checkasm_check_func(get_func(w), func_%dx%d, w, h)) { // 初始化測試數(shù)據(jù) - 放在循環(huán)內(nèi)部為每個測試選擇不同的數(shù)據(jù)模式 INITIALIZE_BUF(src); CLEAR_BUF(dst_c); CLEAR_BUF(dst_a); // 測試該配置 checkasm_call_ref(dst_c, dst_c_stride, src, src_stride, w, h); checkasm_call_new(dst_a, dst_a_stride, src, src_stride, w, h); checkasm_check_rect_padded(dst_c, dst_c_stride, dst_a, dst_a_stride, w, h, dst); // 基準(zhǔn)測試該配置 checkasm_bench_new(checkasm_alternate(dst_a, dst_c), dst_a_stride, src, src_stride, w, h); } } } checkasm_report(func);這里的checkasm_check_func()名稱支持 printf 風(fēng)格格式化如func_%dx%d會在報告中形成func_4x4、func_8x8這樣清晰的命名。使用隨機參數(shù)用checkasm_rand()及相關(guān)函數(shù)生成多樣化的測試輸入實現(xiàn)見 utils.h 的 rng 分組// 隨機整數(shù) int byte checkasm_rand() 0xFF; uint32_t flags checkasm_rand_uint32(); // 隨機浮點 double d checkasm_randf(); // [0.0, 1.0) float f (float) checkasm_randf() * 100.0f; // [0.0, 100.0) // 正態(tài)分布 double normal checkasm_rand_norm(); // mean0, stddev1 checkasm_randomize_normf(buf, len); // 用 N(0,1) 填充緩沖區(qū)rng 分組還提供checkasm_rand_int32()、checkasm_rand_dist()、checkasm_randomize_dist/distf/range/rangef等變體它們都基于CheckasmConfig中指定的種子未指定時按當(dāng)前時間取種因此可復(fù)現(xiàn)——這正是后面確定性測試一節(jié)的基礎(chǔ)。組織報告用checkasm_report()把相關(guān)的函數(shù)分組匯報。推薦的模式是一個函數(shù)組一個 check_ 函數(shù) 一次 reportstatic void check_add_functions(const DSPContext *dsp) { // 測試多個相關(guān)函數(shù) for (int bpc 8; bpc 12; bpc 2) { if (checkasm_check_func(get_add8(bpc), add8_%dbpc, bpc)) { // Test add8 } if (checkasm_check_func(get_add16(bpc), add16_%dbpc, bpc)) { // Test add16 } } // 對整個組匯報一次 checkasm_report(add); }對于只有少數(shù)幾個函數(shù)、沒有邏輯分組、或?qū)儆陔s項函數(shù)的非常簡單的測試checkasm_report()可以省略。任何其后沒有checkasm_report()調(diào)用的函數(shù)將隱式地以測試本身的名字被匯報。注意這只有在這些函數(shù)是某個測試中最后被測試的函數(shù)時才有意義因為之后任何checkasm_report()調(diào)用都會把之前所有函數(shù)納入其中該行為自 v1.0.1 起見 test.h。常見測試模式模式一2D 緩沖區(qū)處理處理帶 stride 的 2D 緩沖區(qū)如濾波、運動補償?shù)暮瘮?shù)是 checkasm 最典型的應(yīng)用場景static void check_filter(const DSPContext *dsp) { // 定義帶 padding 的 64x64 緩沖區(qū) BUF_RECT(uint8_t, src, 64, 64); BUF_RECT(uint8_t, dst_c, 64, 64); BUF_RECT(uint8_t, dst_a, 64, 64); checkasm_declare(void, uint8_t *dst, ptrdiff_t dst_stride, const uint8_t *src, ptrdiff_t src_stride, int w, int h); for (int w 4; w 64; w 1) { if (checkasm_check_func(dsp-filter, filter_w%d, w)) { // 測試多個高度 for (int h 4; h 64; h 1) { INITIALIZE_BUF_RECT(src); CLEAR_BUF_RECT(dst_c); CLEAR_BUF_RECT(dst_a); checkasm_call_ref(dst_c, dst_c_stride, src, src_stride, w, h); checkasm_call_new(dst_a, dst_a_stride, src, src_stride, w, h); // 連 padding 一起檢查 checkasm_check_rect_padded(dst_c, dst_c_stride, dst_a, dst_a_stride, w, h, dst); } // 只對全高度配置做基準(zhǔn) checkasm_bench_new(checkasm_alternate(dst_a, dst_c), dst_a_stride, src, src_stride, w, 64); } } checkasm_report(filter); }模式二有內(nèi)部狀態(tài)的函數(shù)對會修改內(nèi)部狀態(tài)或有副作用的函數(shù)關(guān)鍵在于讓參考實現(xiàn)與被測實現(xiàn)從完全相同的初始狀態(tài)出發(fā)static void check_decoder(const DecoderContext *dec) { DecoderState state_c, state_a; uint8_t bitstream[128]; // 用隨機數(shù)據(jù)初始化比特流 RANDOMIZE_BUF(bitstream); checkasm_declare(int, DecoderState *state, const uint8_t *data, int len); if (checkasm_check_func(dec-decode, decode)) { // 把兩個狀態(tài)初始化為相同 init_decoder_state(state_c, bitstream, 128); init_decoder_state(state_a, bitstream, 128); // 解碼并比較 int result_c checkasm_call_ref(state_c, bitstream, 128); int result_a checkasm_call_new(state_a, bitstream, 128); // 比較返回值 if (result_c ! result_a) { if (checkasm_fail()) { fprintf(stderr, return value mismatch: %d vs %d\n, result_c, result_a); } } // 比較最終狀態(tài)可選 // // 該檢查是否成立取決于 DecoderState 是否含 padding 字節(jié) // 或不確定的內(nèi)部狀態(tài) checkasm_check1d(uint8_t, state_c, state_a, sizeof(DecoderState), decoder state); checkasm_bench_new(state_a, bitstream, 128); } checkasm_report(decode); }注意checkasm_fail()的用法它記錄失敗并返回 1 僅當(dāng)用戶請求了詳細(xì)診斷輸出如--verbose時因此把fprintf包在if (checkasm_fail())分支里即可實現(xiàn)靜默失敗、按需診斷。模式三多輸出函數(shù)對產(chǎn)生多個輸出值的函數(shù)例如同時算方差與和需要逐一比較每個輸出static void check_stats(const DSPContext *dsp) { CHECKASM_ALIGN(uint8_t buf[64 * 64]); checkasm_declare(int, const uint8_t *buf, int len, unsigned *variance, unsigned *sum); if (checkasm_check_func(dsp-compute_stats, compute_stats)) { unsigned var_c, var_a, sum_c, sum_a; INITIALIZE_BUF(buf); int result_c checkasm_call_ref(buf, 64*64, var_c, sum_c); int result_a checkasm_call_new(buf, 64*64, var_a, sum_a); // 比較所有輸出 if (result_c ! result_a || var_c ! var_a || sum_c ! sum_a) { if (checkasm_fail()) { fprintf(stderr, result: %d vs %d, var: %u vs %u, sum: %u vs %u\n, result_c, result_a, var_c, var_a, sum_c, sum_a); } } checkasm_bench_new(buf, 64*64, var_a, sum_a); } checkasm_report(compute_stats); }模式四自定義輸入生成對于需要特定測試模式的函數(shù)例如變換類函數(shù)可以自行構(gòu)造最壞情況輸入// 生成最壞情況輸入來壓測實現(xiàn) static void generate_worst_case(uint16_t *buf, int len, int bitdepth_max) { // 創(chuàng)建倒序排列的輸入值序列 for (int i 0; i len; i) buf[i] (len - 1 - i) bitdepth_max; } static void check_transform(const DSPContext *dsp) { #define WIDTH 64 CHECKASM_ALIGN(int16_t src [WIDTH]); CHECKASM_ALIGN(int16_t dst_c[WIDTH]); CHECKASM_ALIGN(int16_t dst_a[WIDTH]); checkasm_declare(void, int16_t *dst, const int16_t *src, int width); if (checkasm_check_func(dsp-transform, transform)) { // 同時測試隨機輸入與最壞情況輸入 for (int pattern 0; pattern 2; pattern) { if (pattern 0) { INITIALIZE_BUF(src); // 隨機輸入 } else { generate_worst_case(src, WIDTH, 32767); // 最壞情況模式 } CLEAR_BUF(dst_c); CLEAR_BUF(dst_a); checkasm_call_ref(dst_c, src, WIDTH); checkasm_call_new(dst_a, src, WIDTH); checkasm_check1d(int16_t, dst_c, dst_a, WIDTH, dst); } checkasm_bench_new(checkasm_alternate(dst_a, dst_c), src, WIDTH); } checkasm_report(transform); }高級主題浮點結(jié)果比較ULP 與絕對容差匯編實現(xiàn)與 C 參考實現(xiàn)因計算順序不同浮點結(jié)果不可能逐位一致必須用容差比較。checkasm 提供兩套方案ULPUnits in Last Place末位單位容差與絕對 epsilon 容差完整 API 見 utils.h 的 floatcmp 分組static void check_float_func(const DSPContext *dsp) { #define WIDTH 128 CHECKASM_ALIGN(float src [WIDTH]); CHECKASM_ALIGN(float dst_c[WIDTH]); CHECKASM_ALIGN(float dst_a[WIDTH]); checkasm_declare(void, float *dst, const float *src, int len); if (checkasm_check_func(dsp-process_float, process_float)) { checkasm_randomize_normf(src, WIDTH); checkasm_call_ref(dst_c, src, WIDTH); checkasm_call_new(dst_a, src, WIDTH); // 用 ULP 容差比較 // 注意這里不能用 checkasm_check1d() const int max_ulp 1; checkasm_check2d(float_ulp, dst_c, 0, dst_a, 0, WIDTH, 1, dst, max_ulp); // 或用絕對 epsilon 容差 // if (!checkasm_float_near_abs_eps_array(dst_c, dst_a, 1e-6f, WIDTH)) { // checkasm_fail(); // } checkasm_bench_new(checkasm_alternate(dst_a, dst_c), src, WIDTH); } checkasm_report(process_float); }float_ulp是checkasm_check2d()支持的特殊類型名會路由到checkasm_check_impl_float_ulp()聲明見 utils.h其最后一個可變參數(shù)即為max_ulp。浮點工具集還包含checkasm_float_near_ulp()、checkasm_float_near_abs_eps_ulp()、checkasm_double_near_abs_eps()及對應(yīng)的數(shù)組版本以及無前綴的短別名float_near_ulp等。padding 與越界寫檢測很多匯編內(nèi)核會順手多寫幾個元素例如按 SIMD 寬度對齊。checkasm_check_rect_padded()系列能把 padding 區(qū)域也納入比對從而檢測函數(shù)是否寫越界static void check_bounds(const DSPContext *dsp) { BUF_RECT(uint8_t, dst_c, 64, 64); BUF_RECT(uint8_t, dst_a, 64, 64); checkasm_declare(void, uint8_t *dst, ptrdiff_t stride, int w, int h); if (checkasm_check_func(dsp-fill, fill)) { const int w 64, h 64; CLEAR_BUF_RECT(dst_c); CLEAR_BUF_RECT(dst_a); checkasm_call_ref(dst_c, dst_c_stride, w, h); checkasm_call_new(dst_a, dst_a_stride, w, h); // 標(biāo)準(zhǔn)檢查不含 padding checkasm_check2d(uint8_t, dst_c, dst_c_stride, dst_a, dst_a_stride, w, h, dst); // 帶 padding 檢測檢測 w×h 之外的寫入 checkasm_check_rect_padded(dst_c, dst_c_stride, dst_a, dst_a_stride, w, h, dst); // 允許右邊緣最多越寫 16 個元素對齊導(dǎo)致的越寫 checkasm_check_rect_padded_align(dst_c, dst_c_stride, dst_a, dst_a_stride, w, h, dst, 16, 1); checkasm_bench_new(checkasm_alternate(dst_a, dst_c), dst_a_stride, w, h); } checkasm_report(fill); }從 utils.h 的宏定義看checkasm_check_rect_padded()展開為checkasm_check2d_padded(type, ..., align_w1, align_h1, padding8)——即默認(rèn)允許 1 個元素的對齊越寫、檢查 8 個元素的 padding 帶checkasm_check_rect_padded_align()則允許你自定義align_w/align_h文檔中的16, 1表示只允許右邊緣最多越寫 16 個元素。更底層的checkasm_check2d_padded()接受align_w、align_h、padding三個額外參數(shù)align_h0可關(guān)閉上下邊緣的越寫檢查。與之配套的一維版本是checkasm_check1d_padded()但它因?qū)崿F(xiàn)原因不接受變參如需float_ulp檢查請改用align_h0的checkasm_check2d_padded()。多配置基準(zhǔn)測試與幾何平均checkasm_bench_new()可以在同一個checkasm_check_func()塊內(nèi)多次調(diào)用框架會把所有計時結(jié)果累積并以幾何平均匯報這非常適合多個尺寸合并成一個綜合數(shù)字的場景if (checkasm_check_func(dsp-filter, filter_w%d, w)) { for (int h 4; h 64; h 1) { // 對所有高度做正確性測試 checkasm_call_ref(dst_c, dst_c_stride, src, src_stride, w, h); checkasm_call_new(dst_a, dst_a_stride, src, src_stride, w, h); checkasm_check2d(uint8_t, dst_c, dst_c_stride, dst_a, dst_a_stride, w, h, dst); // 對每個配置做基準(zhǔn) checkasm_bench_new(checkasm_alternate(dst_a, dst_c), dst_a_stride, src, src_stride, w, h); } // 框架對同一 checkasm_check_func() 的所有基準(zhǔn)運行匯報幾何平均 } checkasm_report(filter_w%d, w);除此之外還有兩種策略對每個配置單獨調(diào)用checkasm_check_func()以獲得每個尺寸獨立的基準(zhǔn)報告或者只對最大的輸入尺寸調(diào)用checkasm_bench_new()以考察極限行為具體取舍討論見 benchmarking.md 的 Choosing Configurations 一節(jié)。多 Bitdepth 測試對支持多種位深bitdepth的編解碼函數(shù)一種做法是在循環(huán)內(nèi)按位深重建 DSP 上下文并約束隨機數(shù)據(jù)范圍static void check_pixfunc(void) { DSPContext dsp; #define WIDTH 64 CHECKASM_ALIGN(uint16_t src [WIDTH]); CHECKASM_ALIGN(uint16_t dst_c[WIDTH]); CHECKASM_ALIGN(uint16_t dst_a[WIDTH]); checkasm_declare(void, uint16_t *dst, const uint16_t *src, int len); for (int bpc 10; bpc 12; bpc 2) { const int bitdepth_max (1 bpc) - 1; dsp_context_init(dsp, checkasm_get_cpu_flags(), bpc); if (checkasm_check_func(dsp-process, process_%dbpc, bpc)) { // 在合法位深范圍內(nèi)隨機化 checkasm_randomize_mask16(src, WIDTH, bitdepth_max); checkasm_call_ref(dst_c, src, WIDTH); checkasm_call_new(dst_a, src, WIDTH); checkasm_check1d(uint16_t, dst_c, dst_a, WIDTH, dst); checkasm_bench_new(checkasm_alternate(dst_a, dst_c), src, WIDTH); } } checkasm_report(process); }這里checkasm_randomize_mask16(src, WIDTH, bitdepth_max)保證輸入始終落在合法位深范圍如 10-bit 時不超過 1023避免無意義的比較。另一種思路是把測試文件本身編譯多次用預(yù)處理器宏如-DBITDEPTH10區(qū)分位深。自定義失敗報告當(dāng)返回值或內(nèi)部狀態(tài)需要更細(xì)粒度的診斷時可以在checkasm_fail()分支內(nèi)打印自定義信息static void check_complex(const DSPContext *dsp) { // ... if (checkasm_check_func(dsp-complex, complex)) { for (int param 0; param 16; param) { int result_c checkasm_call_ref(param); int result_a checkasm_call_new(param); // 檢查返回值 if (result_c ! result_a) { if (checkasm_fail()) { // 僅當(dāng)用戶請求詳細(xì)錯誤診斷時才執(zhí)行此分支 fprintf(stderr, return mismatch for param%d: %d vs %d\n, param, result_c, result_a); } } } } checkasm_report(complex); }checkasm_fail()的返回值語義在 test.h 中有明確文檔1 表示失敗詳情應(yīng)當(dāng)被詳細(xì)打印0 表示靜默。它展開為checkasm_fail_func(%s:%d, __FILE__, __LINE__)自動附帶失敗位置。通過 wrapper 間接調(diào)用函數(shù)當(dāng)被測函數(shù)必須通過一個 wrapper 間接調(diào)用時使用checkasm_call()和checkasm_call_checked()調(diào)用任意輔助函數(shù)。此時checkasm_declare()聲明的函數(shù)類型必須是 wrapper 的類型而不是傳給checkasm_check_func()的內(nèi)部函數(shù)類型內(nèi)部函數(shù)的指針可以通過無類型的checkasm_key_ref/checkasm_key_new取得typedef int (my_func)(int); // 調(diào)用實際函數(shù)的 wrapper static int sum_upto_n(my_func *func, int count) { int sum 0; for (int i 0; i count; i) sum func(i); return sum; } static void check_wrapper(void) { // 聲明 wrapper 的簽名而非內(nèi)部函數(shù)的簽名 checkasm_declare(int, my_func *, int); if (checkasm_check_func(get_my_func(), my_wrapped_func)) { const int count checkasm_rand() % 100; // 把 checkasm_key_new 轉(zhuǎn)型為合適的類型并傳給 wrapper const my_func *my_ref (my_func *) checkasm_key_ref; const my_func *my_new (my_func *) checkasm_key_new; int sum_c checkasm_call(sum_upto_n, my_ref, count); int sum_a checkasm_call_checked(sum_upto_n, my_new, count); if (sum_c ! sum_a) checkasm_fail(); } }注意在這種模式下傳給checkasm_check_func()的值甚至不必是函數(shù)指針——它可以是任意指針或指針大小的整數(shù)如配置結(jié)構(gòu)體、分發(fā)表索引只要它能唯一標(biāo)識被測的底層實現(xiàn)即可。checkasm_call()只提供信號處理用于調(diào)用參考實現(xiàn)等名義上安全的代碼checkasm_call_checked()才做完整校驗棧保護(hù)、寄存器 clobber 檢測等見 test.h因此被測的優(yōu)化實現(xiàn)應(yīng)當(dāng)用checkasm_call_checked()。MMX 函數(shù)的特殊處理x86x86 上的 MMX 函數(shù)常常在返回前省略emms指令期望調(diào)用方在循環(huán)結(jié)束后手動執(zhí)行。emms用于在任何浮點代碼執(zhí)行前清空 MMX 狀態(tài)但它非常慢所以優(yōu)化過的循環(huán)通常在循環(huán)末尾才執(zhí)行一次以最小化開銷。對這種不合 ABI 的函數(shù)使用checkasm_declare_emms()static void check_sad_mmx(const DSPContext *dsp) { #define SIZE 16 CHECKASM_ALIGN(uint8_t src [SIZE * SIZE]); CHECKASM_ALIGN(uint8_t ref [SIZE * SIZE]); // 聲明 CPU_FLAG_MMX 后每當(dāng)該標(biāo)志激活時會在調(diào)用本函數(shù)后自動執(zhí)行 emms checkasm_declare_emms(CPU_FLAG_MMX, int, const uint8_t *src, const uint8_t *ref, ptrdiff_t stride); if (checkasm_check_func(dsp-sad_16x16, sad_16x16)) { INITIALIZE_BUF(src); INITIALIZE_BUF(ref); // checkasm 會在 checkasm_call_new() 之后自動調(diào)用 emms int result_c checkasm_call_ref(src, ref, SIZE); int result_a checkasm_call_new(src, ref, SIZE); if (result_c ! result_a) { if (checkasm_fail()) { fprintf(stderr, sad mismatch: %d vs %d\n, result_c, result_a); } } // 基準(zhǔn)迭代之后 checkasm 同樣會調(diào)用 emms checkasm_bench_new(src, ref, SIZE); } checkasm_report(sad); }第一個參數(shù)是 CPU 標(biāo)志掩碼如CPU_FLAG_MMX | CPU_FLAG_MMXEXT當(dāng)掩碼中的任一 CPU 標(biāo)志激活時checkasm 會在每次checkasm_call_new()和基準(zhǔn)運行之后調(diào)用emms。在非 x86 平臺上checkasm_declare_emms()等價于checkasm_declare()見 test.h 的條件宏定義。現(xiàn)代 SIMD 指令集SSE 及以后不使用 MMX 寄存器因此不需要emms。只對純 MMX 代碼或顯式使用 MMX 寄存器的 MMXEXT 函數(shù)使用checkasm_declare_emms()。提示與技巧確定性測試固定種子checkasm 使用帶種子的 PRNG 保證測試可復(fù)現(xiàn)。傳入一個位置參數(shù)即可使用指定種子./checkasm 12345 # 使用種子 12345失敗時會打印實際使用的種子便于復(fù)現(xiàn)checkasm: using random seed 987654321 ... sad_16x16: FAILED (ref:1234 new:1235)從 checkasm.h 的CheckasmConfig看種子相關(guān)的字段是seed與seed_setseed非零或seed_set非零時使用給定種子否則按當(dāng)前時間取種repeat字段還可以讓測試用連續(xù)多個種子重復(fù)運行設(shè)為 -1 時幾乎窮舉所有種子用于窮盡式測試。選擇性測試只測試匹配特定模式的函數(shù)或測試模塊# 只測試匹配模式的函數(shù) ./checkasm --functionadd_* # 只測試特定的測試模塊 ./checkasm --testmath # 兩者組合 ./checkasm --testdsp --functionblend_*--test/--function對應(yīng)CheckasmConfig中的test_pattern/function_pattern字段shell 風(fēng)格通配符NULL 表示運行全部。還可使用--list-tests、--list-functions、--list-cpu-flags查看可用的測試、函數(shù)與 CPU 特性清單getting-started.md 中的--help輸出列出了全部選項。詳細(xì)輸出啟用 verbose 模式以獲取詳細(xì)的失敗信息./checkasm --verbose當(dāng)使用內(nèi)置的checkasm_check*()系列緩沖比較助手時verbose 模式會自動對被差異區(qū)域輸出 hexdump該行為在 utils.h 的checkasm_check2d()文檔中有明確說明。基準(zhǔn)測試用適當(dāng)?shù)臅r長運行基準(zhǔn)# 快速基準(zhǔn)默認(rèn)時長 ./checkasm --bench # 更長時間的基準(zhǔn)以獲得更精確結(jié)果每個函數(shù) 10ms ./checkasm --bench --duration10000 # 以不同格式導(dǎo)出結(jié)果 ./checkasm --bench --csv results.csv ./checkasm --bench --json results.json ./checkasm --bench --html results.html--duration以微秒為單位控制每個函數(shù)的基準(zhǔn)時長--csv/--json/--html對應(yīng)CheckasmConfig.format的CHECKASM_FORMAT_*枚舉值。checkasm 的統(tǒng)計方法對數(shù)正態(tài)分布建模、log 空間線性回歸、幾何平均、no-op 開銷校正與結(jié)果解讀cycles、時間、相對加速比(vs ref)列詳見 benchmarking.md。輔助宏用輔助宏減少重復(fù)代碼#define TEST_FILTER(name, w, h) \ if (checkasm_check_func(dsp-name, #name _%dx%d, w, h)) { \ test_filter_##name(dsp, w, h); \ checkasm_bench_new(dst, dst_stride, src, src_stride, w, h); \ } // 用法 TEST_FILTER(blur, 16, 16); TEST_FILTER(blur, 32, 32); TEST_FILTER(sharpen, 16, 16);與框架配置銜接從測試到完整程序writing-tests.md聚焦于測試函數(shù)內(nèi)部的寫法而一個可運行的 checkasm 程序還需要在main()中注冊測試與 CPU 特性。這部分的完整示例見 getting-started.md 的 Quick Start核心模式是填充CheckasmConfig定義于 checkasm.h后交給checkasm_main()int main(int argc, const char *argv[]) { CheckasmConfig config { .tests tests, // CheckasmTest 數(shù)組{0} 結(jié)尾 .cpu_flags cpu_flags, // CheckasmCpuInfo 數(shù)組{0} 結(jié)尾 .cpu detect_cpu_flags(), }; return checkasm_main(config, argc, argv); }checkasm 會按cpu_flags數(shù)組中聲明的順序增量式遍歷 CPU 特性集合每個后續(xù)特性繼承之前已激活的標(biāo)志set_cpu_flags回調(diào)負(fù)責(zé)更新你的全局函數(shù)分發(fā)表測試函數(shù)內(nèi)可用checkasm_get_cpu_flags()查詢當(dāng)前激活標(biāo)志以選擇對應(yīng)實現(xiàn)??蚣軆?nèi)部還提供了一系列校驗?zāi)芰π盘柼幚韈heckasm_set_signal_handler_state()、棧破壞檢測checkasm_push_stack_guard()/checkasm_pop_stack_guard()、x86 上 MMX 狀態(tài)清理checkasm_clear_cpu_state()這些在 test.h 的 internal 分組中均有定義。checkasm 自身的自測用例如 tests/x86/tests.c就通過故意 clobber 寄存器DEF_NOOP_FUNC(clobber_r0)等與故意寫壞棧corrupt_stack_x86來驗證這些檢測機制確實生效。下一步掌握測試編寫之后可以繼續(xù)閱讀 benchmarking.md學(xué)習(xí)如何利用 checkasm 的基準(zhǔn)測試能力精確度量并比較優(yōu)化實現(xiàn)的性能——包括對數(shù)正態(tài)分布統(tǒng)計建模、log 空間線性回歸、幾何平均匯總、no-op 開銷校正以及關(guān)閉 CPU 頻率縮放、禁用 turbo boost 等系統(tǒng)級最佳實踐。贊分享音視頻【免費下載鏈接】vlcVLC media player - plays everything, runs anywhere. Code here: https://code.videolan.org/videolan/vlc項目地址https://gitcode.com/gh_mirrors/vl/vlc點擊查看免費下載相關(guān)推薦如何用 XPipe 統(tǒng)一管理所有服務(wù)器一份完整上手指南如何用 XPipe 統(tǒng)一管理所有服務(wù)器一份完整上手指南 XPipe 是一款開源的本地連接中樞加遠(yuǎn)程文件管理器讓你在桌面上用統(tǒng)一界面管理 SSH、Docker音視頻3步掌握WanVideo_comfyComfyUI視頻生成模型終極整合指南3步掌握WanVideo_comfyComfyUI視頻生成模型終極整合指南 WanVideo_comfy是一個專為ComfyUI設(shè)計的視頻生成模型整合工具它音視頻Snapshot report for test.jsSnapshot report for test.js The actual snapshot is saved in test.js.snap . Gener測試上一篇【親測免費】 jOOQ - 更優(yōu)雅地處理 Java SQL 編程下一篇【親測免費】 Crawler4J - Web爬蟲框架創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考