【问题标题】:Does MKL optimize cblas for *major order?MKL 是否针对 *major 订单优化 cblas?
【发布时间】:2015-12-03 10:14:47
【问题描述】:

我正在使用mklcblas_dgemm,目前与CblasRowMajorCblasNoTransCblasNotrans一起用于我的矩阵。

我知道c 是一种以行为主的语言,而dgemm 是一种以列为主的算法。如果我链接到mkl,我很想知道切换矩阵的顺序是否会对cblas_dgemm 算法产生任何影响。 mkl 是否足够聪明,可以在幕后做一些我试图优化矩阵乘法的事情?如果不是,使用mkl 执行矩阵乘法的最佳方法是什么?

【问题讨论】:

  • 我想我曾经读过它,但我现在找不到它。为什么不把两个大矩阵相乘,看看有没有区别?
  • 您似乎假设有 one DGEMM 实现可用于正常和转置有序情况。在像 MKL 这样的性能优化 BLAS 中,几乎可以肯定情况并非如此,将 DGEMM 称为“算法”可能是不正确的。它是一个由一个或多个算法实现的功能,这些算法是根据内部启发式算法选择的,作为程序员,您不应该知道或需要了解太多。

标签: c algorithm matrix blas intel-mkl


【解决方案1】:

TL;DR:简而言之无论您使用 row-major 还是 column-major 排序来执行矩阵-矩阵乘法都无关紧要使用 MKL(和其他 BLAS 实现)。


我知道 c 是一种以行为主的语言,而 dgemm 是一种以列为主的算法。

DGEMM不是列主要算法,它是用于计算具有一般矩阵的矩阵-矩阵乘积的 BLAS 接口。 DGEMM(以及大部分 BLAS)的通用参考实现是Netlib's,它是用 Fortran 编写的。它假设列优先顺序的唯一原因是因为 Fortran 是一种列优先顺序语言。 DGEMM(和相应的 BLAS 3 级函数)不是专门用于列主要数据。

DGEMM 计算什么?

基础数学中的 DGEMM 执行 2D matrix-matrix multiplication。用于乘以 2D 矩阵的标准 算法要求您沿其行遍历一个矩阵,并沿其列遍历另一个矩阵。为了执行矩阵-矩阵乘法,AB = C,我们将 A 的行乘以 B 的列strong> 生成 C。因此,输入矩阵的顺序无关紧要,因为一个矩阵必须沿其行遍历,而另一个矩阵必须沿其列遍历。

使用 MKL 研究行优先和列优先 DGEMM 计算

英特尔 MKL 足够聪明,可以在后台利用这一点,并为 row-majorcolumn-major 数据提供完全相同的性能。

cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, ...);

cblas_dgemm(CblasColMajor, CblasNoTrans, CblasNoTrans, ...);

将以相似的性能执行。我们可以用一个相对简单的程序来测试一下

#include <float.h>
#include <mkl.h>
#include <omp.h>
#include <stdio.h>

void init_matrix(double *A, int n, int m, double d);
void test_dgemm(CBLAS_LAYOUT Layout, double *A, double *B, double *C, const MKL_INT m, const MKL_INT n, const MKL_INT k, int nSamples, double *timing);
void print_summary(const MKL_INT m, const MKL_INT n, const MKL_INT k, const int nSamples, const double *timing);

int main(int argc, char **argv) {
    MKL_INT n, k, m;
    double *a, *b, *c;
    double *timing;
    int nSamples = 1;

    if (argc != 5){
        fprintf(stderr, "Error: Wrong number of arguments!\n");
        fprintf(stderr, "usage: %s mMatrix nMatrix kMatrix NSamples\n", argv[0]);
        return -1;
    }

    m = atoi(argv[1]);
    n = atoi(argv[2]);
    k = atoi(argv[3]);

    nSamples = atoi(argv[4]);

    timing = malloc(nSamples * sizeof *timing);

    a = mkl_malloc(m*k * sizeof *a, 64);
    b = mkl_malloc(k*n * sizeof *a, 64);
    c = mkl_calloc(m*n, sizeof *a, 64);

    /** ROW-MAJOR ORDERING **/
    test_dgemm(CblasRowMajor, a, b, c, m, n, k, nSamples, timing);

    /** COLUMN-MAJOR ORDERING **/
    test_dgemm(CblasColMajor, a, b, c, m, n, k, nSamples, timing);

    mkl_free(a);
    mkl_free(b);
    mkl_free(c);
    free(timing);
}

void init_matrix(double *A, int n, int m, double d) {
    int i, j;
    #pragma omp for schedule (static) private(i,j)
    for (i = 0; i < n; ++i) {
        for (j = 0; j < m; ++j) {
            A[j + i*n] = d * (double) ((i - j) / n);
        }
    }
}

