尽管众所周知,使用嵌套的std::vector
表示矩阵是a bad idea,但由于它具有灵活性并且许多现有函数都可以处理std::vector
,因此现在就使用它。
我认为,在很小的情况下,速度差异可以忽略。但事实证明,vector<vector<double>>
比numpy.dot()
慢[[10倍慢。
A
和B
是大小为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 ++实现更快?问题
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 ++代码)numpy
->最多快4.5倍,但是只有在事先知道partial unroll时才能这样做否。如size
中指出的,this comment是不需要知道。我们可以限制展开循环的循环变量的最大值,并使用普通循环处理其余元素。例如,请参见size
。
C[i][j]
的调用,最多快5.2倍。实现为sum
。此结果表明here毫无疑问地很慢。std::vector::operator[]
标志->最多快6.2倍(顺便说一下,我们当然使用-march=native
。]-O3
元素的指针来减少对运算符[]
的调用,因为A
的元素在展开循环中被顺序访问。 ->最多比优化4快6.2倍,并且快一点。代码显示如下。A
标志展开-funroll-loops
循环->不变for
->不变#pragma GCC unroll n
标志打开链接时间优化->不变#pragma GCC unroll n
->不变-flto
->不变B
to avoid cache miss,交换计算顺序,块算法和部分展开->最多快2.2倍] >>B
->快4.7倍std::vector
->与优化3相同std::vector<std::vector>
倍->(当前)慢PGO(profile-guided optimization)倍同样,您可以获得__builtin_prefetch()
上的所有代码。但是,让我们引用一些代码,所有这些代码都是从C ++代码的多线程版本调用的函数。
原代码
(__builtin_prefetch()
)13.06
这是上面优化5的实现。当前最佳代码
([2.10
)
自我们首次发布此问题以来,我们已经尝试了许多优化。我们花了整整两天的时间来解决这个问题,终于到了我们不知道如何优化当前最佳代码的地步。我们怀疑像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,因此现在就使用它。我以为,在...