gpt4 book ai didi

c - 矩阵和 vector 乘法优化算法

转载 作者:太空宇宙 更新时间:2023-11-04 02:14:18 25 4
gpt4 key购买 nike

假设维度非常大(一个矩阵中有多达 10 亿个元素)。我将如何为矩阵 vector 积实现缓存遗忘算法?基于维基百科,我需要递归地分而治之,但我觉得会有很多开销。这样做会有效吗?

跟进问答:OpenMP with matrices and vectors

最佳答案

因此,“我如何使这个基本的线性代数运算变得更快”这个问题的答案总是随处可见,可以找到并链接到针对您的平台调整的 BLAS 库。例如,GotoBLAS (其工作正在 OpenBLAS 中继续),或较慢的自动调整 ATLAS ,或像英特尔的 MKL 这样的商业软件包。线性代数对于许多其他操作来说是如此基础,以至于需要付出大量努力来针对各种平台优化这些包,而且您根本不可能在几个下午的工作中想出可以竞争的东西。您正在寻找一般密集矩阵 vector 乘法的特定子例程调用是 SGEMV/DGEMV/CGEMV/ZGEMV。

缓存不经意算法或自动调整适用于当您不必为调整系统的特定缓存架构而烦恼时 - 通常情况下这可能没问题,但因为人们愿意为 BLAS 例程执行此操作,然后提供调优结果,这意味着您最好只使用这些例程。

GEMV 的内存访问模式非常简单,您实际上不需要分而治之(对于矩阵转置的标准情况也是如此)- 您只需找到缓存 block 大小并使用它。在 GEMV (y = Ax) 中,您仍然必须扫描整个矩阵一次,因此在那里没有什么可重用的(因此有效的缓存使用),但您可以尝试尽可能多地重用 x 以便加载它一次而不是(行数)次 - 您仍然希望访问 A 是缓存友好的。所以明显的缓存阻塞要做的事情是打破 block :

  A x -> [ A11 | A12 ] | x1 | = | A11 x1 + A12 x2 |
[ A21 | A22 ] | x2 | | A21 x1 + A22 x2 |

你当然可以递归地做到这一点。但是做一个简单的实现,它比简单的双循环慢,而且比正确的 SGEMV 库调用慢得多:

$ ./gemv
Testing for N=4096
Double Loop: time = 0.024995, error = 0.000000
Divide and conquer: time = 0.299945, error = 0.000000
SGEMV: time = 0.013998, error = 0.000000

代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include "mkl.h"

float **alloc2d(int n, int m) {
float *data = malloc(n*m*sizeof(float));
float **array = malloc(n*sizeof(float *));
for (int i=0; i<n; i++)
array[i] = &(data[i*m]);
return array;
}

void tick(struct timeval *t) {
gettimeofday(t, NULL);
}

/* returns time in seconds from now to time described by t */
double tock(struct timeval *t) {
struct timeval now;
gettimeofday(&now, NULL);
return (double)(now.tv_sec - t->tv_sec) + ((double)(now.tv_usec - t->tv_usec)/1000000.);
}

float checkans(float *y, int n) {
float err = 0.;
for (int i=0; i<n; i++)
err += (y[i] - 1.*i)*(y[i] - 1.*i);
return err;
}

/* assume square matrix */
void divConquerGEMV(float **a, float *x, float *y, int n,
int startr, int endr, int startc, int endc) {

int nr = endr - startr + 1;
int nc = endc - startc + 1;

if (nr == 1 && nc == 1) {
y[startc] += a[startr][startc] * x[startr];
} else {
int midr = (endr + startr+1)/2;
int midc = (endc + startc+1)/2;
divConquerGEMV(a, x, y, n, startr, midr-1, startc, midc-1);
divConquerGEMV(a, x, y, n, midr, endr, startc, midc-1);
divConquerGEMV(a, x, y, n, startr, midr-1, midc, endc);
divConquerGEMV(a, x, y, n, midr, endr, midc, endc);
}
}
int main(int argc, char **argv) {
const int n=4096;
float **a = alloc2d(n,n);
float *x = malloc(n*sizeof(float));
float *y = malloc(n*sizeof(float));
struct timeval clock;
double eltime;

printf("Testing for N=%d\n", n);

for (int i=0; i<n; i++) {
x[i] = 1.*i;
for (int j=0; j<n; j++)
a[i][j] = 0.;
a[i][i] = 1.;
}

/* naive double loop */
tick(&clock);
for (int i=0; i<n; i++) {
y[i] = 0.;
for (int j=0; j<n; j++) {
y[i] += a[i][j]*x[j];
}
}
eltime = tock(&clock);
printf("Double Loop: time = %lf, error = %f\n", eltime, checkans(y,n));

for (int i=0; i<n; i++) y[i] = 0.;

/* naive divide and conquer */
tick(&clock);
divConquerGEMV(a, x, y, n, 0, n-1, 0, n-1);
eltime = tock(&clock);
printf("Divide and conquer: time = %lf, error = %f\n", eltime, checkans(y,n));

/* decent GEMV implementation */
tick(&clock);

float alpha = 1.;
float beta = 0.;
int incrx=1;
int incry=1;
char trans='N';

sgemv(&trans,&n,&n,&alpha,&(a[0][0]),&n,x,&incrx,&beta,y,&incry);
eltime = tock(&clock);
printf("SGEMV: time = %lf, error = %f\n", eltime, checkans(y,n));

return 0;
}

关于c - 矩阵和 vector 乘法优化算法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9897480/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com