为什么MATLAB在矩阵乘法中如此之快?


190

我正在使用CUDA,C ++,C#,Java建立一些基准,并使用MATLAB进行验证和矩阵生成。当我使用MATLAB执行矩阵乘法时,2048x2048甚至更大的矩阵几乎都会立即相乘。

             1024x1024   2048x2048   4096x4096
             ---------   ---------   ---------
CUDA C (ms)      43.11      391.05     3407.99
C++ (ms)       6137.10    64369.29   551390.93
C# (ms)       10509.00   300684.00  2527250.00
Java (ms)      9149.90    92562.28   838357.94
MATLAB (ms)      75.01      423.10     3133.90

只有CUDA具有竞争力,但是我认为至少C ++会比较接近,并且速度不会慢60倍。我也不知道该如何看待C#结果。该算法与C ++和Java相同,但与相比有很大2048的不同1024

MATLAB如何快速执行矩阵乘法?

C ++代码:

float temp = 0;
timer.start();
for(int j = 0; j < rozmer; j++)
{
    for (int k = 0; k < rozmer; k++)
    {
        temp = 0;
        for (int m = 0; m < rozmer; m++)
        {
            temp = temp + matice1[j][m] * matice2[m][k];
        }
        matice3[j][k] = temp;
    }
}
timer.stop();

14
可能是您使用哪种算法的问题。
罗伯特·J。

24
确保Matlab不会缓存您的结果,这是一个棘手的野兽。首先确保计算实际上正在执行,然后进行比较。
rubenvb


10
实际上,我确实认为这篇文章很有趣,但是我真的很想看到更合适的基准。例如,我认为Matlab R2011a自动使用多线程,而矩阵乘法是使用Intel的mkl / blas库实现的。因此,我猜想如果使用mkl调用进行矩阵乘法,则c ++会更快。问题是Matlab的开销是多少。我知道这取决于矩阵乘法的其他细节,但是上述数字目前已经毫无意义。
卢卡斯

1
您可以将运行时间O(n ^ 2.81)的“ Strassen算法”用于大型方阵乘法,这比以O(n ^ 3)运行的本机乘法快10倍。SSE / AVX还可帮助您将代码执行速度提高8-20倍左右。总之,您可以比Matlab更快地实现ac实现。
杜加恩

Answers:


85

这是在装有Tesla C2070的计算机上使用MATLAB R2011a + 并行计算工具箱的结果:

>> A = rand(1024); gA = gpuArray(A);
% warm up by executing the operations a couple of times, and then:
>> tic, C = A * A; toc
Elapsed time is 0.075396 seconds.
>> tic, gC = gA * gA; toc
Elapsed time is 0.008621 seconds.

MATLAB使用高度优化的库进行矩阵乘法,这就是为什么简单的MATLAB矩阵乘法如此之快的原因。该gpuArray版本使用MAGMA

在配备Tesla K20c的计算机上使用R2014a进行更新,以及新功能timeitgputimeit功能:

>> A = rand(1024); gA = gpuArray(A);
>> timeit(@()A*A)
ans =
    0.0324
>> gputimeit(@()gA*gA)
ans =
    0.0022

在具有16个物理核心和Tesla V100的WIN64计算机上使用R2018b更新

>> timeit(@()A*A)
ans =
    0.0229
>> gputimeit(@()gA*gA)
ans =
   4.8019e-04

(注意:在某些时候(我忘记了确切的时间)gpuArray从MAGMA切换到cuBLAS- gpuArray尽管MAGMA仍用于某些操作)


为什么这么重要?
疯狂物理学家

为什么重要?我试图深入了解MATLAB在各种情况下使用的库,以解释为什么MATLAB的性能很好-即因为它使用了高度优化的数值库。
Edric

175

与“ MATLAB使用高度优化的库”或“ MATLAB使用MKL”一次在Stack Overflow上相比,此类问题还是很常见。

历史:

矩阵乘法(连同矩阵向量,向量向量乘法以及许多矩阵分解)是线性代数中最重要的问题。从早期开始,工程师就一直在使用计算机解决这些问题。

