基础篇15C++科学计算-OpenBLAS的安装与使⽤
BLAS简介
类似于Anaconda⾥⾯的numpy,C++⾥⾯也有类似的矩阵运算库,称之为BLAS(Basic Linear Algebra Subprograms):基础线性代数⼦程序库。
⽀持的数据类型有:
单精度浮点数(float)
双精度浮点数(double)
单精度复数
双精度复数
在机器学习⾥⾯⼀般我们只⽤float类型,很少使⽤double,考虑到性能,我们认为float的精度已经够了,⽽且速度快。
还有⼀个更⾼级的叫LAPACK,现在我们常⽤的BLAS其实就是LAPACK⾥⾯的⼀部分。
BLAS⽀持对⼦程序的封装,其实就是⼦函数了,它的⼦程序分类:
Level1: 标量操作、向量操作、向量-向量操作
Level2:矩阵-向量操作
Level3: 矩阵-矩阵操作
BLAS的实现
BLAS:标准实现(Fortran)
CBLAS:C的BLAS标准实现
Atlas:⼀种优化实现
GotoBLAS:多线程性能良好的优化实现(已停⽌更新)
OpenBLAS:⽬前性能最好的开源实现,基于GotoBLAS
MKL:Intel实现,在Intel处理器上性能最佳
各种BLAS实现的优劣对⽐
市⾯上的BLAS实现⾮常多,最早是⽤Fortran。
CBLAS是C的BLAS标准实现,但是这种实现有个缺点,速度⾮常慢。
Atlas:⽐C要好,但不够好。⽐如腾讯QQ空间有⼀个功能,就是标脸框那个程序,怎么做呢,你每上传⼀张图⽚之后都会进⾏图⽚处理,处理完之后进⾏⼈脸检测,识别出来再把结果返回到你的业务服务器上去。那这就有个问题了,我怎么能⾮常快的处理这些图⽚。对于流量很⼤的⽹站来说,它的数据量是⾮常⼤的,我怎么能够⾼实时性的处理这些东西呢。这就对我们计算产⽣⾮常⾼的要求,要求我们计算⾮常快,⽽且是多线程的计算,所以,后来外国友⼈就推出了GotoBLAS,特点是在多线程的情况下性能是⽐较良好的。不过这个程序在2010已结不更新了,不去⽤它了。
⽬前性能最好的开源实现OpenBLAS。OpenBLAS的整体性能不够稳定,但是平均性能是所有库⾥⾯最好的。它的开发者是中国⼈,中科院研究所的张先义,GitHub仓库叫xianyi。它是由中国⼈维护的⽬前最好的开源实现,Caffe⾥⾯⽤的BLAS库就是
OpenBLAS。
⽐如我们使⽤的numpy,上⾯使⽤的是Python接⼝,底下也⽤了BLAS库,⽐如anaconda的numpy就
使⽤了OpenBLAS。
⾮开源库,MKL:Math Kernel Library。Intel推出的和Intel C++ 编译器绑定的⼀个库,这是⽬前在Intel处理器上性能最好的BLAS 库,⽽且⽐OpenBLAS要稳定。但是有两个问题,1.它是和Intel C++ Compiler绑定的。如果你想使⽤它你必须使⽤Intel C++ Compiler。事实上呢可以利⽤DLL的封装性在这上⾯再封⼀层。我们可以先通过Visual C++的DLL,再去调⽤因特尔的C++的DLL,这个就脱离了必须和Intel C++ Compiler绑定的这个⼀个限制,这是⼀种变通。2.它只能⽤在Intel CPU 上。⽐如说你要做移动程序上的机器学习,⽐如把⼈脸识别放到你的⼿机上去做。它都是ARM芯⽚,MKL就⽆能为⼒了,因为它是给Intel做的。这个时候我们的⾸选就只有OpenBLAS,所以我们今天介绍的是OpenBLAS。
OpenBLAS是跨平台的,windows、Linux、macOS下都是可以有的。
Mac的Xcode下安装OpenBLAS
1. 直接去OpenBLAS的或下载到本地即可,本⽂以GitHub下载为例说明。
2. 导⼊动态依赖库
在OpenBLAS的⽂件夹中到如下动态库,直接拖⼊Link Binary With Libraries。
3. 设置头⽂件和库的本地查路径。
4. 导⼊头⽂件,开始使⽤。
#include <cblas.h>
BLAS的基本使⽤
我们先新建Xcode的C++⼯程,导⼊必要的头⽂件、声明会⽤到的函数定义和函数调⽤。
#include <iostream>
#include <vector>
#include <cstdlib>
#include <cblas.h>
// 随机⽣成20以内的给定尺⼨数组
static void RandomFill(std::vector<float>& numbers,size_t size);
// 打印数组元素的函数
static void Print(const std::vector<float>& numbers);
// vector是⼀维的,输出是个矩阵,那输出的时候就要指定有⼏⾏⼏列static void Print(const std::vector<float>& numbers, int rows, int cols); // 寻数组中最⼤的那个元素的索引和值
static void TestLevel1();
// 测试Level2⾥⾯最常⽤的函数:向量和矩阵的乘积
static void TestLevel2();
static void TestLevel3();
int main(int argc, const char * argv[]) {
TestLevel1();
TestLevel2();
TestLevel3();
return 0;
}
void RandomFill(std::vector<float>& numbers, size_t size) {
// 预分配size的缓冲区,这样性能相对更好⼀点
for (size_t i = 0; i != size; ++ i) {
numbers[i] = static_cast<float>(rand() % 20);
}
}
void Print(const std::vector<float>& numbers) {
for (float number : numbers) {
std::cout << number << ' ';
}
std::cout << std::endl;
}
void Print (const std:: vector<float>& numbers ,int rows, int cols) {
for (int row =0; row != rows; ++ row) {
for (int col = 0; col != cols; ++ col) {
// 取出每⼀列的数字
std::cout << numbers[row * cols + col] << ' ';
}
// 没输出⼀⾏之后换⼀⾏
std::cout << std::endl;
}
}
接下来我们依次看⼀下OpenBLAS在三个⼦程序上的基本使⽤。Level1:出数组的最⼤值和改值所在的Index
static void TestLevel1() {
const int VECTOR_SIZE = 4;
std::vector<float> fv1;
RandomFill(fv1, VECTOR_SIZE);
Print(fv1);
/**
从数组⾥⾯出最⼤的那个数的索引:cblas_isamax()属于Level1的函数
VECTOR_SIZE 数组长度
fv1.data 数组缓冲区指针的⾸地址,怎么获得fv1内部缓冲区呢,⽤fv1.data
third params: 跳跃数量,两个元素之间间隔⼏个元素,也就是每处理⼀个元素之后,+1得到下⼀个元素,如果想跳过⼀个元素 +2 */
size_t maxIndex = cblas_isamax(VECTOR_SIZE, fv1.data(), 1);
std::cout << maxIndex << std::endl;
std::cout << fv1[maxIndex] << std::endl;
}
/*
Prints:
7 9 13 18
3
18
*/
Level2常⽤函数:计算向量与矩阵的乘积
/*
测试Level2⾥⾯最常⽤的函数:向量和矩阵的乘积
*/
static void TestLevel2()
{
// 假设我们有⼀个三⾏⼆列的矩阵
const int M = 3;
const int N = 2;
/*
A(M*N),x(N*1), y(M*1)
我们定义三个矩阵a、x、y
a:M*N的矩阵:维度为M*N矩阵
x:N*1的矩阵,实际上是⼀个向量,维度为N矩阵
y:M*1的矩阵,实际上是长度为M的向量,维度为M的矩阵
*/
std::vector<float> a;
std::vector<float> x;
std::vector<float> y;
RandomFill(a, M * N);
RandomFill(x, N);
RandomFill(y, M);
std::cout << "A" << std::endl;
Print(a, M, N);
std::cout << "x" << std::endl;
Print(x);
std::cout << "y" << std::endl;
Print(y);
/*
我们的⽬标是想计算这么⼀个公式:
y := alpha * A * x + beta * y
A:是⼀个矩阵,x是⼀个向量,所以我希望说去计算⼀个矩阵和向量的乘积。alpha是⼀个乘积的缩放,
beta是对y的缩放,
相当于把y⾥⾯的数字乘以beta,再加上A矩阵和向量的乘积。
那这边有⼀个特例,假如我y⾥⾯都是0,或这beta是0的情况下,我就可以把公式看成:
// y := alpha * A * x
这个函数名称为:cblas_sgemv()
// s:single 单精度浮点数
// ge: 是⼀个乘法
// m: matrix
// v: vector
*/
/**
参数解释:
param CblasRowMajor ⾏主序还是列主序,默认⾏主序,何为主序:即数组存储元素的⽅式--按⾏存储还是按列存储,⾏主序:00,01,列主序00,10 param CblasNoTrans 矩阵是否需要转置,不需要转置,如果需要转置的话,运算的时候它会⾃动做转置
param M 矩阵的⾏数
param N 矩阵的列数
param 1.0f alpha ,我们设为1
param a.data a矩阵的缓冲区⾸地址
param lda a矩阵的列数
param x.data x矩阵的缓冲区⾸地址
param 1 x⾥⾯每次跳跃累加的个数,默认为1
param 2.0f beta对y的缩放值
param y.data y矩阵的缓冲区⾸地址
param 1 y⾥⾯每次跳跃累加的个数,默认为1
*/
int lda = N;
cblas_sgemv(CblasRowMajor, CblasNoTrans, M, N, 1.0f, a.data(), lda, x.data(), 1, 2.0f, y.data(), 1);
std::cout << "result y" << std::endl;
Print(y);
}
/*
Prints:
A
7 9
13 18
10 12
x
4 18
y
3 9 0
result y
196 394 256
*/
Level3常⽤函数:计算两个矩阵的乘积
/**
计算两个矩阵的乘积
*/
xcode入门
static void TestLevel3() {
// 我们希望计算两个矩阵的乘积,我们就需要定义三个参数M、N、K。
const int M = 3;
const int N = 2;
const int K = 4;
std::vector<float> a;
std::vector<float> b;
std::vector<float> c;
RandomFill(a, M * K);
RandomFill(b, K * N);
RandomFill(c, M * N);
/
/ 输出A、B、C 三个矩阵
std::cout << "A" << std::endl;
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论