1
169
一个好的起点是这本伟大的书 The Science of Programming Matrix Computations 罗伯特·范德盖恩和恩里克·昆塔纳·奥特合著。他们提供免费下载版本。 BLAS分为三个级别:
顺便说一句,大多数(甚至全部)高性能BLAS实现都不是用Fortran实现的。ATLAS是在C中实现的。GotoBLAS/OpenBLAS是在C中实现的,其性能关键部件是在汇编程序中实现的。只有BLAS的参考实现是用Fortran实现的。然而,所有这些BLAS实现都提供了一个Fortran接口,这样它就可以与LAPACK链接(LAPACK从BLAS获得所有性能)。 优化编译器在这方面起着次要作用(对于GotoBLAS/OpenBLAS来说,编译器根本不重要)。 IMHO没有BLAS实现使用诸如铜匠维诺格拉德算法或斯特拉森算法之类的算法。可能的原因是:
BLIS papers . 他们写得非常好。在我的讲座“高性能计算的软件基础”中,我根据他们的论文实现了矩阵积。实际上,我实现了矩阵乘积的几个变体。最简单的变体完全用纯C编写,代码不到450行。所有其他变体只是优化了循环
矩阵积的总体性能 只有 取决于这些循环。大约99.9%的时间是在这里度过的。在其他变体中,我使用内部函数和汇编代码来提高性能。您可以在此处看到教程介绍了所有变体: ulmBLAS: Tutorial on GEMM (Matrix-Matrix Product) 再加上BLIS论文,我们就很容易理解像英特尔MKL这样的库是如何获得这样的性能的。为什么使用行或列主存储并不重要! 最终基准如下(我们称之为ulmBLAS项目): Benchmarks for ulmBLAS, BLIS, MKL, openBLAS and Eigen
我还写了一些关于BLAS如何用于数值线性代数问题的教程,如求解线性方程组: High Performance LU Factorization
好的,给你: Coding a Cache Optimized Parallel LU Factorization 附言:我也做了一些改善uBLAS性能的实验。实际上,提高(是的,玩文字游戏:)uBLAS的性能非常简单: 这里有一个类似的项目 BLAZE |
2
28
首先,BLAS只是一个大约50个函数的接口。该接口有许多相互竞争的实现。
大多数实现都以或多或少明显的方式将每个操作分解为小维矩阵或向量操作。例如,大型1000x1000矩阵乘法可能会分解为50x50矩阵乘法序列。 这些固定大小的小尺寸操作(称为内核)在特定于CPU的汇编代码中使用其目标的几个CPU功能进行硬编码:
此外,在典型的map-reduce设计模式中,这些内核可以使用多个线程(CPU内核)彼此并行执行。
(提示:这就是为什么如果您使用ATLAS,您最好为您的特定机器手动构建和调整库,然后使用预构建的库。) |
3
16
首先,有比你现在使用的更有效的矩阵乘法算法。 其次,您的CPU一次可以执行多条指令。 CPU每个周期执行3-4条指令,如果使用SIMD单元,每条指令处理4个浮点或2个双倍。(当然,这个数字也不准确,因为CPU通常每个周期只能处理一条SIMD指令)
|
4
11
我不知道BLAS的具体实现,但是有比O(n3)复杂度更好的更有效的矩阵乘法算法。众所周知的是 Strassen Algorithm |
5
4
第二个问题的大多数参数——汇编程序、分解成块等(但不是少于N^3的算法,它们确实是过度开发的)——都发挥了作用。但是,算法的低速度主要是由矩阵大小和三个嵌套循环的不幸排列造成的。您的矩阵太大,无法立即放入缓存中。您可以重新排列循环,以便尽可能多地对缓存中的一行执行操作,这样可以显著减少缓存刷新(顺便说一句,将块拆分为小块具有类似效果,如果块上的循环以类似方式排列,效果最佳)。下面是方形矩阵的模型实现。在我的计算机上,与标准实现(如您的)相比,它的时间消耗约为1:10。 换句话说:永远不要按照我们在学校学过的“行乘以列”方案编写矩阵乘法程序。 在重新排列循环之后,通过展开循环、汇编代码等获得了更多的改进。
还有一句话:在我的计算机上,这个实现甚至比用BLAS例程cblas_dgemm(在您的计算机上试用!)替换所有的实现更好。但直接调用Fortran库的dgemm_要快得多(1:4)。我认为这个例程实际上不是Fortran而是汇编代码(我不知道库中有什么,我没有源代码)。我完全不清楚的是,为什么cblasúU dgemm没有那么快,因为据我所知,它只是dgemmú的包装。 |
6
3
iPhone matrix functions -它们比C版本快8倍多,甚至还没有“优化”组装-还没有管道衬里,也没有不必要的堆栈操作。 此外,您的代码不是“ restrict correct “-编译器如何知道当它修改C时,它不是在修改A和B? |
7
2
对于MM乘法中的原始代码,大多数操作的内存引用是性能差的主要原因。内存运行速度比缓存慢100-1000倍。 大部分的加速来自于在MM乘法中对这个三环函数采用循环优化技术。采用了两种主回路优化技术;展开和阻塞。关于展开,我们展开最外层的两个循环,并将其阻塞,以便在缓存中重用数据。外部循环展开通过减少在整个操作过程中不同时间对相同数据的内存引用数量,帮助优化临时数据访问。在特定编号处阻塞循环索引,有助于将数据保留在缓存中。您可以选择优化二级缓存或三级缓存。 |
8
-25
另一件事是Fortran按列存储数据,而C按行存储数据。我还没有检查过你的代码,但是要注意你是如何执行产品的。在C语言中,您必须按行扫描:这样您可以沿连续内存扫描阵列,从而减少缓存未命中。缓存未命中是效率低下的第一个原因。 第三,这取决于您使用的blas实现。有些实现可能是用汇编语言编写的,并针对您使用的特定处理器进行了优化。netlib版本是用fortran 77编写的。 此外,您正在执行许多操作,其中大多数是重复和冗余的。所有这些获得索引的乘法都对性能有害。我真的不知道BLAS是如何做到这一点的,但有很多技巧可以防止昂贵的操作。
试试看,我相信你会省下一些钱。 关于你的#1问题,原因是如果你使用一个简单的算法,矩阵乘法的比例是O(n^3)。有些算法 scale much better . |
Fib · 查找R包中使用的Fortran文件 2 年前 |
Holmz · 使用ifort 2020和OneAPI更改字符串行为? 2 年前 |
imronuke · nvfortran编译器有用标志的建议 2 年前 |
R. N · gfortran指针过程类型问题 6 年前 |
ptb · 将Torvalds的“好品味”应用于Fortran链表 6 年前 |