矩阵乘法有两种不同的方式(比较时间)

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

我有一个任务 - 比较2个矩阵乘法 - 默认方式,和第二个矩阵换位后的乘法,我们应该指出哪个方法更快的差异。我在下面写了这样的东西,但timetime2几乎相等。在一种情况下,第一种方法更快,我使用相同大小的矩阵运行乘法,而在另一种情况下,第二种方法更快。做错了吗?我应该在代码中更改一些内容吗?

clock_t start = clock();

    int sum;
    for(int i=0; i<size; ++i) {
        for(int j=0; j<size; ++j) {
            sum = 0;
            for(int k=0; k<size; ++k) {
                sum = sum + (m1[i][k] * m2[k][j]);
            }
            score[i][j] = sum;
        }
    }

    clock_t end = clock();
    double time = (end-start)/(double)CLOCKS_PER_SEC;

    for(int i=0; i<size; ++i) {
        for(int j=0; j<size; ++j) {
            int temp = m2[i][j];
            m2[i][j] = m2[j][i];
            m2[j][i] = temp;
        }
    }

    clock_t start2 = clock();

    int sum2;
    for(int i=0; i<size; ++i) {
        for(int j=0; j<size; ++j) {
            sum2 = 0;
            for(int k=0; k<size; ++k) {
                sum2 = sum2 + (m1[k][i] * m2[k][j]);
            }
            score[i][j] = sum2;
        }
    }

    clock_t end2 = clock();
    double time2 = (end2-start2)/(double)CLOCKS_PER_SEC;
c matrix
1个回答
0
投票

您的代码和/或您的理解存在多个严重问题。让我试着解释一下。

矩阵乘法受到处理器加载并将值存储到内存的速率的瓶颈。大多数当前架构使用缓存来帮助解决这个问题。数据从内存移动到缓存,从缓存移动到内存中。为了最大限度地利用缓存,您需要确保使用该块中的所有数据。为此,您需要确保在内存中按顺序访问数据。

在C中,多维数组在row-major order中指定。这意味着最右边的索引在内存中是连续的;即a[i][k]a[i][k+1]在记忆中是连续的。

根据体系结构,处理器等待(并且什么也不做)数据从RAM移动到缓存(反之亦然)的时间可能包括也可能不包括在CPU时间中(例如clock()测量) ,虽然分辨率非常差)。对于这种测量(“微基准”),测量和报告使用的CPU和实际(或挂钟)时间要好得多;特别是如果微基准测试在不同的机器上运行,以更好地了解变化的实际影响。

会有很多变化,所以通常情况下,你会测量几百次重复所花费的时间(每次重复可能进行多次操作;足以轻松测量),存储每次重复的持续时间,并报告其中位数。为什么中位数,而不是最小值,最大值,平均值?因为总会偶尔出现故障(由于外部事件或其他因素导致的不合理测量),这通常产生比正常情况高得多的值;除非删除,否则这会使最大程度无趣,并使平均值(平均值)偏斜。最低限度通常是过于乐观的情况,其中一切恰好都是完美的;这在实践中很少发生,所以只是好奇心,而不是实际的兴趣。另一方面,中位时间为您提供了一个实际测量:您可以预期测试用例的所有运行中的50%不超过测量的中值时间。

在POSIXy系统(Linux,Mac,BSD)上,您应该使用clock_gettime()来测量时间。 struct timespec格式具有纳秒精度(1秒= 1,000,000,000纳秒),但分辨率可能更小(即,每当它们改变时,时钟改变超过1纳秒)。我个人用

#define _POSIX_C_SOURCE 200809L
#include <time.h>

static struct timespec  cpu_start, wall_start;
double                  cpu_seconds, wall_seconds;

void timing_start(void)
{
    clock_gettime(CLOCK_REALTIME, &wall_start);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &cpu_start);
}

void timing_stop(void)
{
    struct timespec  cpu_end, wall_end;
    clock_gettime(CLOCK_REALTIME, &wall_end);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &cpu_end);

    wall_seconds = (double)(wall_end.tv_sec - wall_start.tv_sec)
                 + (double)(wall_end.tv_nsec - wall_start.tv_nsec) / 1000000000.0;
    cpu_seconds = (double)(cpu_end.tv_sec - cpu_start.tv_sec)
                + (double)(cpu_end.tv_nsec - cpu_start.tv_nsec) / 1000000000.0;
}

你在手术前叫timing_start(),手术后叫timing_stop();然后,cpu_seconds包含所花费的CPU时间量和wall_seconds所用的实际挂钟时间(以秒为单位,使用例如%.9f来打印所有有意义的小数)。

以上内容不适用于Windows,因为Microsoft不希望您的C代码可移植到其他系统。它更喜欢开发自己的“标准”。 (那些C11“安全”_s() I / O函数变体是一个愚蠢的假,与例如POSIX getline()相比,或者除了Windows之外的所有系统上的宽字符支持状态。)

矩阵乘法是

c[r][c] = a[r][0] * b[0][c]
        + a[r][1] * b[1][c]
        :         :
        + a[r][L] * b[L][c]

其中aL+1列,bL+1行。

为了使求和循环使用连续元素,我们需要转置b。如果B[c][r] = b[r][c],那么

c[r][c] = a[r][0] * B[c][0]
        + a[r][1] * B[c][1]
        :         :
        + a[r][L] * B[c][L]

请注意,aB在内存中是连续的,但是可以分开(可能彼此“远”),以便处理器在这种情况下有效地利用缓存。

OP使用一个简单的循环,类似于下面的伪代码,来转置b

For r in rows:
    For c in columns:
        temporary = b[r][c]
        b[r][c] = b[c][r]
        b[c][r] = temporary
    End For
End For

上面的问题是每个元素都参与交换两次。例如,如果b有10行和列,r = 3, c = 5交换b[3][5]b[5][3],但后来,r = 5, c = 3再次交换b[5][3]b[3][5]!基本上,双循环最终将矩阵恢复到原始顺序;它不会进行转置。

考虑以下条目和实际转置:

b[0][0] b[0][1] b[0][2]      b[0][0] b[1][0] b[2][0]
b[1][0] b[1][1] b[1][2]  ⇔   b[0][1] b[1][1] b[2][1]
b[2][0] b[2][1] b[2][2]      b[0][2] b[1][2] b[2][2]

对角线条目未交换。您只需要在上三角形部分(c > r)或下三角形部分(r > c)中进行交换,以交换所有条目,因为每个交换都会将一个条目从上三角形交换到下三角形,反之亦然。

所以,回顾一下:

做错了吗?

是。你的转置什么都不做。你还没有理解为什么人们想要转置第二个矩阵。您的时间测量依赖于低精度CPU时间,这可能无法反映在RAM和CPU缓存之间移动数据所花费的时间。在第二个测试用例中,m2“转置”(除非它不是,因为你交换每个元素对两次,返回它们的方式),你的最内层循环超过最左边的数组索引,这意味着它计算错误的结果。 (此外,因为最内层循环的连续迭代在内存中访问彼此远离的项目,所以它是反优化的:它使用速度方面最差的模式。)

以上所有可能听起来都很苛刻,但根本不是这样。我不认识你,我也不想评价你;我只是在你当前的理解中指出了这个特定答案中的错误,并且只是希望它能够帮助你和在类似情况下遇到这个问题的其他人学习。

© www.soinside.com 2019 - 2024. All rights reserved.