繁体   English   中英

数组中的矩阵子集运算

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM