使用check库ck_assert断言出现严重性能损耗的技术求助
问题原因分析
你遇到的ck_assert性能问题核心在于每次断言成功时都会调用_mark_point函数,而标准assert在断言成功时几乎没有开销(甚至在定义NDEBUG后会被预处理器完全移除)。
_mark_point并非空操作,它负责向Check测试框架报告断言成功的位置,用于统计测试覆盖率和检查点计数。这个动态链接库中的实现涉及:
- 线程安全的计数器更新(适配Check的多线程测试支持)
- 文件、行号的字符串处理
- 框架内部状态的维护
单次调用的开销微乎其微,但你的循环执行了近250万次,每次循环包含3次断言,累计750万次_mark_point调用,最终导致性能急剧下降。
解决方案
1. 批量断言,减少_mark_point调用次数
将循环内的多次断言合并为单次批量验证,大幅降低框架交互次数。示例:
START_TEST(julian_gregorian) { uint32_t jd = 0; uint32_t out_jd = 0; uint32_t in_jd; int16_t gyi; uint8_t gmi; uint16_t gdi; int16_t gyo; uint8_t gmo; uint16_t gdo; int test_passed = 1; uint32_t failed_jd = 0; for (jd = 0; jd < 2488069; jd++) { test_gregorian_calendar(ctx, jd, &gyi, &gmi, &gdi, &gyo, &gmo, &gdo); if (!(gyi == gyo && gmi == gmo && gdi == gdo)) { test_passed = 0; failed_jd = jd; break; } } ck_assert_msg(test_passed, "Failed at JD: %u", failed_jd); } END_TEST
这种方式仅调用1次_mark_point,性能接近标准assert,同时保留Check框架的失败定位能力。
2. 实现条件切换的轻量断言宏
如果需要保留逐次断言的失败定位能力,可以自定义宏,在性能测试阶段用assert,正式测试时切换回ck_assert:
#ifdef PERFORMANCE_TEST #define fast_assert(expr) assert(expr) #else #define fast_assert(expr) ck_assert(expr) #endif
编译时通过-DPERFORMANCE_TEST=1启用快速模式,日常测试关闭该宏以保留Check的完整功能。
3. 优化Check框架运行模式
通过调整框架配置减少额外开销:
- 设置环境变量
CK_FORK=no禁用测试进程隔离(注意:测试崩溃会影响整个进程) - 用优化选项(如
-O2)编译Check库,默认Debug编译会带来不必要的性能损耗
4. 拆分测试流程
先用标准assert完成快速功能验证,确认逻辑正确后,再用ck_assert执行一次测试生成正式报告;或仅在循环中用assert,在循环外添加少量ck_assert满足框架的检查计数要求。
内容的提问来源于stack exchange,提问作者Soroush Rabiei
相关产品推荐
相关产品推荐

