如何优化 CFD C 代码中的决策
How to optimize a decision in a CFD C code
我想优化 CFD 代码中不同功能的使用,用户可以在运行时通过程序读取的配置文件选择这些功能。
我想出了一个最小的工作示例,其中有两个单独的函数和一个输入。一种将输入平方,一种将其立方。通过命令行选项,用户可以选择要使用的功能。 squares/cubes 一堆数字的代码(它计算 x^2 或 x^3 从 0 到 1 的积分,具体取决于选择的函数)在 for 循环中并输出结果。第一个变体只是 for 循环 (case1) 中的一个 switch case。我尝试的第二件事是一个函数指针,它被设置在循环之前 (case2)。我做的第三件事是有选择地只编译用户打算使用预处理器命令(案例 3)使用的函数。
案例 1:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
double f_square(double x) {return x * x;}
double f_cube(double x) {return x * x * x;}
int main(int argc, char *argv[])
{
double x;
double sum = 0;
double del_x = 4e-10;
printf("Speed test -- no optimisation\n");
clock_t startClock = clock();
for (x = 0; x < 1; x += del_x) {
switch (argv[1][0]) {
case '2':
sum += f_square(x) * del_x;
break;
case '3':
sum += f_cube(x) * del_x;
break;
default:
printf("Invalid choice! Abort\n");
exit(1);
}
}
clock_t endClock = clock();
printf("Int_{0}^{1} x^%c: %.8g\n", argv[1][0], sum);
printf("Execution time: %.6f\n", (endClock - startClock) / (double)CLOCKS_PER_SEC);
}
案例2:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
double f_square(double x) {return x * x;}
double f_cube(double x) {return x * x * x;}
int main(int argc, char *argv[])
{
double x;
double sum = 0;
double del_x = 4e-10;
double (*f)(double);
printf("Speed test -- function pointers\n");
switch (argv[1][0]) {
case '2':
f = &f_square;
break;
case '3':
f = &f_cube;
break;
default:
printf("Invalid choice! Abort\n");
exit(1);
}
clock_t startClock = clock();
for (x = 0; x < 1; x += del_x) {
sum += f(x) * del_x;
}
clock_t endClock = clock();
printf("Int_{0}^{1} x^%c: %.8g\n", argv[1][0], sum);
printf("Execution time: %.6f\n", (endClock - startClock) / (double)CLOCKS_PER_SEC);
}
案例 3:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#ifdef SQUARE
double f(double x) {return x * x;}
#endif
#ifdef CUBE
double f(double x) {return x * x * x;}
#endif
int main(void)
{
double x;
double sum = 0;
double del_x = 4e-10;
printf("Speed test -- selective compilation\n");
clock_t startClock = clock();
for (x = 0; x < 1; x += del_x) {
sum += f(x) * del_x;
}
clock_t endClock = clock();
#ifdef SQUARE
printf("Int_{0}^{1} x^2: %.8g\n", sum);
#endif
#ifdef CUBE
printf("Int_{0}^{1} x^3: %.8g\n", sum);
#endif
printf("Execution time: %.6f\n", (endClock - startClock) / (double)CLOCKS_PER_SEC);
}
在测量执行时间时我发现了一些奇怪的事情:
- 使用 O0 我得到了我预期的分布,case3 是最快的,其次是 case2,然后是 case1
- O1--O3 case2 的表现总是比 case1 和 case3 差很多
这里有一些比较执行时间的图片
- Compiled with O0
- Compiled with O1
- Compiled with O3.
这让我很困惑,我想知道我可以做些什么才能在不损失性能的情况下使用函数指针,因为出于灵活性原因,我真的很想使用函数指针。
=> 为什么函数指针这么慢?
我想补充一点,我不是软件工程师,而是航空航天工程专业的学生,遗憾的是我们没有太多的编程课程,
所以每个细节都可能有所帮助。
下面是类似功能的两个实现的反汇编视图:https://c.godbolt.org/z/l24Zhl
请注意,使用 -O2,第一个方法内联对 f_cube
和 f_square
的调用(注意没有调用程序集中的函数),但第二个版本没有。
第一个版本很可能会进一步 sped-up 由于处理器上的 Branch Prediction。
您是否分析过您的代码并发现此区域是瓶颈?请记住,首先优化 most-used 代码可以最大程度地提高速度。记住:先做起来,再做起来快。
我想优化 CFD 代码中不同功能的使用,用户可以在运行时通过程序读取的配置文件选择这些功能。
我想出了一个最小的工作示例,其中有两个单独的函数和一个输入。一种将输入平方,一种将其立方。通过命令行选项,用户可以选择要使用的功能。 squares/cubes 一堆数字的代码(它计算 x^2 或 x^3 从 0 到 1 的积分,具体取决于选择的函数)在 for 循环中并输出结果。第一个变体只是 for 循环 (case1) 中的一个 switch case。我尝试的第二件事是一个函数指针,它被设置在循环之前 (case2)。我做的第三件事是有选择地只编译用户打算使用预处理器命令(案例 3)使用的函数。
案例 1:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
double f_square(double x) {return x * x;}
double f_cube(double x) {return x * x * x;}
int main(int argc, char *argv[])
{
double x;
double sum = 0;
double del_x = 4e-10;
printf("Speed test -- no optimisation\n");
clock_t startClock = clock();
for (x = 0; x < 1; x += del_x) {
switch (argv[1][0]) {
case '2':
sum += f_square(x) * del_x;
break;
case '3':
sum += f_cube(x) * del_x;
break;
default:
printf("Invalid choice! Abort\n");
exit(1);
}
}
clock_t endClock = clock();
printf("Int_{0}^{1} x^%c: %.8g\n", argv[1][0], sum);
printf("Execution time: %.6f\n", (endClock - startClock) / (double)CLOCKS_PER_SEC);
}
案例2:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
double f_square(double x) {return x * x;}
double f_cube(double x) {return x * x * x;}
int main(int argc, char *argv[])
{
double x;
double sum = 0;
double del_x = 4e-10;
double (*f)(double);
printf("Speed test -- function pointers\n");
switch (argv[1][0]) {
case '2':
f = &f_square;
break;
case '3':
f = &f_cube;
break;
default:
printf("Invalid choice! Abort\n");
exit(1);
}
clock_t startClock = clock();
for (x = 0; x < 1; x += del_x) {
sum += f(x) * del_x;
}
clock_t endClock = clock();
printf("Int_{0}^{1} x^%c: %.8g\n", argv[1][0], sum);
printf("Execution time: %.6f\n", (endClock - startClock) / (double)CLOCKS_PER_SEC);
}
案例 3:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#ifdef SQUARE
double f(double x) {return x * x;}
#endif
#ifdef CUBE
double f(double x) {return x * x * x;}
#endif
int main(void)
{
double x;
double sum = 0;
double del_x = 4e-10;
printf("Speed test -- selective compilation\n");
clock_t startClock = clock();
for (x = 0; x < 1; x += del_x) {
sum += f(x) * del_x;
}
clock_t endClock = clock();
#ifdef SQUARE
printf("Int_{0}^{1} x^2: %.8g\n", sum);
#endif
#ifdef CUBE
printf("Int_{0}^{1} x^3: %.8g\n", sum);
#endif
printf("Execution time: %.6f\n", (endClock - startClock) / (double)CLOCKS_PER_SEC);
}
在测量执行时间时我发现了一些奇怪的事情:
- 使用 O0 我得到了我预期的分布,case3 是最快的,其次是 case2,然后是 case1
- O1--O3 case2 的表现总是比 case1 和 case3 差很多
这里有一些比较执行时间的图片
- Compiled with O0
- Compiled with O1
- Compiled with O3.
这让我很困惑,我想知道我可以做些什么才能在不损失性能的情况下使用函数指针,因为出于灵活性原因,我真的很想使用函数指针。
=> 为什么函数指针这么慢?
我想补充一点,我不是软件工程师,而是航空航天工程专业的学生,遗憾的是我们没有太多的编程课程, 所以每个细节都可能有所帮助。
下面是类似功能的两个实现的反汇编视图:https://c.godbolt.org/z/l24Zhl
请注意,使用 -O2,第一个方法内联对 f_cube
和 f_square
的调用(注意没有调用程序集中的函数),但第二个版本没有。
第一个版本很可能会进一步 sped-up 由于处理器上的 Branch Prediction。
您是否分析过您的代码并发现此区域是瓶颈?请记住,首先优化 most-used 代码可以最大程度地提高速度。记住:先做起来,再做起来快。