我正在为iPhone实现基于切线距离的OCR解决方案,该解决方案严重依赖于253x7大小的浮点矩阵的快速乘法。为了证明概念,我实现了我自己的天真矩阵例程:
Matrix operator*(const Matrix& matrix) const {
if(cols != matrix.rows) throw "cant multiply!";
Matrix result(rows, matrix.cols);
for(int i = 0; i < result.rows; i++){
for(int j = 0; j < result.cols; j++){
T tmp = 0;
for(int k = 0; k < cols; k++){
tmp += at(i,k) * matrix.at(k,j);
}
result.at(i,j) = tmp;
}
}
return result;
}
如你所见,这是非常基本的。 在PoC表现良好之后,我决定通过合并Accelerate Framework的矩阵乘法(可能使用SIMD和其他奇特的东西来完成繁重的工作......)来进一步提高性能极限:
Matrix operator*(const Matrix& m) const {
if(cols != m.rows) throw "cant multiply!";
Matrix result(rows,m.cols);
cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, rows, m.cols, cols, 1, matrix, cols, m.matrix, m.cols, 1, result.matrix, result.cols);
return result;
}
令人震惊(至少对我来说),上面的代码花了两倍的时间来乘以矩阵!我尝试使用单精度而不是双精度,因为我怀疑它是与CPU的字大小相关的东西(32位浮点数与32位ARM上的64位双精度),但没有性能提升......
我做错了什么?我的253x7矩阵是否太小而不能显着提升性能?
答案 0 :(得分:1)
几个问题:
253 x 7乘以什么尺寸的矩阵?如果你正在做253x7 * 7x1,那么一个通用的乘法例程将花费大部分时间用于修改代码,并且调优的库可以做到这一点很少会使它比一个简单的实现更快
您正在使用什么硬件,iOS版本是什么?特别是对于双精度,较旧的硬件和较旧的iOS版本在性能方面更受限制。例如,在Cortex-A8上,双精度算术完全没有流水线,所以几乎没有任何一个库可以做任何事情来打败天真的实现。
如果其他矩阵不是非常小,并且硬件是最近的,请提交一个错误(意外的低性能绝对一个错误)。具有高纵横比的小矩阵很难在通用矩阵乘法中快速生成,但它仍然是一个很好的bug。
如果硬件/ iOS版本已经过时,您可能仍然希望使用Accelerate,因为它应该在较新的硬件/软件上表现得更好。
如果另一个矩阵很小,那么可能没什么可做的。 ARM上没有双精度SIMD,矩阵太小而无法从缓存阻塞中受益,并且矩阵的尺寸也太小而无法从循环展开中获益。
如果您知道先验您的矩阵将完全是253x7 * 7x ???,那么通过完全展开,您应该能够比天真的实现和任何通用库做得更好矩阵乘法的内维。
答案 1 :(得分:0)
基本上,是的。 “x7”部分可能太小,不足以使CBLAS的开销值得。进行函数调用的成本加上CBLAS函数为您提供的所有灵活性需要一段时间才能进行备份。每当您传递CblasNoTrans
之类的选项时,请记住其中有if()
来管理该选项。 cblas_dgemm
特别累积到C中,因此它必须读取前一个结果元素,应用乘法,然后在存储之前添加。这是一项额外的工作。
您可能想要尝试vDSP功能而不是CBLAS。 vDSP_mmul
稍微简单一些,不会累积到结果中。我在vDSP_*
小数据集(几千个元素)上运气不错。
也就是说,我对此的体验是天真的C实现在小数据集上通常非常快。避免函数调用是一个巨大的好处。说到这一点,请确保内联at()
来电。否则你在循环中浪费了很多时间。您可以通过使用指针添加来串行移动矩阵而不是乘法(通过[]
随机访问所需)来加速C实现。在矩阵这个小的,它可能或不值得;你需要描述一下。查看汇编器输出非常有启发性。
请记住,您绝对必须在设备上对此内容进行分析。模拟器中的性能无关紧要。这不仅仅是模拟器更快;它完全不同。在模拟器上速度快得多的东西在设备上会慢得多。