void test_dgemm(CBLAS_LAYOUT Layout, double *A, double *B, double *C, const MKL_INT m, const MKL_INT n, const MKL_INT k, int nSamples, double *timing) {
    int i;
    MKL_INT lda = m, ldb = k, ldc = m;
    double alpha = 1.0, beta = 0.0;

    if (CblasRowMajor == Layout) {
        printf("\n*****ROW-MAJOR ORDERING*****\n\n");
    } else if (CblasColMajor == Layout) {
        printf("\n*****COLUMN-MAJOR ORDERING*****\n\n");
    }

    init_matrix(A, m, k, 0.5);
    init_matrix(B, k, n, 0.75);
    init_matrix(C, m, n, 0);

    // First call performs any buffer/thread initialisation
    cblas_dgemm(Layout, CblasNoTrans, CblasNoTrans, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc);

    double tmin = DBL_MAX, tmax = 0.0;
    for (i = 0; i < nSamples; ++i) {
        init_matrix(A, m, k, 0.5);
        init_matrix(B, k, n, 0.75);
        init_matrix(C, m, n, 0);

        timing[i] = dsecnd();
        cblas_dgemm(Layout, CblasNoTrans, CblasNoTrans, m, n, k, alpha, A, lda, B, ldb, beta, C, ldc);
        timing[i] = dsecnd() - timing[i];

        if (tmin > timing[i]) tmin = timing[i];
        else if (tmax < timing[i]) tmax = timing[i];
    }

    print_summary(m, n, k, nSamples, timing);
}

void print_summary(const MKL_INT m, const MKL_INT n, const MKL_INT k, const int nSamples, const double *timing) {
    int i;

    double tavg = 0.0;
    for(i = 0; i < nSamples; i++) {
        tavg += timing[i];
    }
    tavg /= nSamples;

    printf("#Loop | Sizes  m   n   k  | Time (s)\n");
    for(i = 0; i < nSamples; i++) {
        printf("%4d %12d %3d %3d  %6.4f\n", i + 1 , m, n, k, timing[i]);
    }

    printf("Summary:\n");
    printf("Sizes  m   n   k  | Avg. Time (s)\n");
    printf(" %8d %3d %3d %12.8f\n", m, n, k, tavg);
}

在我的系统上会产生

$ ./benchmark_dgemm 1000 1000 1000 5
*****ROW-MAJOR ORDERING*****

#Loop | Sizes  m   n   k  | Time (s)
   1         1000 1000 1000  0.0589
   2         1000 1000 1000  0.0596
   3         1000 1000 1000  0.0603
   4         1000 1000 1000  0.0626
   5         1000 1000 1000  0.0584
Summary:
Sizes  m   n   k  | Avg. Time (s)
     1000 1000 1000   0.05995692

*****COLUMN-MAJOR ORDERING*****

#Loop | Sizes  m   n   k  | Time (s)
   1         1000 1000 1000  0.0597
   2         1000 1000 1000  0.0610
   3         1000 1000 1000  0.0581
   4         1000 1000 1000  0.0594
   5         1000 1000 1000  0.0596
Summary:
Sizes  m   n   k  | Avg. Time (s)
     1000 1000 1000   0.05955171

我们可以看到 column-major 排序时间和 row-major 排序时间之间几乎没有区别。 column-major 0.0595 秒row-major 0.0599 秒。再次执行此操作可能会产生以下结果,其中行优先计算快 0.00003 秒。

$ ./benchmark_dgemm 1000 1000 1000 5
*****ROW-MAJOR ORDERING*****

#Loop | Sizes  m   n   k  | Time (s)
   1         1000 1000 1000  0.0674
   2         1000 1000 1000  0.0598
   3         1000 1000 1000  0.0595
   4         1000 1000 1000  0.0587
   5         1000 1000 1000  0.0584
Summary:
Sizes  m   n   k  | Avg. Time (s)
     1000 1000 1000   0.06075310

*****COLUMN-MAJOR ORDERING*****

#Loop | Sizes  m   n   k  | Time (s)
   1         1000 1000 1000  0.0634
   2         1000 1000 1000  0.0596
   3         1000 1000 1000  0.0582
   4         1000 1000 1000  0.0582
   5         1000 1000 1000  0.0645
Summary:
Sizes  m   n   k  | Avg. Time (s)
     1000 1000 1000   0.06078266

【讨论】:

  • 谢谢。我一直想做这样的分析,但被其他项目阻止了。您是否研究过行与列对非方阵的影响?我有非方阵 (800x10),我将其乘以转置以获得 (10x10) 的条件矩阵,我想知道当行和列维度不同时,同样的分析是否成立。
  • @Laurbert515 我刚刚对这些矩阵大小进行了基准测试,row-majorcolumn-major 之间的差异约为 1 微秒,其中在我的笔记本电脑上计算大约需要 5 微秒。在我的回答中,有时 row-major 更快,有时 column-major。大多数时候,英特尔 MKL 会根据输入矩阵大小选择最佳方法来计算结果。但是,可能有时您确实需要自己执行基准测试以确定最佳方法。对于大多数人(和案例)来说,这无关紧要。
  • 我执行了几个测试(通过我们的 C# MKL 包装器),发现“col”和“row”矩阵之间存在真正的差异(因子 x2)。接下来的代码(向量作为矩阵X矩阵) MKLAccess.AlignedMath.cblas_dgemm(NoTranspose, NoTranspose, 1.0, vectorMatrDouble, matrixMatrDouble, 0.0, resultMatrDouble);我还测试了 cblas_dgemm 与 cblas_sgemm 并且由于“列”测试显示相同的时间,但在“行”的情况下显示“x2”因子。测试数据:cblas_dgemm (A[1, 35] x B[35, 125324] =>R[1, 125324]
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多