我不是历史专家,但是很明显,那时,每个人都只是用简单的循环重写了他的FORTRAN版本。随之而来的是一些标准化,其中包括“内核”(基本例程)的识别,这是解决大多数线性代数问题所需要的。然后,在称为“基本线性代数子程序(BLAS)”的规范中将这些基本操作标准化。然后,工程师可以在代码中调用这些经过良好测试的标准BLAS例程,从而使工作变得更加轻松。

BLAS:

BLAS从1级(定义标量向量和矢量向量运算的第一个版本)发展到2级(向量矩阵运算)到3级(矩阵矩阵运算),并提供了越来越多的“内核”,因此更加标准化以及更多基本的线性代数运算。最初的FORTRAN 77实现仍可在Netlib的网站找到

为了获得更好的性能:

因此,多年来(尤其是在BLAS 1级和2级发行之间:80年代初),随着矢量操作和缓存层次结构的出现,硬件发生了变化。这些改进使得有可能大大提高BLAS子例程的性能。然后,不同的供应商带来了越来越高效的BLAS例程实现。

我不知道所有的历史实现(当时我还不是出生或孩子),但是其中两个最著名的实现出现在2000年代初:Intel MKL和GotoBLAS。您的Matlab使用Intel MKL,它是一个非常好的,经过优化的BLAS,它可以解释您所看到的出色性能。

矩阵乘法的技术细节:

那么,为什么Matlab(MKL)这么快dgemm(双精度通用矩阵-矩阵乘法)呢?简而言之:因为它使用向量化和良好的数据缓存。用更复杂的术语来说:请参阅乔纳森·摩尔(Jonathan Moore)提供的文章

基本上,当您使用提供的C ++代码执行乘法时,您根本就不会缓存。由于我怀疑您创建了一个指向行数组的指针数组,因此您在内部循环中对“ matice2”的第k列的访问matice2[m][k]非常缓慢。确实,当您访问时matice2[0][k],必须获取矩阵数组0的第k个元素。然后,在下一次迭代中,您必须访问matice2[1][k],这是另一个数组(数组1)的第k个元素。然后,在下一次迭代中,您将访问另一个数组,依此类推...由于整个矩阵matice2无法容纳最高的缓存(8*1024*1024字节大),因此程序必须从主内存中获取所需的元素,从而损失了很多时间。

如果只是转置矩阵,以便访问位于连续的内存地址中,则代码将已经运行得更快,因为现在编译器可以同时将整个行加载到缓存中。只需尝试以下修改版本:

timer.start();
float temp = 0;
//transpose matice2
for (int p = 0; p < rozmer; p++)
{
    for (int q = 0; q < rozmer; q++)
    {
        tempmat[p][q] = matice2[q][p];
    }
}
for(int j = 0; j < rozmer; j++)
{
    for (int k = 0; k < rozmer; k++)
    {
        temp = 0;
        for (int m = 0; m < rozmer; m++)
        {
            temp = temp + matice1[j][m] * tempmat[k][m];
        }
        matice3[j][k] = temp;
    }
}
timer.stop();

因此,您可以看到缓存局部性如何极大地提高了代码的性能。现在,实际的dgemm实现将其利用到了非常广泛的水平:它们对由TLB的大小(翻译后备缓冲区,长话短说:可以有效缓存的内容)定义的矩阵块执行乘法运算,以便将其流式传输到处理器可以处理的数据量。另一方面是向量化,它们使用处理器的向量化指令来实现最佳指令吞吐量,而跨平台的C ++代码实际上无法做到这一点。

最后,人们声称这是因为Strassen或Coppersmith-Winograd算法是错误的,由于上述硬件考虑,这两种算法在实践中均无法实现。


2
我刚刚观看了Scott Meyers的视频,介绍了缓存大小的重要性以及使数据适合缓存行大小的问题,以及在源中没有共享数据但最终在硬件上共享数据的多线程解决方案可能会遇到的问题/ core-thread level: youtu.be/WDIkqP4JbkE
WillC '18



11

