我正在使用OProfile在树莓派 3B+ 上分析以下函数。(我在树莓派上使用 gcc 10.2 版(不进行交叉编译)和编译器的以下标志:-O1 -mfpu-neon -mneon-for-64bits
. 最后包含生成的汇编代码。)
void do_stuff_u32(const uint32_t* a, const uint32_t* b, uint32_t* c, size_t array_size)
{
for (int i = 0; i < array_size; i++)
{
uint32_t tmp1 = b[i];
uint32_t tmp2 = a[i];
c[i] = tmp1 * tmp2;
}
}
我正在查看L1D_CACHE_REFILL
这PREFETCH_LINEFILL
两个 cpu 事件。查看文档,PREFETCH_LINEFILL
计算由于预取而L1D_CACHE_REFILL
导致的缓存行填充次数,并计算由于缓存未命中而导致的缓存行重新填充次数。对于上述循环,我得到了以下结果:
数组大小 | array_size / L1D_CACHE_REFILL | array_size / PREFETCH_LINEFILL |
---|---|---|
16777216 | 18.24 | 8.366 |
我想上面的循环是内存绑定的,这在某种程度上由值 8.366 确认:每个循环实例需要 3 x uint32_t
,即 12B。8.366 个循环实例需要约 100B 的内存数据。但是预取器每 8.366 个循环实例只能将 1 个缓存行填充到 L1,根据 Cortex-A53 的手册,它有 64B。因此,其余的缓存访问将导致缓存未命中,即 18.24。如果将这两个数字结合起来,您将得到 ~5.7,这意味着每 5.7 个循环实例从预取或缓存未命中重新填充中填充 1 个缓存行。而 5.7 循环实例需要 5.7 x 3 x 4 = 68B,或多或少与缓存行大小一致。
然后我在循环中添加了更多东西,然后变成以下内容:
void do_more_stuff_u32(const uint32_t* a, const uint32_t* b, uint32_t* c, size_t array_size)
{
for (int i = 0; i < array_size; i++)
{
uint32_t tmp1 = b[i];
uint32_t tmp2 = a[i];
tmp1 = tmp1 * 17;
tmp1 = tmp1 + 59;
tmp1 = tmp1 /2;
tmp2 = tmp2 *27;
tmp2 = tmp2 + 41;
tmp2 = tmp2 /11;
tmp2 = tmp2 + tmp2;
c[i] = tmp1 * tmp2;
}
}
而 cpu 事件的分析数据是我不明白的:
数组大小 | array_size / L1D_CACHE_REFILL | array_size / PREFETCH_LINEFILL |
---|---|---|
16777216 | 11.24 | 7.034 |
由于循环需要更长的时间来执行,预取器现在只需要 7.034 个循环实例来填充 1 个缓存行。但我不明白的是为什么缓存丢失也更频繁地发生,反映在数字 11.24 上,与之前的 18.24 相比?有人可以阐明如何将所有这些放在一起吗?
更新以包含生成的程序集
循环1:
cbz x3, .L178
lsl x6, x3, 2
mov x3, 0
.L180:
ldr w4, [x1, x3]
ldr w5, [x0, x3]
mul w4, w4, w5
lsl w4, w4, 1
str w4, [x2, x3]
add x3, x3, 4
cmp x3, x6
bne .L180
.L178:
循环2:
cbz x3, .L178
lsl x6, x3, 2
mov x5, 0
mov w8, 27
mov w7, 35747
movk w7, 0xba2e, lsl 16
.L180:
ldr w3, [x1, x5]
ldr w4, [x0, x5]
add w3, w3, w3, lsl 4
add w3, w3, 59
mul w4, w4, w8
add w4, w4, 41
lsr w3, w3, 1
umull x4, w4, w7
lsr x4, x4, 35
mul w3, w3, w4
lsl w3, w3, 1
str w3, [x2, x5]
add x5, x5, 4
cmp x5, x6
bne .L180
.L178: