C++ - 高效计算矢量矩阵乘积
C++ - Efficiently computing a vector-matrix product
我需要尽可能高效地计算产品向量矩阵。具体来说,给定一个向量s
和一个矩阵A
,我需要计算s * A
。我有一个类Vector
,它包装了一个std::vector
和一个类Matrix
,它也包装了一个std::vector
(为了提高效率)。
天真的方法(我目前正在使用的方法)是拥有类似的东西
Vector<T> timesMatrix(Matrix<T>& matrix)
{
Vector<unsigned int> result(matrix.columns());
// constructor that does a resize on the underlying std::vector
for(unsigned int i = 0 ; i < vector.size() ; ++i)
{
for(unsigned int j = 0 ; j < matrix.columns() ; ++j)
{
result[j] += (vector[i] * matrix.getElementAt(i, j));
// getElementAt accesses the appropriate entry
// of the underlying std::vector
}
}
return result;
}
它工作正常,需要近 12000 微秒。请注意,向量s
有 499 个元素,而A
是499 x 15500
。
下一步是尝试并行化计算:如果我有N
线程,那么我可以为每个线程提供向量s
的一部分和矩阵A
的"对应"行。每个线程将计算一个 499 大小的Vector
,最终结果将是它们的条目总和。
首先,在类Matrix
中,我添加了一个方法,用于从Matrix
中提取一些行并构建一个较小的行:
Matrix<T> extractSomeRows(unsigned int start, unsigned int end)
{
unsigned int rowsToExtract = end - start + 1;
std::vector<T> tmp;
tmp.reserve(rowsToExtract * numColumns);
for(unsigned int i = start * numColumns ; i < (end+1) * numColumns ; ++i)
{
tmp.push_back(matrix[i]);
}
return Matrix<T>(rowsToExtract, numColumns, tmp);
}
然后我定义了一个线程例程
void timesMatrixThreadRoutine
(Matrix<T>& matrix, unsigned int start, unsigned int end, Vector<T>& newRow)
{
// newRow is supposed to contain the partial result
// computed by a thread
newRow.resize(matrix.columns());
for(unsigned int i = start ; i < end + 1 ; ++i)
{
for(unsigned int j = 0 ; j < matrix.columns() ; ++j)
{
newRow[j] += vector[i] * matrix.getElementAt(i - start, j);
}
}
}
最后,我修改了上面显示的timesMatrix
方法的代码:
Vector<T> timesMatrix(Matrix<T>& matrix)
{
static const unsigned int NUM_THREADS = 4;
unsigned int matRows = matrix.rows();
unsigned int matColumns = matrix.columns();
unsigned int rowsEachThread = vector.size()/NUM_THREADS;
std::thread threads[NUM_THREADS];
Vector<T> tmp[NUM_THREADS];
unsigned int start, end;
// all but the last thread
for(unsigned int i = 0 ; i < NUM_THREADS - 1 ; ++i)
{
start = i*rowsEachThread;
end = (i+1)*rowsEachThread - 1;
threads[i] = std::thread(&Vector<T>::timesMatrixThreadRoutine, this,
matrix.extractSomeRows(start, end), start, end, std::ref(tmp[i]));
}
// last thread
start = (NUM_THREADS-1)*rowsEachThread;
end = matRows - 1;
threads[NUM_THREADS - 1] = std::thread(&Vector<T>::timesMatrixThreadRoutine, this,
matrix.extractSomeRows(start, end), start, end, std::ref(tmp[NUM_THREADS-1]));
for(unsigned int i = 0 ; i < NUM_THREADS ; ++i)
{
threads[i].join();
}
Vector<unsigned int> result(matColumns);
for(unsigned int i = 0 ; i < NUM_THREADS ; ++i)
{
result = result + tmp[i]; // the operator+ is overloaded
}
return result;
}
它仍然有效,但现在需要近 30000 微秒,几乎是以前的三倍。
我做错了什么吗?你认为有更好的方法吗?
编辑 - 使用"轻量级"VirtualMatrix
按照 Ilya Ovodov 的建议,我定义了一个包装T* matrixData
的类VirtualMatrix
,它在构造函数中初始化
VirtualMatrix(Matrix<T>& m)
{
numRows = m.rows();
numColumns = m.columns();
matrixData = m.pointerToData();
// pointerToData() returns underlyingVector.data();
}
然后有一个方法可以检索矩阵的特定条目:
inline T getElementAt(unsigned int row, unsigned int column)
{
return *(matrixData + row*numColumns + column);
}
现在执行时间更好(大约 8000 微秒),但也许需要做出一些改进。特别是线程例程现在是
void timesMatrixThreadRoutine
(VirtualMatrix<T>& matrix, unsigned int startRow, unsigned int endRow, Vector<T>& newRow)
{
unsigned int matColumns = matrix.columns();
newRow.resize(matColumns);
for(unsigned int i = startRow ; i < endRow + 1 ; ++i)
{
for(unsigned int j = 0 ; j < matColumns ; ++j)
{
newRow[j] += (vector[i] * matrix.getElementAt(i, j));
}
}
}
真正慢的部分是嵌套for
循环的部分。如果我删除它,结果显然是错误的,但在不到 500 微秒的时间内"计算"。也就是说,现在传递参数几乎不需要时间,而沉重的部分实际上是计算。
据你说,有没有办法让它更快?
实际上,您为extractSomeRows中的每个线程制作了矩阵的部分副本。这需要很多时间。重新设计它,使"某些行"成为指向位于原始矩阵中的数据的虚拟矩阵。
通过更明确地表示要乘以 4 来对架构使用矢量化汇编指令,即对于 x86-64 SSE2+ 和可能的 ARM NEON。
C++编译器通常可以将循环展开为矢量化代码,前提是在条件元素中显式执行操作:
C/C++中简单快速的矩阵向量乘法
还可以选择使用专门为矩阵乘法制作的库。对于较大的矩阵,使用基于快速傅立叶变换的特殊实现、斯特拉森算法等替代算法可能更有效。事实上,最好的办法是使用这样的 C 库,然后将其包装在看起来类似于 C++ 向量的接口中。
- 为什么"do while"循环不断退出,即使条件计算结果为 false?
- 递归函数计算序列中的平方和(并输出过程)
- C++中高效的大型稀疏块压缩线性方程
- (C++)分析树以计算返回错误值的简单算术表达式
- 我的字符计数代码计算错误.为什么
- 在计算中使用二的幂有多有利可图
- 如何实现高效的算法来计算大型数据集的多个不同值?
- 在手臂氖中高效计算两个不同的数字
- 使用元编程进行高效的索引计算
- 向量的高效组合最小值和平均值计算
- 高效计算阶乘
- 高效计算两个向量公共元素的索引
- 在unix/win32上高效计算用于日志记录的日期/时间戳
- 为什么低效的阶乘计算…高效(快速)
- C++ - 高效计算矢量矩阵乘积
- OpenGL:高效计算变换矩阵
- 高效频率计算的数据结构决策.
- 在Visual c++中开发一个用于高效数值计算的静态库
- c++中小数据集的高效中值计算
- 处理大数据网络文件的高效算法,用于计算n个最近节点