答案是LAPACKBLAS库使MATLAB在矩阵运算上的速度令人目眩,而不是MATLAB的任何专有代码。

在C ++代码中使用LAPACK和/或BLAS库进行矩阵运算,您应获得与MATLAB类似的性能。这些库应该可以在任何现代系统上免费使用,并且部分库是几十年来在学术界开发的。请注意,有多种实现,包括一些封闭源代码,例如Intel MKL

这里提供了有关BLAS如何获得高性能的讨论


顺便说一句,在我的经验中,直接从c调用LAPACK库是一件很痛苦的事(但值得)。您需要非常仔细地阅读文档。


8

进行矩阵乘法时,您需要使用朴素乘法方法,该方法耗时为O(n^3)

存在矩阵乘法O(n^2.4)。这意味着n=2000您的算法所需的计算量约为最佳算法的100倍。
您应该真正在Wikipedia页面上查看矩阵乘法,以获取有关实现它的有效方法的更多信息。


MATLAB和MATLAB可能会使用这种算法,因为1024 * 1024矩阵乘法的时间小于2048 * 2048矩阵乘法的时间的8倍!做得好MATLAB的人。
雷诺

4
尽管它们具有理论上的优势,但我相当怀疑它们是否使用“高效”乘法算法。即使施特拉森的算法实现困难,铜匠,威诺格拉德算法,你可能已经读到只是普通的实用的(现在)。:另外,相关的SO线程stackoverflow.com/questions/17716565/...
Ernir

该算法仅适用于非常大的矩阵。

@Renaud。那就是相对固定开销的定义
疯狂物理学家

6

根据您所使用的Matlab版本,我相信它可能已经在使用您的GPU。

另一件事; Matlab跟踪矩阵的许多属性。此外,还对角线,埃尔米特线等进行了优化,并专门基于其算法。也许它基于您正在传递的零矩阵而专门化,或者类似的东西?也许是缓存重复的函数调用,这会弄乱您的时间安排?也许它可以优化重复使用的矩阵产品?

为防止此类情况发生,请使用随机数矩阵,并确保通过将结果打印到屏幕或磁盘等来强制执行。


4
作为大量的ML用户,我可以告诉您他们还没有使用GPGPU。新版本的matlab一定要使用SSE1 / 2。但是我已经做了测试。一个执行逐元素乘法的MexFunction的运行速度是其两倍A.*B。因此,OP几乎肯定会在某事上发疯。
KitsuneYMG 2011年

6
带有并行计算工具箱的Matlab 可以使用CUDA GPU,但这很明确-您必须将数据推送到GPU。
Edric

我使用M1 = single(rand(1024,1024)* 255); M2 =单(rand(1024,1024)* 255); 并且M3 = M1 * M2; ...然后写入浮点数的二进制文件,所有操作都非常快。

3

MATLAB使用来自英特尔的LAPACK的高度优化的实现,称为英特尔数学内核库(Intel MKL),特别是dgemm函数。速度该库利用了包括SIMD指令和多核处理器在内的处理器功能。他们没有记录他们使用哪种特定算法。如果要从C ++调用英特尔MKL,应该会看到类似的性能。

我不确定MATLAB用于GPU乘法的库是什么,但是可能类似于nVidia CUBLAS


1
您是对的,但是您看到这个答案了吗?但是,IPP不是MKL,并且MKL与IPP相比具有更优越的线性代数性能。此外,IPP在最新版本中不推荐使用其矩阵数学模块。
chappjc 2015年

抱歉,我的意思是MKL不是IPP
gregswiss 2015年

您是对的,另一个答案可以解决这个问题。太冗长了,我错过了。
gregswiss 2015年

2

对“为什么matlab比其他程序执行xxx更快的原因”的普遍回答是,matlab具有许多内置的优化功能。

所使用的其他程序通常不具有这些功能,因此人们应用自己的创意解决方案,这比专业优化的代码慢得多。

这可以用两种方式解释:

1)通用/理论方法:Matlab并没有明显更快,您只是在做基准测试而已

2)现实的方式:对于Matlab来说,在实践中速度更快,因为c ++语言太容易以无效的方式使用。


