通过std :: vector的矩阵乘法比numpy慢10倍

问题描述 投票:2回答:1

尽管众所周知,使用嵌套的std::vector表示矩阵是a bad idea,但由于它具有灵活性并且许多现有函数都可以处理std::vector,因此现在就使用它。

我认为,在很小的情况下,速度差异可以忽略。但事实证明,vector<vector<double>>numpy.dot()慢[[10倍慢。

AB是大小为size x size的矩阵。假设平方矩阵只是为了简单起见。 (我们不打算将讨论限于平方矩阵的情况。)我们以确定性的方式初始化每个矩阵,最后计算C = A * B

我们将“计算时间”定义为仅计算C = A * B所经过的时间。换句话说,不包括各种开销。

Python3代码

import numpy as np import time import sys if (len(sys.argv) != 2): print("Pass `size` as an argument.", file = sys.stderr); sys.exit(1); size = int(sys.argv[1]); A = np.ndarray((size, size)); B = np.ndarray((size, size)); for i in range(size): for j in range(size): A[i][j] = i * 3.14 + j B[i][j] = i * 3.14 - j start = time.time() C = np.dot(A, B); print("{:.3e}".format(time.time() - start), file = sys.stderr);

C ++代码

using namespace std; #include <iostream> #include <vector> #include <chrono> int main(int argc, char **argv) { if (argc != 2) { cerr << "Pass `size` as an argument.\n"; return 1; } const unsigned size = atoi(argv[1]); vector<vector<double>> A(size, vector<double>(size)); vector<vector<double>> B(size, vector<double>(size)); for (int i = 0; i < size; ++i) { for (int j = 0; j < size; ++j) { A[i][j] = i * 3.14 + j; B[i][j] = i * 3.14 - j; } } auto start = chrono::system_clock::now(); vector<vector<double>> C(size, vector<double>(size, /* initial_value = */ 0)); for (int i = 0; i < size; ++i) { for (int j = 0; j < size; ++j) { for (int k = 0; k < size; ++k) { C[i][j] += A[i][k] * B[k][j]; } } } cerr << scientific; cerr.precision(3); cerr << chrono::duration<double>(chrono::system_clock::now() - start).count() << "\n"; }

C ++代码(多线程)

numpy.dot() is automatically calculated in parallel以来,我们还编写了C ++代码的多线程版本。

您可以从numpy.dot()中获取所有代码。

结果

[GitHub版本比C++(带有Python 3)版本慢10倍以上。

numpy

问题

有什么方法可以使C ++实现更快?


我尝试过的优化

  1. [matrix_size: 200x200 --------------- Time in seconds --------------- C++ (not multithreaded): 8.45e-03 C++ (1 thread): 8.66e-03 C++ (2 threads): 4.68e-03 C++ (3 threads): 3.14e-03 C++ (4 threads): 2.43e-03 Python 3: 4.07e-04 ----------------------------------------------- matrix_size: 400x400 --------------- Time in seconds --------------- C++ (not multithreaded): 7.011e-02 C++ (1 thread): 6.985e-02 C++ (2 threads): 3.647e-02 C++ (3 threads): 2.462e-02 C++ (4 threads): 1.915e-02 Python 3: 1.466e-03 ----------------------------------------------- ->至多快3.5倍(不是swap calculation order代码,而是C ++代码)
  2. 优化1加numpy->最多快4.5倍,

    但是只有在事先知道partial unroll时才能这样做否。如size中指出的,this comment是不需要知道。我们可以限制展开循环的循环变量的最大值,并使用普通循环处理其余元素。例如,请参见size

  3. 优化2,加上通过引入简单变量my implementation->最小化C[i][j]的调用,最多快5.2倍。实现为sum。此结果表明here毫无疑问地很慢。
  4. 优化3,加上g ++ std::vector::operator[]标志->最多快6.2倍(顺便说一下,我们当然使用-march=native。]
  5. 优化3,加上通过引入指向-O3元素的指针来减少对运算符[]的调用,因为A的元素在展开循环中被顺序访问。 ->最多比优化4快6.2倍,并且快一点。代码显示如下。
  6. g ++ A标志展开-funroll-loops循环->不变
  7. g ++ for->不变
  8. g ++ #pragma GCC unroll n标志打开链接时间优化->不变
  9. #pragma GCC unroll n->不变
  10. -flto->不变
  11. 长线性Block Algorithm而不是嵌套的transpose B to avoid cache miss,交换计算顺序,块算法和部分展开->最多快2.2倍] >>

  12. 优化1,加B->快4.7倍
  13. 优化3,加上PGO->与优化3相同>]
  14. 优化3,加上g ++特定的std::vector->与优化3相同

  15. 当前状态

    (最初)慢std::vector<std::vector>倍->(当前)慢PGO(profile-guided optimization)

同样,您可以获得__builtin_prefetch()上的所有代码。但是,让我们引用一些代码,所有这些代码都是从C ++代码的多线程版本调用的函数。

原代码

__builtin_prefetch()13.06

当前最佳代码

([2.10
这是上面优化5的实现。

GitHub

自我们首次发布此问题以来,我们已经尝试了许多优化。我们花了整整两天的时间来解决这个问题,终于到了我们不知道如何优化当前最佳代码的地步。我们怀疑像GitHub这样的更复杂的算法是否会做得更好,因为我们处理的情况并不大,并且void f(const vector<vector<double>> &A, const vector<vector<double>> &B, vector<vector<double>> &C, unsigned row_start, unsigned row_end) {
    const unsigned j_max = B[0].size();
    const unsigned k_max = B.size();
    for (int i = row_start; i < row_end; ++i) {
        for (int j = 0; j < j_max; ++j) {
            for (int k = 0; k < k_max; ++k) {
                C[i][j] += A[i][k] * B[k][j];
            }
        }
    }
}
上的每个操作都非常昂贵,因此,如我们所见,仅减少GitHub的调用就可以很好地改善性能。

但是,我们(希望)相信我们可以做得更好。

尽管众所周知,使用嵌套的std :: vector表示矩阵是一个坏主意,但由于它具有灵活性并且许多现有函数都可以处理std :: vector,因此现在就使用它。我以为,在...

performance matrix vector scientific-computing
1个回答
1
投票
矩阵乘法相对容易优化。但是,如果要达到不错的cpu利用率,它将变得很棘手,因为您需要对所使用的硬件有深入的了解。实现快速matmul内核的步骤如下:

    使用SIMD指令
© www.soinside.com 2019 - 2024. All rights reserved.