击败典型的 BLAS 库矩阵乘法性能

计算科学 优化 C++ 矩阵 表现 布拉斯
2021-12-06 17:40:54

我们使用公式的沉闷的矩阵乘法算法

Cij=kAikBkj

通过字面上的 3 个循环,我们将得到一个非常慢的程序,因为我们没有利用处理器的矢量化能力。这种缓慢实现的一个例子是:

T comp;
for (int i = 0; i < lhs.rows(); i++)
{
    for (int j = 0; j < rhs.columns(); j++)
    {
        comp = 0;
        for (int k = 0; k < lhs.columns(); k++)
        {
            comp += lhs.at(i,k)*rhs.at(k,j);
        }
        result.at(i,j) = comp;
    }
}

我的问题是:如果我们考虑矢量化,我们会击败像 OpenBLAS 这样的典型库吗?.

为什么?因为我非常想专门化我的矩阵乘法,以至于 BLAS 无法提供我需要的东西。

如果我们假设这些矩阵是边长为的方阵,并且以列优先顺序存储并且在内存中是连续的(例如,std::vector,具有元素)。考虑以下代码片段:nn2

transpose(lhs); //apply in-place transpose
for (int i = 0; i < lhs.columns(); i++)
{
    for (int j = 0; j < rhs.columns(); j++)
    {
        result.at(i,j) = 
            std::transform(
            lhs.begin()+i*lhs.rows(),
            lhs.begin()+(i+1)*lhs.rows(),
            rhs.begin()+j*rhs.rows(),
            std::multiplies<T>);
    }
}
transpose(lhs);

现在这不仅考虑了矢量化,甚至还使用标准算法让编译器完全自由地进行它想要的所有优化。所以我的问题是:这会击败好的 BLAS 库吗?我还应该怎么做才能使这样的代码变得超级高效?

4个回答

合并评论:

,您不太可能击败典型的 BLAS 库,例如英特尔的 MKL、AMD 的数学核心库或 OpenBLAS。1 这些不仅使用矢量化,而且(至少对于主要功能)使用以特定于体系结构的汇编语言手写的内核,以便最佳地利用可用的矢量扩展(SSE、AVX)、多核和缓存重用. 例如,OpenBLAS 有

dgemm_kernel_16x2_haswell.S     
dgemm_kernel_4x4_haswell.S  
dgemm_kernel_4x8_haswell.S  
dgemm_kernel_4x8_sandy.S    
dgemm_kernel_6x4_piledriver.S   
dgemm_kernel_8x2_bulldozer.S    
dgemm_kernel_8x2_piledriver.S

这仅适用于x86_64体系结构——不仅每个指令集有不同的实现,还适用于不同的寄存器块(这意味着可以根据矩阵大小使用不同的内核)。当然,在可能的情况下,它们会使用(也经过优化的)BLAS2 和 BLAS1 操作。这不是编译器可以自动完成的。


1. BLAS 的 LAPACK 实现netlib并不真正重要——它是一个参考实现,应该被视为接口的(功能)规范。(见:http : //dx.doi.org/10.1145/355841.355847、http : //dx.doi.org/10.1145/42288.42291http://dx.doi.org/10.1145/77626.79170

如果我们考虑矢量化,我们会击败像 OpenBLAS 这样的典型库吗?

我不完全同意其他答案,我想说那种确实,像 Intel (R) MKL 这样的库具有使用大量优化的手写内核。另一方面,您无法在自己的机器上编译 Intel (R) MKL,因此它必须检测是否可以在运行时使用 AVX 指令(如果可以的话)。

这就是为什么我相信您实际上可以使用 C++ 模板库击败这些 BLAS 实现,这些模板库大量使用 SIMD 和 OpenMP 进行多线程处理。

Blaze 库就是一个很好的例子Bitbucket 存储库还有一个基准页面,他们还比较了BLAS 3 级例程。对于非常大的矩阵,Blaze 和 Intel (R) MKL 的速度几乎相同(可能内存有限),但对于较小的矩阵,Blaze 击败了 MKL。对于BLAS 2 级例程来说,这一点更为明显。

如前所述,netlibBLAS 根本没有优化,但它绝对是“refblas”。使用 IKML、ACML、OpenBLAS 或“您的供应商”BLAS,您(以某种方式)确信优化的 BLAS 的操作结果等于“refblas”直到已知错误。请注意:供应商(intel、amd、nvidia、...)努力在他们自己的平台上提供更优越的实现。他们有专业人士关心准确性(例如避免 Pentium FDIV 错误)并了解平台优势和劣势的详细信息,从而调整性能。您的平台最专业的“供应商”BLAS 很可能是您为该平台获得的最好的。OpenBLAS 针对几个流行的当代(不是未来!)平台进行了优化。如果您的代码与平台更加无关,如果没有必要,切勿尝试实现 BLAS 功能。

这不是答案,而是探索该主题的参考。

这里有一篇 Higham 关于矩阵乘法的文章(~ 1990 年)。

标题:在 3 级 BLAS 中利用快速矩阵乘法