7
他正在将MATLAB速度与他在两分钟内编写的函数的速度进行比较。我可以在10分钟内编写一个更快的函数,或者在两个小时内编写一个更快的函数。MATLAB专家们花了两个多小时来加快他们的矩阵乘法。
gnasher729 2014年

2

鲜明的对比不仅是由于Matlab的惊人优化(已经在许多其他答案中进行了讨论),还在于您将矩阵表示为对象的方式。

似乎您已将矩阵设为列表列表?列表列表包含指向列表的指针,这些指针随后包含矩阵元素。包含列表的位置是任意分配的。当您遍历第一个索引(行号?)时,内存访问时间非常重要。相比之下,为什么不尝试使用以下方法将矩阵实现为单个列表/向量?

#include <vector>

struct matrix {
    matrix(int x, int y) : n_row(x), n_col(y), M(x * y) {}
    int n_row;
    int n_col;
    std::vector<double> M;
    double &operator()(int i, int j);
};

double &matrix::operator()(int i, int j) {
    return M[n_col * i + j];
}

应该使用相同的乘法算法,以使翻牌次数相同。(对于大小为n的平方矩阵,为n ^ 3)

我想请您安排时间,以便结果与您先前(在同一台计算机上)的结果相媲美。通过比较,您将确切显示可访问的内存时间!


2

在C ++中,它很慢,因为您没有使用多线程。本质上,如果A = BC,它们都是矩阵,则A的第一行可以独立于第二行进行计算,依此类推。如果A,B和C都是n×n矩阵,则可以通过以下方法来加快乘法速度: n ^ 2的因数

a_ {i,j} = sum_ {k} b_ {i,k} c_ {k,j}

