繁体   English   中英

高速缓存未中对矩阵乘法时间的影响

[英]Effect of cache misses on time of matrix multiplication

我正在尝试从较小的矩阵大小开始逐步进行矩阵乘法,以期观察一旦矩阵不再适合高速缓存,时间将突然改变。 但是令我失望的是,我总是得到一个看似相同的函数的非常平滑的图形。 我尝试从小到4x4的矩阵开始,并逐渐3400x3400其增加到3400x3400 (等于11MB整数值),但是时间函数没有变化。 可能是我在这里缺少一些关键点。 任何帮助将不胜感激。

这是我的C ++代码:

long long clock_time() {
    struct timespec tp;
    clock_gettime(CLOCK_REALTIME, &tp);
    return (long long)(tp.tv_nsec + (long long)tp.tv_sec * 1000000000ll);
}

int main()
{
    for(int matrix_size = 100; matrix_size < 3500; matrix_size += 100)
    {
        int *A = new int[matrix_size*matrix_size];
        int *B = new int[matrix_size*matrix_size];
        int *C = new int[matrix_size*matrix_size];

        long long start = clock_time();

        for(int i = 0; i < matrix_size; ++i)
            for(int j = 0; j < matrix_size; ++j)
                for(int k = 0; k < matrix_size; ++k)
                {
                    C[i + j*matrix_size] = A[i + k*matrix_size] * B[k + j*matrix_size];
                }

        long long end = clock_time();
        long long totalTime = (end - start);

        std::cout << matrix_size << "," << totalTime << std::endl;

        delete[] A;
        delete[] B;
        delete[] C;
    }

    std::cout << "done" ;


    return 0;
}

这是我获取的数据的示例图: 在此处输入图片说明

有关详细数据, 访问https://docs.google.com/spreadsheets/d/1Xtri8w2sLZLQE0566Raducg7G2L4GLqNYIvP4nrp2t8/edit?usp=sharing

更新 :根据浙源和弗兰克的建议,我没有用值i+j初始化我的矩阵并将时间除以2*N^3

for(int i = 0; i < matrix_size; i++)
{
    for(int j = 0; j < matrix_size; j++)
    {
        A[i + j * matrix_size] = i+j;
        B[i + j * matrix_size] = i+j;
        B[i + j * matrix_size] = i+j;
    }

}

结果如下:

在此处输入图片说明

更新2:交换ij循环后: 在此处输入图片说明

好吧,您一定会观察到定时的三次曲线。 假设您使用两个N * N平方矩阵,则矩阵乘法的复杂度或浮点运算(FLOP)的数量为2 * N ^ 3) 随着N增加,FLOP的增加将主导时间的增加,您将不会轻易观察到延迟问题。

如果要调查延迟的纯粹影响,则应按FLOP量“标准化”时序:

measured time / (2 * N ^ 3)

或者:

(2 * N ^ 3) / measured time

前者是每FLOP平均花费的时间,而后者给你每秒翻牌 ,通常被称为FLOPS文学。 FLOP是性能的主要指标(至少对于科学计算而言)。 可以预期的是,随着N增加,前一个指标将出现上行跳跃(增加的延迟),而后一个指标将出现下行跳跃(降低的性能)。

抱歉,我没有编写C ++,因此无法修改您的代码(但这很简单,因为您只需要进行2 * N ^ 3除法即可)。 我曾经用C代码做过相同的实验,这是我在Intel Core 2 Duo上的结果。 注意,我报告的是MFLOP,即10 ^ 6 FLOP。 该图实际上是在R软件中生成的。

在此处输入图片说明


我的上述观察确实假设您正确地理解了所有其他内容。 但是实际上,事实并非如此。

首先 ,矩阵乘法是:

C[i + j*matrix_size] += A[i + k*matrix_size] * B[k + j*matrix_size];

注意+=而不是=

其次 ,您的循环嵌套设计不当。 您正在执行矩阵乘法C = A * B ,其中所有矩阵均以列主顺序存储,因此您应提防循环嵌套顺序,以确保最内层循环始终具有stride-1访问权限。 众所周知,在这种情况下, jki循环嵌套是最佳的。 因此,请考虑以下事项:

for(int j = 0; j < matrix_size; ++j)
    for(int k = 0; k < matrix_size; ++k)
        for(int i = 0; i < matrix_size; ++i)
        {
            C[i + j*matrix_size] += A[i + k*matrix_size] * B[k + j*matrix_size];
        }

第三 ,从矩阵大小100 * 100 ,该大小已经在L1缓存之外,大部分为64KB。 我建议您从N = 24开始。 一些文献表明, N = 60大约是此类缓存的边界值。

第四 ,您需要多次重复乘法以消除测量偏差。 目前,对于每个试验N (或代码中的matrix_size ),您只需进行一次乘法并测量时间。 这是不准确的。 对于小N您会得到虚假的计时。 重复(1000 / N + 1) ^ 3次如何?

  • N很小时,您会重复很多次;
  • 随着N越来越接近1000 ,您重复的次数越少;
  • N > 1000 ,您基本上只做一次乘法。

当然,不要忘记您需要将测量的时间除以重复的时间。


当然,还有其他一些地方可以优化代码,例如在地址计算中使用常量寄存器和消除整数乘法,但是它们的重要性不大,因此未涉及。 数组的初始化也被跳过了,因为Frank的答案已经提到了它。

您没有在数组内部初始化数据,因此您的系统可能分配了一页写时复制内存,并将所有数组映射到该页面。

简而言之,A和B始终占用总共4096字节的硬件内存。 而且由于缓存是基于硬件地址(而不是虚拟地址)完成的,因此您实际上始终处于缓存中。

使用随机数据初始化A和B将按照您的要求强制分配实际的硬件内存。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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