测量TLB的容量和访问TLB的开销

2023-11-21

TLB的全称是translation lookaside buffer,不懂这个概念的人就不要看下去了,本文只讲讲怎么测量TLB的容量和访问TLB的开销。

先来讲基本思路。假设我们有一个横跨多页的大数组arr,页面大小是4096个字节,int大小为4个字节,那么每页就有1024个数组元素。第1次访问arr[0],第1次访问arr[1024],第1次访问arr[2048] …… 于是我们就依次访问了第1页,第2页,第3页……

开一个横跨多页的数组,依次修改各个页里面的数组元素,访问的页面数量逐渐变多,直到超过了TLB的容量,访问数组元素就会变慢,这就是基本思路。

几个问题:

  1. 如何知道操作系统的页面大小?
  2. 对于有多个CPU的计算机,为了得到更准确的数据,如何做到程序只在一个CPU上运行?

Talk is cheap, show you my code:

#define _GNU_SOURCE
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include <sched.h>

#define MAX_NUM_PAGES 256
#define TRIALS 10000000

void trials(int jump, int num_pages, int arr[])
{
    struct timeval start, end;

    gettimeofday(&start, NULL);
    for (int i = 0; i < TRIALS; i++)
    {
        for (int i = 0; i < num_pages * jump; i += jump)
        {
            arr[i] += 1;
        }
    }

    gettimeofday(&end, NULL);
    uint64_t trial_time = (end.tv_sec - start.tv_sec) * 1000000 
                          + end.tv_usec - start.tv_usec;

    double one_trial_time = trial_time 
                           / (double)(num_pages * TRIALS);
    printf("pages:%d, average time:%f\n", 
            num_pages, one_trial_time);
}

int main(int argc, char *argv[])
{
    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(0, &cpuset);
    if (sched_setaffinity(0, sizeof(cpu_set_t), &cpuset))
    {
        fprintf(stderr, "Error setting cpu affinity\n");
        exit(EXIT_FAILURE);
    }

    int page_size = getpagesize();
    int jump = page_size / sizeof(int);
    int *arr = (int *)calloc(MAX_NUM_PAGES * jump, sizeof(int));
    for (int pages = 1; pages < MAX_NUM_PAGES; pages++)
    {
        trials(jump, pages, arr);
    }
}

不开任何优化,使用GCC编译运行,然后查看结果。在我的计算机上,访问页面数量在[1,10]这个区间时,访问一页所需的时间大约为3ns左右;当页面数量落在[11, 26]时,所需时间为6ns左右;继续增加页面数量,所需时间涨到10ns上下了。这说明系统存在二级TLB。我很懒,就不画折线图了。

如果你有什么想法,或者发现了本文的问题,欢迎给我发邮件