Skip to content

flywheel1412/how-to-optimize-gemm

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

32 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

How To Optimize GEMM

行主序的GEMM优化方案

x86

cpufp

克隆高叔叔的X86浮点峰值测试工具,使用方法见README。

本机(Intel(R) Xeon(R) CPU E5-2678 v3 @ 2.50GHz 12 Cores)测试结果如下:

Thread(s): 1
fma fp32 perf: 97.3358 gflops.
fma fp64 perf: 48.8463 gflops.
avx fp32 perf: 48.7343 gflops.
avx fp64 perf: 24.3121 gflops.
sse fp32 perf: 25.9305 gflops.
sse fp64 perf: 12.9604 gflops.
Thread(s): 4
fma fp32 perf: 356.8164 gflops.
fma fp64 perf: 178.6532 gflops.
avx fp32 perf: 178.6297 gflops.
avx fp64 perf: 89.3076 gflops.
sse fp32 perf: 93.3519 gflops.
sse fp64 perf: 46.6798 gflops.

src

测试不同的优化方法对应的gflops。

峰值占比的计算方法为当前算法的gflops除以浮点测试工具中相同线程测出的最高峰值。

文件名 优化方法 gFLOPs 峰值占比 线程数
MMult1.h 无任何优化 1.4gflops 1.4% 1
MMult2.h 一次计算4个元素 1.5gflops 1.5% 1
MMult_1x4_3.h 一次计算4个元素 1.4gflops 1.4% 1
MMult_1x4_4.h 一次计算4个元素 1.4gflops 1.4% 1
MMult_1x4_5.h 一次计算4个元素(将4个循环合并为1个) 1.5gflops 1.5% 1
MMult_1x4_6.h 一次计算4个元素(我们在寄存器中累加C的元素,并对a的元素使用寄存器) 1.6gflops 1.6% 1
MMult_1x4_7.h 在MMult_1x4_6的基础上用指针来寻址B中的元素 5.0gflops 6% 1
MMult_1x4_8.h 在MMult_1x4_7的基础上循环展开四个(展开因子的相对任意选择) 5gflops 6% 1
MMult_1x4_9.h 在MMult_1x4_8的基础上使用间接寻址的方法 5gflops 6% 1
MMult_4x4_3.h 一次计算C中的4x4小块 1.4gflops 1.4% 1
MMult_4x4_4.h 一次计算C中的4x4小块 1.4gflops 1.4% 1
MMult_4x4_5.h 一次计算C中的4x4小块,将16个循环合并一个 1.5gflops 1.5% 1
MMult_4x4_6.h 一次计算C中的4x4小块(我们在寄存器中累加C的元素,并对a的元素使用寄存器) 8.2gflops 8.4% 1
MMult_4x4_7.h 在MMult_4x4_6的基础上用指针来寻址B中的元素 8.4gflops 8.6% 1
MMult_4x4_8.h 使用更多的寄存器 7.7gflops 7.7% 1
MMult_4x4_10.h SSE指令集优化 8.5gflops 8.7% 1
MMult_4x4_11.h SSE指令集优化, 并且为了保持较小问题规模所获得的性能,我们分块矩阵C(以及相应的A和B) 8.5gflops 8.7% 1
MMult_4x4_13.h SSE指令集优化, 对矩阵A和B进行Pack,这样就可以连续访问内存 33.0gflops 34.0% 1

armv7a

armv7afp

理论浮点峰值: 

fmla: 4x2(mul+add)*1.8gHz=14.4gFLOPs

实际浮点峰值测试

|架构|浮点峰值(GFlops)|
|--|--|
|Cortex-A53,armv7a|10.88|

达到硬件浮点峰值的75%

src

  • MMult_4x4_19和MMult_4x4_20来自tpoisonooo(白牛大佬)。
  • 额外测试了conv1x1s1.h(version3)(默认实现为8x4)的4x4分块方法,gflops为4.8gflops。
