Caffe学习笔记5-BLAS与boost::thread加速

Refer from http://yufeigan.github.io/2015/01/02/Caffe%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B05-BLAS%E4%B8%8Eboost-thread%E5%8A%A0%E9%80%9F/


Caffe中运用了大量的优化方法,最近在优化自己代码时候恰好运用了其中的BLAS和Boost::thread。使用过程中遇到了不少问题,这里把我遇到的问题和解决方法整理一下。

BLAS是什么?

BLAS (Basic Linear Algebra Subprograms)基础线性代数子程序库,是一个应用程序接口(API)标准,说的简单点就是向量、矩阵之间乘加这些运算。BLAS虽然本身就有实现但效率不高,因此有大量的开源或商业项目对BLAS进行优化比如OpenBLAS(开源),Intel MKL(收费),ATLAS(开源)。我用的是OpenBLAS这个库。

OpenBLAS

OpenBLAS是C语言实现的,这个库安装比较简单,没有什么问题,唯一的一个问题是使用方法。前面介绍BLAS提供了接口,文档在这里。
这个文档中 Level 1 是vector与vector的操作,Level 2 是vector与matrix的操作,Level 3是matrix与matrix的操作。

每个函数的开头有一个x表示精度比如替换成s表示float类型,d表示double类型。

其实虽然函数很多但其实使用方法大同小异,BLAS之所以分的这么细(区分到对称矩阵,三角矩阵)是为了方便针对不同的情况做优化。所以其实搞清楚最关键的矩阵与矩阵的运算就已经理解了一大半。

以dgemm为例,全称为double-precision generic matrix-matrix muliplication,就是矩阵相乘,在OpenBLAS中的声明是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cblas_dgemm(const enum CBLAS_ORDER Order,
const enum CBLAS_TRANSPOSE TransA,
const enum CBLAS_TRANSPOSE TransB,
const blasint M,
const blasint N,
const blasint K,
const double alpha,
const double *A,
const blasint lda,
const double *B,
const blasint ldb,
const double beta,
double *C,
const blasint ldc)

对应的公式如下:

Cαop(A)op(B)+βC,op(X)=X,XT,XH,Cm×n C←αop(A)op(B)+βC,op(X)=X,XT,XH,C−m×n

参数的说明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const enum CBLAS_ORDER Order,               \\指定矩阵的存储方式如RowMajor
const enum CBLAS_TRANSPOSE TransA, \\A运算后是否转置
const enum CBLAS_TRANSPOSE TransB, \\B运算后是否转置
const blasint M, \\A的行数
const blasint N, \\B的列数
const blasint K, \\A的列数
const double alpha, \\公式中的alpha
const double *A, \\A
const blasint lda, \\A一行的存储间隔
const double *B, \\B
const blasint ldb, \\B一行的存储间隔
const double beta, \\公式中的beta
double *C, \\C
const blasint ldc \\C一行的存储间隔

因为这里的A、B、C矩阵都是以一维数组的形式存储所以需要告诉函数他们一行的存储间隔就是lda、ldb、ldc它们。

OpenBLAS wiki上面的demo

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <cblas.h>
#include <stdio.h>
void main()
{

int i=0;
double A[6] = {1.0,2.0,1.0,-3.0,4.0,-1.0};
double B[6] = {1.0,2.0,1.0,-3.0,4.0,-1.0};
double C[9] = {.5,.5,.5,.5,.5,.5,.5,.5,.5};
cblas_dgemm(CblasColMajor, CblasNoTrans, CblasTrans,3,3,2,1,A, 3, B, 3,2,C,3);
for(i=0; i<9; i++)
printf("%lf ", C[i]);
printf("\n");
}

Boost是什么?

用百度百科的话说,Boost库是一个经过千锤百炼、可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的发动机之一。

实际感受就是一个相当强大的C++拓展库,很多C++标准库里面没有的功能得以实现。最近就用到了ublas,thread,date_time这三个模块。这里做一些简要的介绍。

ublas

调用位置boost::numeric::ublas,是boost的BLAS实现,虽然速度一般,但用起来非常方便可以直接用 + - 运算符号操作,还可以直接用 << >> 等标准输入输出流。

date_time

在我计算多线程运行时间的时候发现标准C++提供的std::clock_t对于多CPU跑线程的情况会把几个CPU的时间加在一起,所以采用了 boost::posix_time::ptime 这种类型计数。解决了计时不准确的问题。

thread

Boost提供的thread虽然功能据说不是非常强大,但是由于使用C++的思想重新设计,使用起来相对比较方便。网上文档非常多,比如:

  1. C++ Boost Thread 编程指南
  2. Boost::Thread使用示例
    在实际运用中还是发现不少问题比如

    线程的并行化

    用一般的方法并不能真正的并行程序,比如]这里的代码:
    1
    2
    3
    4
    5
    6
    7
    std::vector<boost::thread *> z;
    for (int i = 0; i < 2; ++i)
    z.push_back(new boost::thread(workerFunc));
    for (int i = 0; i < 2; ++i){
    z[i]->join();
    delete z[i];
    }

即使是这样在某些情况下我的线程还是不能并行运行,只是同时开始创建而已。
解决方案是使用boost::thread_group

1
2
3
4
boost::thread_group group;
for (int i = 0; i < 15; ++i)
group.create_thread(aFunctionToExecute);
group.join_all();

当执行join_all()的时候才是真正的并行了程序。

成员函数没有实例但又要传参的方法

编译时候错误:error: reference to non-static member function must be called; did you mean to call it with no arguments?
查了Google发现是因为我定义的类中的成员函数用group.create_thread()中调用了没有实例化的成员函数解决方法是使用std::bind
我直接用的是boost::bind

1
group.create_thread(boost::bind(&myfunction, this));

线程同步互斥锁boost::mutex

接触过多线程的人一定不会陌生,当我们用操作操作统一数据的读写时或是数据输出输出时必须要用到互斥量。一开始用boost::mutex::scoped_lock给boost::mutex上锁时只知道用boost::condition控制线程的状态,后来发现代码越写越复杂,后来发现只需要改变boost::mutex作用域就可以自动给boost::mutex lock/unlock。比如如果每次只让一个线程输出信息到屏幕:

1
2
3
4
5
6
7
8
boost::mutex io_mutex;
void foo(){
{
boost::mutex::scoped_lock lock(io_mutex);
std::cout << "something output!" << std::endl;
}
// something to do!
}

用这种方法多个函数在对统一个数据操作的时候就不会有冲突了。


你可能感兴趣的:(Caffe学习笔记5-BLAS与boost::thread加速)