【问题标题】:Justify Memory Access in a cycle在一个循环中证明内存访问
【发布时间】:2013-12-25 16:24:57
【问题描述】:

我有以下功能:

void ikj(float (*a)[N], float (*b)[N], float (*c)[N], int n) {

    int i, j, k;
    float r;

    papi_start();

    for (i = 0; i < n; i++) {
        for (k = 0; k < n; k++) {

            r = a[i][k];

            for (j = 0; j < n; j++)
                c[i][j] += r * b[k][j];

        }
    }

    papi_stop();

}

我正在使用PAPI 来计算在papi_start()papi_stop() 之间我有多少负载和存储,我得到的结果如下:

加载(使用PAPI_LD_INS):

32 26781
64 205053
128 1606077
256 12714815
512 101189551
1024 807406950
2048 6450848188

商店(使用PAPI_SR_INS):

32 8290
64 65698
128 524578
256 4194850
512 33555490
1024 268437701
2048 2147487778

其中第一个值是 N 的大小,第二个值是指令数。

我正在使用 O3 进行编译,我的缓存大小为 L1 = 32KB x 2(指令和数据,8 路)和 L2 = 1024KB(4 路)(2 核共享).. 我的 cpu 是Intel T3200 和 SSE3..

我知道 O3 优化了代码,因此它会在其他功能中使用预取,因为我正在加载连续地址并且我的缓存有 64 字节的行大小,所以我一次加载 16 个浮点数,但我的计算没有达不到这个值,谁能给我解释一下?

编辑:这是我的程序集文件,很抱歉将它们扔在这里,但我从未使用过程序集,我无法真正理解它:

http://dl.dropboxusercontent.com/u/878621/mmc.s http://dl.dropboxusercontent.com/u/878621/mmc_asm.s

谢谢!

【问题讨论】:

  • 如果您还没有告诉我们您的 n 是什么(除其他外),我们很难猜到。
  • 您是否查看了编译器输出以了解编译器分配给寄存器的内容?寄存器访问不算作内存访问。此外,编译器可能已经对您的代码进行了矢量化处理,因此它通过一次加载或存储执行多个操作。
  • 是的,我做到了,它是输出值的第一列,从 32 到 2048 .. @JerryCoffin
  • 另外,PAPI 是否计算实际的加载和存储指令,或 L1D 获取和写回的数量?如果是加载和存储指令,那么缓存大小和行大小无关紧要。
  • @gnometorule - 硬件预取是内部的,编译器可能会添加额外的软件预取,这可能是有用的,因为编译器刚刚分析了整个代码,比 CPU 更了解程序上下文运行。但是,两者都与此性能计数器结果无关

标签: c++ c memory assembly papi


【解决方案1】:

看看商店,你得到的数字非常接近N**3 / 4。显然,我们希望它是 O(N**3)。

这表明 4 个浮点写入被合并到 PAPI_SR_INS 正在测量的任何一个中。换一种方式看,您正在计算 16 字节写入的数量。

类似地,加载次数大致为3/4 N**3。主要项应该是来自最内层循环内的 b 和 c 的负载,每次迭代将是 2 次读取。老实说,我无法理解这一点。

如果您不确切知道要测量的内容,并且不将其与生成的代码相关联,则很难预测测量结果。

编辑:这些数字似乎与执行的加载和存储指令相关,但与 L1、L2 等事务或未命中的数量无关——因此不太可能与实际性能相关。花费的时间不是更值得担心的数字吗?鉴于现代 CPU 架构的复杂性,我相信任何一天的测量都胜过预测。

【讨论】:

  • 快速浏览一下汇编文件表明循环正在被矢量化以一次对四个元素进行操作。我猜 Jose 需要学习一点 x86 汇编。
  • @JoeZ:是的,这解释了商店。负载计数实际上没有意义(我也希望它是 1/2 * N^3)。
  • 好吧,a[i][k] 的外部循环负载会发生 N^2 次,并且取决于哪个循环矢量化,它可能需要 4 次负载。 4 * N^2 + N^3 / 2 开始接近。我承认我没有非常仔细地阅读程序集。
  • 对于 N=2048,至少外部循环应该是微不足道的。
  • 程序集中的内部循环 (.L10) 有两个 4-float 读取和一个 4-float 写入,正如您所期望的。 (而且非常简洁。)仍然对负载计数器实际计数的内容感到困惑。
猜你喜欢
  • 1970-01-01
  • 2011-02-23
  • 1970-01-01
  • 2020-10-11
  • 1970-01-01
  • 2015-03-19
  • 2013-07-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多