文件名 优化方法 gFLOPs 峰值占比 线程数
MMult1.h 无任何优化 0.24gflops 2.1% 1
MMult2.h 一次计算4个元素 0.24gflops 2.1% 1
MMult_1x4_3.h 一次计算4个元素 0.24gflops 2.1% 1
MMult_1x4_4.h 一次计算4个元素 0.24gflops 2.1% 1
MMult_1x4_5.h 一次计算4个元素(将4个循环合并为1个) 0.25gflops 2.2% 1
MMult_1x4_7.h 一次计算4个元素(我们在寄存器中累加C的元素,并对a的元素使用寄存器),用指针来寻址B中的元素 0.98gflops 9.0% 1
MMult_1x4_8.h 在MMult_1x4_7的基础上循环展开四个(展开因子的相对任意选择) 1.1gflops 10% 1
MMult_4x4_3.h 一次计算C中的4x4小块 0.24gflops 2.1% 1
MMult_4x4_4.h 一次计算C中的4x4小块 0.24gflops 2.1% 1
MMult_4x4_5.h 一次计算C中的4x4小块,将16个循环合并一个 0.25gflops 2.2% 1
MMult_4x4_6.h 一次计算C中的4x4小块(我们在寄存器中累加C的元素,并对a的元素使用寄存器) 1.75gflops 16.0% 1
MMult_4x4_7.h 在MMult_4x4_6的基础上用指针来寻址B中的元素 1.75gflops 16.0% 1
MMult_4x4_8.h 使用更多的寄存器 1.75gflops 16.0% 1
MMult_4x4_10.h NEON指令集优化 2.6gflops 23.8% 1
MMult_4x4_11.h NEON指令集优化, 并且为了保持较小问题规模所获得的性能,我们分块矩阵C(以及相应的A和B) 2.6gflops 23.8% 1
MMult_4x4_13.h NEON指令集优化, 对矩阵A和B进行Pack,这样就可以连续访问内存 2.6gflops 23.8% 1
MMult_4x4_18.h Neon Assembly,Cache优化 3.0gflops 27.5% 1
MMult_4x4_19.h MMult_4x4_18基础上+更长的pld+ldd+指令重排 3.8gflops 34.59% 1
MMult_4x4_20.h MMult_4x4_19基础上更换vldr + 简单调整ping pong 4.0gflops 36.7% 1
conv1x1s1.h(version1) 一次计算多行,neon汇编优化 3.4gflops 31.0% 1
conv1x1s1.h(version2) pack,kernel提前做,neon汇编优化,8x4分块 4.9gflops 45% 1
conv1x1s1.h(version3) pack,kernel提前做,输入NC4HW4,neon汇编优化,8x4分块 5.5gflops 50.5% 1
conv1x1s1.h(version4) idea from megengine pack,kernel提前做,输入NC4HW4,neon汇编优化,12x4分块 5.2gflops 47.8% 1
  • 猜测,分块时块的大小需要尽量大,恰好可以塞进Cache能获得最大性能,目前测试情况来看:
1x1 < 1x4 < 4x4 < 8x4 ...
  • 测试了12x4,发现并不是这样,和MegEngine的大佬交流之后知道A53的硬件利用率极限是75%,另外ldq和fmla不能双发射,所以需要将指令拆成ldx和ldr+ins,因为ldx可以和fmla双发射,ldr和ins可以双发射,就可以突破到75%,待编码验证。

  • 为什么A53极限硬件利用率为75%,因为ldr和ins都不能和fmla双发射,所以每3条fmla就需要带一条ldr+ins,fmla的吞吐极限就是3/4。

armv8a

armv8afp

理论浮点峰值: 

fmla: 4x2(mul+add)*1.55gHz=12.4gFLOPs (Jetson Nano)

|架构|浮点峰值(GFlops)|
|--|--|
|Cortex-A57,armv8a|11.39|

达到硬件浮点峰值的91.8%

src

文件名 优化方法 gFLOPs 峰值占比 线程数
MMult1.h 无任何优化 0.75gflops 6.5% 1
MMult2.h 一次计算4个元素 0.8gflops 7.0% 1
MMult_1x4_3.h 一次计算4个元素 0.8gflops 7.0% 1
MMult_1x4_4.h 一次计算4个元素 0.89gflops 7.8% 1
MMult_1x4_5.h 一次计算4个元素(将4个循环合并为1个) 1.57gflops 13.8% 1
MMult_1x4_7.h 一次计算4个元素(我们在寄存器中累加C的元素,并对A的元素使用寄存器),用指针来寻址B中的元素 1.92gflops 16.8% 1
MMult_1x4_8.h 在MMult_1x4_7的基础上循环展开四个(展开因子的相对任意选择) 1.28gflops 11.2% 1
MMult_4x4_3.h 一次计算C中的4x4小块 0.75gflops 6.5% 1
MMult_4x4_4.h 一次计算C中的4x4小块 0.80gflops 7.0% 1
MMult_4x4_5.h 一次计算C中的4x4小块,将16个循环合并一个 1.26gflops 11.0% 1
MMult_4x4_6.h 一次计算C中的4x4小块(我们在寄存器中累加C的元素,并对a的元素使用寄存器) 4.29gflops 37.6% 1
MMult_4x4_7.h 在MMult_4x4_6的基础上用指针来寻址B中的元素 4.12gflops 36.0% 1
MMult_4x4_8.h 使用更多的寄存器 4.3gflops 37.7% 1
MMult_4x4_10.h NEON指令集优化 4.3gflops 37.7% 1
MMult_4x4_11.h NEON指令集优化, 并且为了保持较小问题规模所获得的性能,我们分块矩阵C(以及相应的A和B) 4.3gflops 37.7% 1
MMult_4x4_13.h NEON指令集优化, 对矩阵A和B进行Pack,这样就可以连续访问内存 4.77gflops 41.8% 1
MMult_4x4_18.h Neon Assembly,Cache优化 8.44gflops 74.1% 1

相关链接

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C 61.9%
  • C++ 34.9%
  • Assembly 2.1%
  • Other 1.1%