OpenMp多线程编程计时问题 原

在做矩阵乘法并行化测试的时候,在利用<time.h>的clock()计时时出现了一点问题。

首先看串行的程序:

// matrix_cpu.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define NUM 2048

void matrixMul(float *A, float *B, float *C, int M, int K, int N)
{
    int i, j, k;
    for(i = 0; i < M; i++)
    {
        for(j = 0; j < N; j++)
        {
            float sum = 0.0f;
            for(k = 0; k < K; k++)
            {
                sum += A[i*k+k] * B[k*N+j];
            }
            C[i*N+j] = sum;
        }
    }
}

int main(int argc, char* argv[])
{
    float *A, *B, *C;
    clock_t start, finish;
    double duration;

    A = (float *) malloc (sizeof(float) * NUM * NUM);
    B = (float *) malloc (sizeof(float) * NUM * NUM);
    C = (float *) malloc (sizeof(float) * NUM * NUM);
    memset(A, 0, sizeof(float) * NUM * NUM);
    memset(B, 0, sizeof(float) * NUM * NUM);
    memset(C, 0, sizeof(float) * NUM * NUM);
    
    printf("Start...\n");

    start = clock();
    matrixMul(A, B, C, NUM, NUM, NUM);
    finish = clock();
    
    duration = (double)(finish - start) / CLOCKS_PER_SEC;
    printf("Time: %fs\n", duration);
    return 0;
}

在编译后,运行该程序,得到如下结果:

[wfshen@cu05 matrix]$ ./matrix_cpu
Start...
Time: 26.130000s

由于CPU是至强E5-2650,所以算得比较快(但目前仍然是串行,也就是说单核单线程),这样也要26秒了(在博主的i5-4200 ThinkPad上用时是171秒)。

加上time命令再运行一遍,结果如下:

[wfshen@cu05 matrix]$ time ./matrix_cpu
Start...
Time: 26.770000s

real	0m28.073s
user	0m26.779s
sys	0m0.019s

可以看到,时间与程序中统计的差不多,实际执行时间由于加了malloc等的时间所以长了一点,但还是合情合理的。

那么,再来看并行的OpenMP程序:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define NUM 2048
#define THREAD_NUM 2

void matrixMul(float *A, float *B, float *C, int M, int K, int N)
{
    int i, j, k;
#pragma omp parallel for private(j,k) num_threads(THREAD_NUM)
    for(i = 0; i < M; i++)
    {
        for(j = 0; j < N; j++)
        {
            float sum = 0.0f;
            #pragma ivdep
            for(k = 0; k < K; k++)
            {
                sum += A[i*k+k] * B[k*N+j];
            }
            C[i*N+j] = sum;
        }
    }
}

int main(int argc, char* argv[])
{
    float *A, *B, *C;
    clock_t start, finish;
    double duration;

    A = (float *) malloc (sizeof(float) * NUM * NUM);
    B = (float *) malloc (sizeof(float) * NUM * NUM);
    C = (float *) malloc (sizeof(float) * NUM * NUM);
    memset(A, 0, sizeof(float) * NUM * NUM);
    memset(B, 0, sizeof(float) * NUM * NUM);
    memset(C, 0, sizeof(float) * NUM * NUM);

    printf("Start...\n");

    start = clock();
    matrixMul(A, B, C, NUM, NUM, NUM);
    finish = clock();

    duration = (double)(finish - start) / CLOCKS_PER_SEC;
    printf("Time: %fs\n", duration);
    return 0;
}

可以看到,该OpenMP程序只使用了两个线程,那么运行时间理论上来说能减半。

在编译后,运行该程序,得到如下结果:

[wfshen@cu05 matrix]$ ./matrix_omp
Start...
Time: 26.550000s

这就奇怪了,明明心里面数了一下大概花了15秒,但是为什么计时还是26秒呢?

再加上time命令运行一遍:

[wfshen@cu05 matrix]$ time ./matrix_omp
Start...
Time: 26.440000s

real	0m13.438s
user	0m26.457s
sys	0m0.016s