例如,如果您使用Eigen [ http://eigen.tuxfamily.org/dox/GettingStarted.html ],则内置多线程,并且线程数是可调整的。


2

因为MATLAB是最初为数字线性代数(矩阵运算)开发的一种编程语言,它具有专门为矩阵乘法开发的库。而现在, MATLAB还可以使用的GPU(图形处理单元),这种额外。

如果我们查看您的计算结果:

             1024x1024   2048x2048   4096x4096
             ---------   ---------   ---------
CUDA C (ms)      43.11      391.05     3407.99
C++ (ms)       6137.10    64369.29   551390.93
C# (ms)       10509.00   300684.00  2527250.00
Java (ms)      9149.90    92562.28   838357.94
MATLAB (ms)      75.01      423.10     3133.90

然后我们可以看到不仅MATLAB在矩阵乘法方面如此之快:CUDA C(来自NVIDIA的编程语言)比MATLAB具有更好的结果。CUDA C还具有专门为矩阵乘法开发的库,并且使用GPU。

MATLAB的简短历史

新墨西哥大学计算机科学系主任克莱夫·莫勒(Cleve Moler)于1970年代后期开始开发MATLAB。他设计了它,使学生可以访问LINPACK(用于执行数值线性代数的软件库)和EISPACK(是用于线性代数数值计算的软件库),而无需学习Fortran。它很快传播到其他大学,并在应用数学界引起了广泛的关注。工程师Jack Little在1983年Moler对斯坦福大学的访问中接触了它。认识到它的商业潜力,他与Moler和Steve Bangert一起加入了。他们用C语言重写了MATLAB,并于1984年成立了MathWorks,以继续其发展。这些重写的库称为JACKPAC。2000年,对MATLAB进行了重写,以使用一组更新的用于矩阵处理的库LAPACK(这是用于数字线性代数的标准软件库)。

资源

什么是CUDA C

CUDA C还使用专门为矩阵乘法而开发的库,例如OpenGL(开放图形库)。它还使用GPU和Direct3D(在MS Windows上)。

CUDA平台的设计工作,如C,C ++和Fortran编程语言。与以前的Direct3DOpenGL之类的要求高级图形编程技能的API相比,这种可访问性使并行编程专家更容易使用GPU资源。而且,CUDA支持诸如OpenACCOpenCL之类的编程框架。

在此处输入图片说明

CUDA处理流程示例:

  1. 将数据从主内存复制到GPU内存
  2. CPU启动GPU计算内核
  3. GPU的CUDA内核并行执行内核
  4. 将结果数据从GPU内存复制到主内存

比较CPU和GPU的执行速度

我们运行了一个基准测试,其中测量了在Intel Xeon Processor X5650上使用NVIDIA Tesla C2050 GPU执行64、128、512、1024和2048网格大小的50个步骤所花费的时间。

在此处输入图片说明

对于2048的网格大小,该算法显示计算时间减少了7.5倍,从CPU上的一分钟减少到GPU上的不到10秒。日志比例图显示,对于较小的网格大小,CPU实际上更快。但是,随着技术的发展和成熟,GPU解决方案越来越能够处理较小的问题,我们希望这种趋势会持续下去。

资源

从《 CUDA C编程指南》的简介中:

在市场对实时高清3D图形的无限需求的推动下,可编程图形处理器单元(GPU)已经发展成为高度并行,多线程,多核的处理器,具有巨大的计算能力和非常高的内存带宽,如Figure 1和所示Figure 2

图1. CPU和GPU的每秒浮点运算

在此处输入图片说明

图2。CPU和GPU的内存带宽

在此处输入图片说明

CPU和GPU之间的浮点功能差异背后的原因是,GPU专用于计算密集型,高度并行的计算-正是图形渲染所针对的-并因此进行了设计,以使更多的晶体管致力于数据处理而不是数据缓存和流控制,如示意图所示Figure 3

图3。GPU为数据处理投入了更多晶体管

在此处输入图片说明

更具体地说,GPU特别适合解决可以表示为数据并行计算的问题-在许多数据元素上并行执行同一程序-具有很高的算术强度-算术运算与内存运算的比率。由于对每个数据元素执行相同的程序,因此对复杂的流控制有较低的要求,并且由于它对许多数据元素执行并且具有很高的算术强度,因此可以通过计算而不是大数据缓存来隐藏内存访问延迟。 。

数据并行处理将数据元素映射到并行处理线程。许多处理大型数据集的应用程序可以使用数据并行编程模型来加快计算速度。在3D渲染中,大量像素和顶点被映射到并行线程。同样,图像和媒体处理应用程序(例如渲染图像的后处理,视频编码和解码,图像缩放,立体视觉和模式识别)可以将图像块和像素映射到并行处理线程。实际上,从通用信号处理或物理模拟到计算金融或计算生物学,数据并行处理可加速图像渲染和处理领域之外的许多算法。

资源

高级阅读


一些有趣的facs

我已经编写了与Matlab一样快的C ++矩阵乘法,但是要多加注意。(在Matlab为此使用GPU之前)。

这个答案中引用。


2
最后的引用不是“事实”,而是吹牛。自从发布该人以来,该人已经收到了一些代码请求。但是看不到任何代码。
克里斯·伦戈

1
您对可以在GPU上进行计算的速度的描述根本没有解决这个问题。我们都知道,128个小核心可以完成比2个大核心更多的相同单调工作。“现在,MATLAB还可为此额外使用GPU(图形处理单元)。” 是的,但默认情况下不是。正常矩阵乘法仍使用BLAS。
克里斯·伦戈

@CrisLuengo,好的,这不是事实!也许您对他的“吹牛”是正确的–我们对此一无所知,我们也不知道他为什么不回答。提第二条评论:GPU上的计算描述回答了这个问题,因为对于线性代数中的矩阵乘法,它使用浮点运算。也许并非所有人都能理解,但我认为他们必须了解这一基本知识。在其他情况下,在阅读有关矩阵的文章之前,他们必须首先学习此基础知识。如果有人给我写信,那么我将添加此详细信息。谢谢!
巴拉塔(Bharata)

@CrisLuengo,我写了这个词"additionally"。这意味着:可以使用。这也意味着普通矩阵乘法仍使用软件库。您是否认为我必须更改自己的帖子才能更容易理解?谢谢您的意见!
巴拉塔(Bharata)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.