使用 C/OpenMP 时出现性能问题
Performance issue while using C/OpenMP
我写了一些代码来测试使用 C 和 OpenMP 的小程序的执行时间,我遇到了一些应用程序执行时间问题。
这是一段代码,负责添加 2 个向量:
float *x_f = (float *)malloc(sizeof(float) * DATA_SIZE);
float *y_f = (float *)malloc(sizeof(float) * DATA_SIZE);
for (int i = 0; i < DATA_SIZE; i++) {
x_f[i] = 1.0f;
y_f[i] = 2.0f;
}
start_time = omp_get_wtime();
#pragma omp parallel num_threads(N_THREADS)
{
const int thread_id = omp_get_thread_num();
int begin = range * thread_id;
int end = begin + range;
if (thread_id + 1 == N_THREADS)
end = DATA_SIZE;
for (int i = begin; i < end; i++) {
x_f[i] += y_f[i];
}
}
end_time = omp_get_wtime();
duration = end_time - start_time;
在同一个应用程序中,我使用相同的代码添加具有双精度类型元素的向量。程序似乎运行良好,但结果对我来说有点奇怪,因为 float 计算时间比 double 大几倍。
Float 1 2 3 4 5 6 7 8 9 10 11 12
1024 0,0036 0,4535 0,6875 0,9443 1,1653 1,5068 1,6951 2,0447 2,3546 2,6611 3,1319 3,1468
double 1 2 3 4 5 6 7 8 9 10 11 12
1024 0,0004 0,0014 0,0016 0,0019 0,0018 0,0018 0,002 0,0021 0,0024 0,0028 0,0045 0,0036
1-12 是 OmpeMP 线程数,1024 是元素向量大小。时间以毫秒为单位。有人可以向我解释,为什么会发生或我做错了什么吗?我是 C 和 OpenMP 的新手,我不知道为什么结果如上。
编辑:完整源代码如下:
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[]) {
double start_time, end_time, duration;
if (argv[1] == NULL || argv[2] == NULL) {
printf("Error parsing data from input. Program will now close");
return 2;
}
int DATA_SIZE = atoi(argv[1]);
int N_THREADS = atoi(argv[2]);
int range = DATA_SIZE / N_THREADS;
// ===================== FLOAT ========================
float *x_f = (float *)malloc(sizeof(float) * DATA_SIZE);
float *y_f = (float *)malloc(sizeof(float) * DATA_SIZE);
for (int i = 0; i < DATA_SIZE; i++) {
x_f[i] = 1.0f;
y_f[i] = 2.0f;
}
start_time = omp_get_wtime();
#pragma omp parallel num_threads(N_THREADS)
{
const int thread_id = omp_get_thread_num();
int begin = range * thread_id;
int end = begin + range;
if (thread_id + 1 == N_THREADS)
end = DATA_SIZE;
for (int i = begin; i < end; i++) {
x_f[i] += y_f[i];
/*printf("x_f[%d]=%f\ty_f[%d]=%f\tThreadNum=%d\n", i, x_f[i], i,
y_f[i],
omp_get_thread_num());*/
}
}
end_time = omp_get_wtime();
duration = end_time - start_time;
// Error checking
for (int i = 0; i < DATA_SIZE; i++) {
if (!(3.0 - x_f[i]) == 0) {
printf("ERROR: %f\n", x_f[i]);
break;
}
}
free(x_f);
free(y_f);
printf("==========[FLOAT]==========\n");
printf("Number of threads: %d\n", N_THREADS);
printf("Data size: %d bytes\n", DATA_SIZE * sizeof(float));
printf("ExecTime: %lf ms\n", duration * 1000);
// ===================== DOUBLE ========================
double *x_lf = (double *)malloc(sizeof(double) * DATA_SIZE);
double *y_lf = (double *)malloc(sizeof(double) * DATA_SIZE);
for (int i = 0; i < DATA_SIZE; i++) {
x_lf[i] = 1.0f;
y_lf[i] = 2.0f;
}
start_time = omp_get_wtime();
#pragma omp parallel num_threads(N_THREADS)
{
const int thread_id = omp_get_thread_num();
int begin = range * thread_id;
int end = begin + range;
if (thread_id + 1 == N_THREADS)
end = DATA_SIZE;
for (int i = begin; i < end; i++) {
x_lf[i] += y_lf[i];
/*printf("x_f[%d]=%f\ty_f[%d]=%f\tThreadNum=%d\n", i, x_lf[i], i,
y_lf[i], omp_get_thread_num());*/
}
}
end_time = omp_get_wtime();
duration = end_time - start_time;
// Error checking
for (int i = 0; i < DATA_SIZE; i++) {
if (!(3.0 - x_lf[i]) == 0) {
printf("ERROR: %f\n", x_lf[i]);
break;
}
}
free(x_lf);
free(y_lf);
printf("\n==========[DOUBLE]==========\n");
printf("Number of threads: %d\n", N_THREADS);
printf("Data size: %d bytes\n", DATA_SIZE * sizeof(double));
printf("ExecTime: %lf ms\n", duration * 1000);
return 0;
}
EDIT2: 全部 table 结果
Results
老实说,你显示的结果很奇怪。您如何将 for 循环更改为:
for (int i = thread_id; i < DATA_SIZE; i += N_THREADS) { /* */ }
使用这个,我得到了 float
和 double
的可比时间。
不过我无法重现您的结果,因为不清楚您是如何定义 range
变量的。
我已经在 Compiler Explorer 上复制了您的代码。如果问题相当大,例如DATA_SIZE=10000000;
一切都按预期工作(浮点数更快,并发减少了 运行 时间)。对于现代处理器,添加 2 个大小为 1024 的数组非常简单,您只是在测量开销或人工制品...
编辑:我刚刚更改了代码的顺序(首先测量双精度,然后测量浮点数),这是我得到的:
==========[DOUBLE]==========
Number of threads: 2
Data size: 8192 bytes
ExecTime: 0.091095 ms
==========[FLOAT]==========
Number of threads: 2
Data size: 4096 bytes
ExecTime: 0.001610 ms
这证明您 运行 首先进行的任何测试都比第二次测试慢得多。我认为这是因为缓存:OpenMP 函数、数据、分配的内存等已经在缓存中,因此您的第二次测试 运行 快得多。
Here is the code refactored。也许有意义。使用 sheredom/ubench
。
请找出编译器参数:-fopenmp -s -lm -O3
。玩 -O
并找出不同之处。
(作为旁注,请阅读:32 OpenMP Traps for C++ Developers)
代码如下:
#include "https://raw.githubusercontent.com/sheredom/ubench.h/master/ubench.h"
#include <assert.h>
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
#define DATA_SIZE 100000
#define N_THREADS 2
#define range DATA_SIZE/N_THREADS
static struct {
float x_f[DATA_SIZE] ;
float y_f[DATA_SIZE] ;
double x_d[DATA_SIZE] ;
double y_d[DATA_SIZE] ;
} * app_data = 0 ;
static void app_start (void)
{
app_data = calloc(1, sizeof(*app_data) );
assert(app_data) ;
for (int i = 0; i < DATA_SIZE; ++i) {
app_data->x_f[i] = 1.0f;
app_data->y_f[i] = 2.0f;
app_data->x_d[i] = 1.0;
app_data->y_d[i] = 2.0;
}
}
UBENCH( omp_measuring, adding_two_arrays_of_floats )
{
#pragma omp parallel num_threads(N_THREADS)
{
const int thread_id = omp_get_thread_num();
const int begin = range * thread_id;
int end = begin + range;
if (thread_id + 1 == N_THREADS)
end = DATA_SIZE;
for (int i = begin; i < end; i++) {
app_data->x_f[i] += app_data->y_f[i];
}
}
}
UBENCH( omp_measuring, adding_two_arrays_of_doubles )
{
#pragma omp parallel num_threads(N_THREADS)
{
const int thread_id = omp_get_thread_num();
const int begin = range * thread_id;
int end = begin + range;
if (thread_id + 1 == N_THREADS)
end = DATA_SIZE;
for (int i = begin; i < end; i++) {
app_data->x_d[i] += app_data->y_d[i];
}
}
}
static void app_end (void) { free(app_data); }
UBENCH_STATE();
int main(int argc, const char *const argv[])
{
app_start();
ubench_main(argc, argv);
app_end();
}
买者自负
OMP 的全部意义(非常)没有实际意义,因为 OMP 显然只有在不使用优化的情况下才有效。很难击败 gnuc -O3
.
Godbolt 的问题是它在处理大型数据集时会卡住。在 VLA(又名超大阵列)上使用 OMP 应该 显得有意义。
实际上,很少需要处理兆字节或千兆字节的数据集。然后人们可能会想象正在使用 GPU。没有 OMP。
奖金
app_start
不是UBENCH规定的。可以将应用程序命令行参数传递给不同的 app_start
:
static void app_start(const unsigned argc, char ** argv )
{
// use app arguments here
}
int main (int argc, char ** argv)
{
app_start(argc,argv);
ubench_main(argc,argv);
app_end();
}
我写了一些代码来测试使用 C 和 OpenMP 的小程序的执行时间,我遇到了一些应用程序执行时间问题。 这是一段代码,负责添加 2 个向量:
float *x_f = (float *)malloc(sizeof(float) * DATA_SIZE);
float *y_f = (float *)malloc(sizeof(float) * DATA_SIZE);
for (int i = 0; i < DATA_SIZE; i++) {
x_f[i] = 1.0f;
y_f[i] = 2.0f;
}
start_time = omp_get_wtime();
#pragma omp parallel num_threads(N_THREADS)
{
const int thread_id = omp_get_thread_num();
int begin = range * thread_id;
int end = begin + range;
if (thread_id + 1 == N_THREADS)
end = DATA_SIZE;
for (int i = begin; i < end; i++) {
x_f[i] += y_f[i];
}
}
end_time = omp_get_wtime();
duration = end_time - start_time;
在同一个应用程序中,我使用相同的代码添加具有双精度类型元素的向量。程序似乎运行良好,但结果对我来说有点奇怪,因为 float 计算时间比 double 大几倍。
Float 1 2 3 4 5 6 7 8 9 10 11 12
1024 0,0036 0,4535 0,6875 0,9443 1,1653 1,5068 1,6951 2,0447 2,3546 2,6611 3,1319 3,1468
double 1 2 3 4 5 6 7 8 9 10 11 12
1024 0,0004 0,0014 0,0016 0,0019 0,0018 0,0018 0,002 0,0021 0,0024 0,0028 0,0045 0,0036
1-12 是 OmpeMP 线程数,1024 是元素向量大小。时间以毫秒为单位。有人可以向我解释,为什么会发生或我做错了什么吗?我是 C 和 OpenMP 的新手,我不知道为什么结果如上。
编辑:完整源代码如下:
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[]) {
double start_time, end_time, duration;
if (argv[1] == NULL || argv[2] == NULL) {
printf("Error parsing data from input. Program will now close");
return 2;
}
int DATA_SIZE = atoi(argv[1]);
int N_THREADS = atoi(argv[2]);
int range = DATA_SIZE / N_THREADS;
// ===================== FLOAT ========================
float *x_f = (float *)malloc(sizeof(float) * DATA_SIZE);
float *y_f = (float *)malloc(sizeof(float) * DATA_SIZE);
for (int i = 0; i < DATA_SIZE; i++) {
x_f[i] = 1.0f;
y_f[i] = 2.0f;
}
start_time = omp_get_wtime();
#pragma omp parallel num_threads(N_THREADS)
{
const int thread_id = omp_get_thread_num();
int begin = range * thread_id;
int end = begin + range;
if (thread_id + 1 == N_THREADS)
end = DATA_SIZE;
for (int i = begin; i < end; i++) {
x_f[i] += y_f[i];
/*printf("x_f[%d]=%f\ty_f[%d]=%f\tThreadNum=%d\n", i, x_f[i], i,
y_f[i],
omp_get_thread_num());*/
}
}
end_time = omp_get_wtime();
duration = end_time - start_time;
// Error checking
for (int i = 0; i < DATA_SIZE; i++) {
if (!(3.0 - x_f[i]) == 0) {
printf("ERROR: %f\n", x_f[i]);
break;
}
}
free(x_f);
free(y_f);
printf("==========[FLOAT]==========\n");
printf("Number of threads: %d\n", N_THREADS);
printf("Data size: %d bytes\n", DATA_SIZE * sizeof(float));
printf("ExecTime: %lf ms\n", duration * 1000);
// ===================== DOUBLE ========================
double *x_lf = (double *)malloc(sizeof(double) * DATA_SIZE);
double *y_lf = (double *)malloc(sizeof(double) * DATA_SIZE);
for (int i = 0; i < DATA_SIZE; i++) {
x_lf[i] = 1.0f;
y_lf[i] = 2.0f;
}
start_time = omp_get_wtime();
#pragma omp parallel num_threads(N_THREADS)
{
const int thread_id = omp_get_thread_num();
int begin = range * thread_id;
int end = begin + range;
if (thread_id + 1 == N_THREADS)
end = DATA_SIZE;
for (int i = begin; i < end; i++) {
x_lf[i] += y_lf[i];
/*printf("x_f[%d]=%f\ty_f[%d]=%f\tThreadNum=%d\n", i, x_lf[i], i,
y_lf[i], omp_get_thread_num());*/
}
}
end_time = omp_get_wtime();
duration = end_time - start_time;
// Error checking
for (int i = 0; i < DATA_SIZE; i++) {
if (!(3.0 - x_lf[i]) == 0) {
printf("ERROR: %f\n", x_lf[i]);
break;
}
}
free(x_lf);
free(y_lf);
printf("\n==========[DOUBLE]==========\n");
printf("Number of threads: %d\n", N_THREADS);
printf("Data size: %d bytes\n", DATA_SIZE * sizeof(double));
printf("ExecTime: %lf ms\n", duration * 1000);
return 0;
}
EDIT2: 全部 table 结果 Results
老实说,你显示的结果很奇怪。您如何将 for 循环更改为:
for (int i = thread_id; i < DATA_SIZE; i += N_THREADS) { /* */ }
使用这个,我得到了 float
和 double
的可比时间。
不过我无法重现您的结果,因为不清楚您是如何定义 range
变量的。
我已经在 Compiler Explorer 上复制了您的代码。如果问题相当大,例如DATA_SIZE=10000000;
一切都按预期工作(浮点数更快,并发减少了 运行 时间)。对于现代处理器,添加 2 个大小为 1024 的数组非常简单,您只是在测量开销或人工制品...
编辑:我刚刚更改了代码的顺序(首先测量双精度,然后测量浮点数),这是我得到的:
==========[DOUBLE]==========
Number of threads: 2
Data size: 8192 bytes
ExecTime: 0.091095 ms
==========[FLOAT]==========
Number of threads: 2
Data size: 4096 bytes
ExecTime: 0.001610 ms
这证明您 运行 首先进行的任何测试都比第二次测试慢得多。我认为这是因为缓存:OpenMP 函数、数据、分配的内存等已经在缓存中,因此您的第二次测试 运行 快得多。
Here is the code refactored。也许有意义。使用 sheredom/ubench
。
请找出编译器参数:-fopenmp -s -lm -O3
。玩 -O
并找出不同之处。
(作为旁注,请阅读:32 OpenMP Traps for C++ Developers)
代码如下:
#include "https://raw.githubusercontent.com/sheredom/ubench.h/master/ubench.h"
#include <assert.h>
#include <omp.h>
#include <stdio.h>
#include <stdlib.h>
#define DATA_SIZE 100000
#define N_THREADS 2
#define range DATA_SIZE/N_THREADS
static struct {
float x_f[DATA_SIZE] ;
float y_f[DATA_SIZE] ;
double x_d[DATA_SIZE] ;
double y_d[DATA_SIZE] ;
} * app_data = 0 ;
static void app_start (void)
{
app_data = calloc(1, sizeof(*app_data) );
assert(app_data) ;
for (int i = 0; i < DATA_SIZE; ++i) {
app_data->x_f[i] = 1.0f;
app_data->y_f[i] = 2.0f;
app_data->x_d[i] = 1.0;
app_data->y_d[i] = 2.0;
}
}
UBENCH( omp_measuring, adding_two_arrays_of_floats )
{
#pragma omp parallel num_threads(N_THREADS)
{
const int thread_id = omp_get_thread_num();
const int begin = range * thread_id;
int end = begin + range;
if (thread_id + 1 == N_THREADS)
end = DATA_SIZE;
for (int i = begin; i < end; i++) {
app_data->x_f[i] += app_data->y_f[i];
}
}
}
UBENCH( omp_measuring, adding_two_arrays_of_doubles )
{
#pragma omp parallel num_threads(N_THREADS)
{
const int thread_id = omp_get_thread_num();
const int begin = range * thread_id;
int end = begin + range;
if (thread_id + 1 == N_THREADS)
end = DATA_SIZE;
for (int i = begin; i < end; i++) {
app_data->x_d[i] += app_data->y_d[i];
}
}
}
static void app_end (void) { free(app_data); }
UBENCH_STATE();
int main(int argc, const char *const argv[])
{
app_start();
ubench_main(argc, argv);
app_end();
}
买者自负
OMP 的全部意义(非常)没有实际意义,因为 OMP 显然只有在不使用优化的情况下才有效。很难击败 gnuc -O3
.
Godbolt 的问题是它在处理大型数据集时会卡住。在 VLA(又名超大阵列)上使用 OMP 应该 显得有意义。
实际上,很少需要处理兆字节或千兆字节的数据集。然后人们可能会想象正在使用 GPU。没有 OMP。
奖金
app_start
不是UBENCH规定的。可以将应用程序命令行参数传递给不同的 app_start
:
static void app_start(const unsigned argc, char ** argv )
{
// use app arguments here
}
int main (int argc, char ** argv)
{
app_start(argc,argv);
ubench_main(argc,argv);
app_end();
}