我写了一个简单的矩阵乘法程序来演示通过perf实现缓存和性能测量的效果。用于比较相对效率的两个函数是sqmat_mult
和sqmat_mult_efficient
:
void sqmat_mult(int x, const int a[x][x], const int b[x][x], int m[x][x])
{
for (int i = 0; i < x; i++) {
for (int j = 0; j < x; j++) {
int sum = 0;
for (int k = 0; k < x; k++) {
sum += a[i][k] * b[k][j]; // access of b array is non sequential
}
m[i][j] = sum;
}
}
}
static void sqmat_transpose(int x, int a[x][x])
{
for (int i = 0; i < x; i++) {
for (int j = i+1; j < x; j++) {
int temp = a[i][j];
a[i][j] = a[j][i];
a[j][i] = temp;
}
}
}
void sqmat_mult_efficient(int x, const int a[x][x], int b[x][x], int m[x][x])
{
sqmat_transpose(x, b);
for (int i = 0; i < x; i++) {
for (int j = 0; j < x; j++) {
int sum = 0;
for (int k = 0; k < x; k++) {
sum += a[i][k] * b[j][k]; // access of b array is sequential
}
m[i][j] = sum;
}
}
sqmat_transpose(x, b);
}
然而,当我基于这两个函数运行perf stat
时,我对&#34; page-faults&#34;感到困惑。统计。 sqmat_mult
的输出为:
428374.070363 task-clock (msec) # 1.000 CPUs utilized
128 context-switches # 0.000 K/sec
127 cpu-migrations # 0.000 K/sec
12,334 page-faults # 0.029 K/sec
8,63,12,33,75,858 cycles # 2.015 GHz (83.33%)
2,89,73,31,370 stalled-cycles-frontend # 0.34% frontend cycles idle (83.33%)
7,90,36,10,13,864 stalled-cycles-backend # 91.57% backend cycles idle (33.34%)
2,24,41,64,76,049 instructions # 0.26 insn per cycle
# 3.52 stalled cycles per insn (50.01%)
8,84,30,79,219 branches # 20.643 M/sec (66.67%)
1,04,85,342 branch-misses # 0.12% of all branches (83.34%)
428.396804101 seconds time elapsed
sqmat_mult_efficient
的输出为:
8534.611199 task-clock (msec) # 1.000 CPUs utilized
39876.726670 task-clock (msec) # 1.000 CPUs utilized
11 context-switches # 0.000 K/sec
11 cpu-migrations # 0.000 K/sec
12,334 page-faults # 0.309 K/sec
1,19,87,36,75,397 cycles # 3.006 GHz (83.33%)
49,19,07,231 stalled-cycles-frontend # 0.41% frontend cycles idle (83.33%)
50,40,53,90,525 stalled-cycles-backend # 42.05% backend cycles idle (33.35%)
2,24,10,77,52,356 instructions # 1.87 insn per cycle
# 0.22 stalled cycles per insn (50.01%)
8,75,27,87,494 branches # 219.496 M/sec (66.68%)
50,48,390 branch-misses # 0.06% of all branches (83.33%)
39.879816492 seconds time elapsed
使用基于转置的乘法,&#34;后端循环空闲&#34;如预期的那样大大减少了。这是由于获取相干内存的等待时间较短。让我感到困惑的是&#34;页面错误&#34;是一样的。起初我认为这可能是一个上下文切换问题,因此我使用调度SCHED_FIFO
和优先级1
运行程序。上下文切换的数量从大约800减少到11,但是&#34;页面错误&#34;与以前完全一样。
我使用的矩阵的维度为2048 x 2048
,因此整个数组不适合4K页面。它也不适合我的整个缓存(在我的情况下为4MiB),因为三个矩阵(3 * 2048 * 2048 * sizeof(int)== 48MiB)的大小远远超过了总的avaibale缓存。假设&#34;页面错误&#34;这里指的是TLB未命中或缓存未命中,我不知道这两种算法如何具有完全相同的数字。据我所知,我看到的很多效率提升都来自L1缓存命中,但这并不意味着RAM缓存传输没有任何作用。
我还做了另一个实验,看看&#34;页面错误&#34;使用数组大小进行缩放 - 正如预期的那样。
所以我的问题是 -
答案 0 :(得分:1)
我是否正确地假设&#34;页面错误&#34;是指TLB未命中还是缓存未命中?
没有。当虚拟内存未映射到物理内存时,会发生页面错误。这有两个典型的 1 原因:
现在页面错误可能只会在高速缓存未命中和TLB未命中之后发生 2 ,因为TLB仅存储实际映射,而高速缓存仅包含也在主存储器中的数据
以下是发生了什么的简化图表:
图片来自维基共享资源的Aravind krishna CC BY-SA 4.0
为什么&#34;页面错误&#34;这两种算法完全相同吗?
您的所有数据都适合物理内存,因此您的情况下无法进行交换。这将为您留下2),每个虚拟页面都会发生一次。您使用48 MiB内存,使用12288页4 kiB大小。这两种算法都是一样的,几乎就是你测量的。
1 其他原因可能是内存映射IO或NUMA平衡。
2 我想你可以构建一个架构,其中TLB包含未映射的虚拟地址信息,或者内存不包括缓存,但我怀疑这样的架构是否存在。