可以看到,实际的运行时间是13秒,但是user却超过了13秒,且几乎是real的两倍。

查了一下,发现了这样的解释:

real: 墙上时间,即程序从开启到结束的实际运行时间
user: 执行用户代码所花的实际时间(不包括内核调用),指进程执行所消耗的实际CPU时间
sys:该程序在内核调用上花的时间

 在,单线程串行的时候,只有一个线程在运行,那么user所代表的就是一个cpu的时间。然而,当到多线程的情况下,一个进程可能有多个线程并行执行,但是user把所有的线程时间都加起来了,也就是算了一个总时间,这样,user的时间也就基本上等于单线程时的user时间。

这样,我们把线程数调到4,再运行代码(大概7秒):

[wfshen@cu05 matrix]$ ./matrix_omp
Start...
Time: 27.270000s
[wfshen@cu05 matrix]$ time ./matrix_omp
Start...
Time: 27.170000s

real	0m7.486s
user	0m27.176s
sys	0m0.018s

可以发现,实际运行时间7秒,CPU总时间27秒,差不多:

再把线程数调到16,再运行代码(大概2秒多):

[wfshen@cu05 matrix]$ ./matrix_omp
Start...
Time: 33.980000s
[wfshen@cu05 matrix]$ time ./matrix_omp
Start...
Time: 33.530000s

real	0m2.241s
user	0m33.479s
sys	0m0.075s

可以发现,CPU总时间有增加的趋势,不过实际时间还是大有减少。E5-2650是8核心16线程,再往上加线程时间反而会增长。

总结:在多线程的情况下,还是用time命令看时间吧。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏性能与架构

Redis 内存优化案例

在Redis的配置文件中有这么两项配置: hash-max-ziplist-entries 512 hash-max-ziplist-value 64 其中...

4217
来自专栏瓜大三哥

FPGA乒乓操作你了解吗? 还不赶快来看

2704
来自专栏专知

浅显易懂的分布式TensorFlow入门教程

【导读】分布式TensorFlow可以有效地提神经网络训练速度,但它的使用并不简单。虽然官方提供了文档和示例,如链接【1】,但是它们太难懂了。本文是一篇浅显易懂...

3197
来自专栏编程

厉害了,用Python一行代码实现人脸识别

摘要: 1行代码实现人脸识别,1. 首先你需要提供一个文件夹,里面是所有你希望系统认识的人的图片。其中每个人一张图片,图片以人的名字命名。2. 接下来,你需要准...

2868
来自专栏梦里茶室

TensorFlow深度学习笔记 Tensorboard入门

Github工程地址:https://github.com/ahangchen/GDLnotes 官方教程:https://www.tensorflow.org...

2168
来自专栏大数据文摘

TensorFlow 1.2.0新版本发布:新增Intel MKL优化深度学习基元

4514
来自专栏macOS 开发学习

Mac开发跬步积累(一):Cocoa Drawing 之 NSImage imageNamed: 到底做了什么?

首先,NSImage提供了支持多种格式图像数据进行管理的api, 但是NSImage对被其管理的实际图像数据几乎是一无所知的,这是因为NSImage并没有直接与...

1173
来自专栏Android相关

处理器结构--分支预测(Branch Prediction)

条件分支指令通常具有两路后续执行分支。即不采取(not taken)跳转,顺序执行后面紧挨JMP的指令;以及采取(taken)跳转到另一块程序内存去执行那里的指...

2743
来自专栏Django中文社区

创建 Django 博客的数据库模型

设计博客的数据库表结构 博客最主要的功能就是展示我们写的文章,它需要从某个地方获取博客文章数据才能把文章展示出来,通常来说这个地方就是数据库。我们把写好的文章永...

3136
来自专栏文武兼修ing——机器学习与IC设计

P2P接口串行FIR设计

配置接口使用寄存器组实现,掉电丢失,因此每次使用之前需要进行配置FIR参数,配置接口时序如下所示:

1484

扫码关注云+社区

领取腾讯云代金券