C语言中使用函数式宏测量函数调用的执行时间
C语言中用
clock()测量函数执行时间的实现与优化问题 在C语言中,标准库函数clock()是测量函数执行时间的常用方法之一。以下是对比两个不同原型函数执行时间的基础实现:
基础实现示例
#include <stdio.h> #include <time.h> int f(int n) { return n < 3 ? 1 : f(n-1) + f(n-2); } int g(int n, int a, int b) { return n < 3 ? b : g(n-1, b, a+b); } int main(void) { double start, elapsed1, elapsed2; printf("n f g\n"); for(int n = 30; n <= 40; n++) { start = (double) clock(); f(n); elapsed1 = (clock() - start) / CLOCKS_PER_SEC; start = (double) clock(); g(n, 1, 1); elapsed2 = ((double) clock() - start) / CLOCKS_PER_SEC; printf("%d %.1f %.1f\n", n, elapsed1, elapsed2); } return 0; }
宏简化版本
由于C语言没有泛型函数指针,我们可以用函数式宏来简化重复的计时代码,减少冗余:
#include <stdio.h> #include <time.h> double START, ELAPSED; #define CPUTIME(FCALL) (START = (double) clock(), FCALL, ELAPSED = ((double) clock() - START) / CLOCKS_PER_SEC) int f(int n) { return n < 3 ? 1 : f(n-1) + f(n-2); } int g(int n, int a, int b) { return n < 3 ? b : g(n-1,b,a+b); } int main(void) { printf("n f g\n"); for(int n = 30; n <= 40; n++) printf("%d %.1f %.1f\n", n, CPUTIME(f(n)), CPUTIME(g(n, 1, 1)) ); return 0; }
技术问询
- 能否让
START和ELAPSED变量在宏定义内局部化,同时仍可将该宏作为非void“函数”调用? - 有没有不使用宏定义的更简洁代码实现方式?
问题解答
问题1:局部化宏内变量并保留非void调用特性
可以实现,借助GNU C扩展的复合语句(({ ... })),它允许在块内定义局部变量,并且整个复合语句的返回值是块内最后一个表达式的结果。这样既可以把计时变量局部化,又能让宏像函数一样返回时间值:
修改后的宏定义及示例代码:
#include <stdio.h> #include <time.h> #define CPUTIME(FCALL) ({ \ clock_t _start = clock(); \ FCALL; \ (double)(clock() - _start) / CLOCKS_PER_SEC; \ }) int f(int n) { return n < 3 ? 1 : f(n-1) + f(n-2); } int g(int n, int a, int b) { return n < 3 ? b : g(n-1, b, a+b); } int main(void) { printf("n f g\n"); for(int n = 30; n <= 40; n++) printf("%d %.1f %.1f\n", n, CPUTIME(f(n)), CPUTIME(g(n, 1, 1)) ); return 0; }
- 用下划线开头的
_start作为局部变量,避免和用户代码中的变量名冲突 - 复合语句最后一行的计算结果就是宏的返回值,完美适配
printf等场景的表达式调用 - 如果需要严格兼容C标准(不依赖GNU扩展),无法直接在宏内定义局部变量同时返回值,只能退而求其次用静态变量(但存在线程安全问题),因此推荐上述GNU扩展方案。
问题2:不使用宏的简洁实现
由于C语言缺乏泛型函数支持,不使用宏的话很难做到和宏一样的极致简洁,但可以通过封装核心计时逻辑来减少重复代码:
方案:封装计时开始/结束为inline函数
#include <stdio.h> #include <time.h> // 封装计时开始逻辑 inline double measure_start(void) { return (double)clock(); } // 封装计时结束与计算逻辑 inline double measure_end(double start) { return ((double)clock() - start) / CLOCKS_PER_SEC; } int f(int n) { return n < 3 ? 1 : f(n-1) + f(n-2); } int g(int n, int a, int b) { return n < 3 ? b : g(n-1, b, a+b); } int main(void) { printf("n f g\n"); for(int n = 30; n <= 40; n++) { double s1 = measure_start(); f(n); double e1 = measure_end(s1); double s2 = measure_start(); g(n,1,1); double e2 = measure_end(s2); printf("%d %.1f %.1f\n", n, e1, e2); } return 0; }
这种方式把重复的时钟获取和计算逻辑封装成内联函数,代码比原始版本更整洁,且完全不依赖宏。缺点是每次计时需要手动调用两次函数,无法像宏那样用一行代码完成整个计时流程。
如果尝试用函数指针或回调来适配不同签名的函数,需要为每个函数写适配包装,反而会增加代码量,不如上述方案简洁。
内容的提问来源于stack exchange,提问作者slago
相关产品推荐
相关产品推荐

