[英]Matrix Subset Operations in Array
我有一个矩阵乘法问题。 我们有一个可以具有可变大小的图像矩阵。 需要为每个可能的 nxn 计算 C = A*B。 C 将被添加到输出图像中,如图所示。 A矩阵的中心点位于下三角。 此外,B 与 A 对角对称放置。A 可以重叠,因此 B 也可以重叠。 可以在下面看到数字以获得更详细的理解:
蓝色 X 点代表 A 的所有可能的中点。算法应该只乘以 A 和 A 的对角镜像版本或称为 B。我用很多 for 循环完成了它。 我需要减少我使用的数量。 请问你能帮帮我吗?
什么样的算法可以用于这个问题? 我有一些令人困惑的地方。
你能用你的天才算法天赋帮助我吗? 或者你能告诉我专家吗?
原题如下:
谢谢。
更新:
#define SIZE_ARRAY 20
#define SIZE_WINDOW 5
#define WINDOW_OFFSET 2
#define INDEX_OFFSET 1
#define START_OFFSET_COLUMN 2
#define START_OFFSET_ROW 3
#define END_OFFSET_COLUMN 3
#define END_OFFSET_ROW 2
#define GET_LOWER_DIAGONAL_INDEX_MIN_ROW (START_OFFSET_ROW);
#define GET_LOWER_DIAGONAL_INDEX_MAX_ROW (SIZE_ARRAY - INDEX_OFFSET - END_OFFSET_ROW)
#define GET_LOWER_DIAGONAL_INDEX_MIN_COL (START_OFFSET_COLUMN);
#define GET_LOWER_DIAGONAL_INDEX_MAX_COL (SIZE_ARRAY - INDEX_OFFSET - END_OFFSET_COLUMN)
uint32_t lowerDiagonalIndexMinRow = GET_LOWER_DIAGONAL_INDEX_MIN_ROW;
uint32_t lowerDiagonalIndexMaxRow = GET_LOWER_DIAGONAL_INDEX_MAX_ROW;
uint32_t lowerDiagonalIndexMinCol = GET_LOWER_DIAGONAL_INDEX_MIN_COL;
uint32_t lowerDiagonalIndexMaxCol = GET_LOWER_DIAGONAL_INDEX_MAX_COL;
void parallelMultiplication_Stable_Master()
{
startTimeStamp = omp_get_wtime();
#pragma omp parallel for num_threads(8) private(outerIterRow, outerIterCol,rA,cA,rB,cB) shared(inputImage, outputImage)
for(outerIterRow = lowerDiagonalIndexMinRow; outerIterRow < lowerDiagonalIndexMaxRow; outerIterRow++)
{
for(outerIterCol = lowerDiagonalIndexMinCol; outerIterCol < lowerDiagonalIndexMaxCol; outerIterCol++)
{
if(outerIterCol + 1 < outerIterRow)
{
rA = outerIterRow - WINDOW_OFFSET;
cA = outerIterCol - WINDOW_OFFSET;
rB = outerIterCol - WINDOW_OFFSET;
cB = outerIterRow - WINDOW_OFFSET;
for(i= outerIterRow - WINDOW_OFFSET; i <= outerIterRow + WINDOW_OFFSET; i++)
{
for(j= outerIterCol - WINDOW_OFFSET; j <= outerIterCol + WINDOW_OFFSET; j++)
{
for(k=0; k < SIZE_WINDOW; k++)
{
#pragma omp critical
outputImage[i][j] += inputImage[rA][cA+k] * inputImage[rB+k][cB];
}
cB++;
rA++;
}
rB++;
cA++;
printf("Thread Number - %d",omp_get_thread_num());
}
}
}
}
stopTimeStamp = omp_get_wtime();
printArray(outputImage,"Output Image");
printConsoleNotification(100, startTimeStamp, stopTimeStamp);
}
如果我设置的线程数超过“1”,我会收到分段错误。 什么诀窍?
我不是在提供解决方案,而是提供一些可能有助于 OP 探索可能方法的想法。
您可以以类似于卷积运算的方式从原始矩阵的值直接评估生成的 C 矩阵的每个元素。
考虑下图(抱歉,如果它令人困惑):
您可以根据阴影区域中的值计算每个 C i, j的值,而不是为每个 A 子矩阵计算每个矩阵乘积。
请注意,C i, j仅依赖于行 i 的一个小子集,并且可以复制右上三角子矩阵(其中选择了 B 子矩阵)的元素,并且可以将其转置为更适合 chache 的住宿。
或者,可能值得探索一种方法,其中对于每个可能的 B i, j ,都评估 C 的所有对应元素。
编辑
请注意,您实际上可以通过对术语进行分组来节省大量计算(并且可能缓存未命中),例如,参见 A 中第 i 行的前两个元素:
这是我的看法。 我在 OP 显示任何代码之前写了这篇文章,所以我没有遵循他们的任何代码模式。
我从一个合适的图像结构开始,只是为了我自己的理智。
struct Image
{
float* values;
int rows, cols;
};
struct Image image_allocate(int rows, int cols)
{
struct Image rtrn;
rtrn.rows = rows;
rtrn.cols = cols;
rtrn.values = malloc(sizeof(float) * rows * cols);
return rtrn;
}
void image_fill(struct Image* img)
{
ptrdiff_t row, col;
for(row = 0; row < img->rows; ++row)
for(col = 0; col < img->cols; ++col)
img->values[row * img->cols + col] = rand() * (1.f / RAND_MAX);
}
void image_print(const struct Image* img)
{
ptrdiff_t row, col;
for(row = 0; row < img->rows; ++row) {
for(col = 0; col < img->cols; ++col)
printf("%.3f ", img->values[row * img->cols + col]);
putchar('\n');
}
putchar('\n');
}
5x5 矩阵乘法太小,无法合理分配给 BLAS。 所以我自己写了一个简单的版本,可以展开循环和/或内联。 这个例程可以使用一些微优化,但现在让我们保持简单。
/** out += left * right for 5x5 sub-matrices */
static void mat_mul_5x5(
float* restrict out, const float* left, const float* right, int cols)
{
ptrdiff_t row, col, inner;
float sum;
for(row = 0; row < 5; ++row) {
for(col = 0; col < 5; ++col) {
sum = out[row * cols + col];
for(inner = 0; inner < 5; ++inner)
sum += left[row * cols + inner] * right[inner * cols + row];
out[row * cols + col] = sum;
}
}
}
现在主要算法的单线程实现。 再一次,没什么特别的。 我们只是迭代下三角矩阵,不包括对角线。 我跟踪左上角而不是中心点。 使索引计算更简单一些。
void compute_ltr(struct Image* restrict out, const struct Image* in)
{
ptrdiff_t top, left, end;
/* if image is not quadratic, find quadratic subset */
end = out->rows < out->cols ? out->rows : out->cols;
assert(in->rows == out->rows && in->cols == out->cols);
memset(out->values, 0, sizeof(float) * out->rows * out->cols);
for(top = 1; top <= end - 5; ++top)
for(left = 0; left < top; ++left)
mat_mul_5x5(out->values + top * out->cols + left,
in->values + top * in->cols + left,
in->values + left * in->cols + top,
in->cols);
}
并行化有点棘手,因为我们必须确保线程在它们的输出矩阵中不重叠。 关键部分、原子或类似的东西会消耗太多的性能。
一个更简单的解决方案是跨步方法:如果我们始终将线程保持 5 行,它们就不会干扰。 因此,我们只需每五行计算一次,同步所有线程,然后计算下一组行,相隔五行,依此类推。
void compute_ltr_parallel(struct Image* restrict out, const struct Image* in)
{
/* if image is not quadratic, find quadratic subset */
const ptrdiff_t end = out->rows < out->cols ? out->rows : out->cols;
assert(in->rows == out->rows && in->cols == out->cols);
memset(out->values, 0, sizeof(float) * out->rows * out->cols);
/*
* Keep the parallel section open for multiple loops to reduce
* overhead
*/
# pragma omp parallel
{
ptrdiff_t top, left, offset;
for(offset = 0; offset < 5; ++offset) {
/* Use dynamic scheduling because the work per row varies */
# pragma omp for schedule(dynamic)
for(top = 1 + offset; top <= end - 5; top += 5)
for(left = 0; left < top; ++left)
mat_mul_5x5(out->values + top * out->cols + left,
in->values + top * in->cols + left,
in->values + left * in->cols + top,
in->cols);
}
}
}
在我的 8 核/16 线程 CPU 上,我对 1000x1000 图像进行 1000 次迭代的基准显示串行版本为 7 秒,并行版本为 1.2 秒。
编辑完整性:这是基准测试的包含和主要内容。
#include <assert.h>
#include <stddef.h>
/* using ptrdiff_t */
#include <stdlib.h>
/* using malloc */
#include <stdio.h>
/* using printf */
#include <string.h>
/* using memset */
/* Insert code from above here */
int main()
{
int rows = 1000, cols = 1000, rep = 1000;
struct Image in, out;
in = image_allocate(rows, cols);
out = image_allocate(rows, cols);
image_fill(&in);
# if 1
do
compute_ltr_parallel(&out, &in);
while(--rep);
# else
do
compute_ltr(&out, &in);
while(--rep);
# endif
}
使用gcc -O3 -fopenmp
编译。
关于评论,以及您使用 OpenMP 的方式:不要使用不必要的指令使事情过于复杂。 OpenMP 可以自己计算出有多少线程可用。 私有变量可以很容易地在并行部分中声明(通常)。
如果您想要特定数量的线程,只需调用适当的环境变量,例如在 Linux 上调用OMP_NUM_THREADS=8./executable
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.