测试cache大小-C语言实现

1. 简介

在实际CPU从内存中取数时很多时候从cache中存取,在这个实验中使用C语言编成估计cache的大小。

2. 实验思路

当一个数组的大小超过cache的大小时,随机读取数组的元素会发生cache的替换现象。如果要存取的数据经常不在cache中(被替换出去/没有载入),CPU需要多次从内存中读取数据。

从内存中读取数据的时间远大与从cache中读取的时间,因此从如果数组大于cache size那么多次随即读取的时间会增加。当随即读取时间出现显著增加时,数组的大小即为cache size的估计量。

3. 代码

代码本身比较简单。注释也都比较详细,直接上代码。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>

#define READ_TIMES 999999999
#define TEST_RANGE 24

int main()
{
    int i;
    // 每次要测试的内存块大小
    int *block_size = (int *)calloc(TEST_RANGE, sizeof(int));
    for (i = 0; i < TEST_RANGE; ++i)
        block_size[i] = pow(2, i);

    srand(time(NULL));
    // 每次循环测试大小为block_size[i]的数组随即读取的时间
    for (i = 0; i < TEST_RANGE; ++i)
    {
        // 用size代替block_size[i]减少代码量
        int size = block_size[i];
        // 创建大小为block_size[i]的数组
        int *block = (int *)calloc(size, sizeof(int));

        int j, temp;
        clock_t start = clock(), total_time;
        // 开始随即读取
        for(j = 0; j < READ_TIMES; ++j)
            temp += block[rand() % size];
        total_time = clock() - start;

        // 由于数组是int类型,因此最终大小要乘 sizeof(int)
        printf("At size: %ldB, we need %lf sec\n", size * sizeof(int),\
            (double)total_time / CLOCKS_PER_SEC);
    }
    return 0;
}
4. 运行结果

得到的运行结果如下

At size: 4B, we need 8.686316 sec
At size: 8B, we need 8.830951 sec
At size: 16B, we need 8.271359 sec
At size: 32B, we need 8.633918 sec
At size: 64B, we need 8.209328 sec
At size: 128B, we need 8.174642 sec
At size: 256B, we need 8.257708 sec
At size: 512B, we need 8.429885 sec
At size: 1024B, we need 8.398997 sec
At size: 2048B, we need 8.389903 sec
At size: 4096B, we need 8.349263 sec
At size: 8192B, we need 8.603128 sec
At size: 16384B, we need 8.294702 sec
At size: 32768B, we need 8.382414 sec
At size: 65536B, we need 8.452870 sec
At size: 131072B, we need 8.577709 sec
At size: 262144B, we need 8.493697 sec
At size: 524288B, we need 8.535531 sec
At size: 1048576B, we need 8.457926 sec
At size: 2097152B, we need 8.466891 sec
At size: 4194304B, we need 8.602997 sec
At size: 8388608B, we need 8.984190 sec
At size: 16777216B, we need 9.560884 sec
At size: 33554432B, we need 10.243553 sec

可以看到在数组大小为8388608 Byte时随机读取的时间发生了比较大的增长。因此可以估计cache的大小为4184304 Byte。

5. 验证

为了验证实验的结果,使用命令

getconf -a | grep CACHE

来获得机器的硬件信息。得到的输出如下:

LEVEL1_ICACHE_SIZE                 65536
LEVEL1_ICACHE_ASSOC                4
LEVEL1_ICACHE_LINESIZE             64
LEVEL1_DCACHE_SIZE                 32768
LEVEL1_DCACHE_ASSOC                8
LEVEL1_DCACHE_LINESIZE             64
LEVEL2_CACHE_SIZE                  524288
LEVEL2_CACHE_ASSOC                 8
LEVEL2_CACHE_LINESIZE              64
LEVEL3_CACHE_SIZE                  4194304
LEVEL3_CACHE_ASSOC                 16
LEVEL3_CACHE_LINESIZE              64
LEVEL4_CACHE_SIZE                  0
LEVEL4_CACHE_ASSOC                 0
LEVEL4_CACHE_LINESIZE              0

可以看到L3-cache的大小为4194304 Byte。因此上面估计的cache大小可以接受。

6. 讨论

实际上可以看到数组大小到达4194304 Byte时随即读取的总时间也比较大,这可能是cache替换时出现抖动引起的。

在没有超过cache size时,随机读取时间也有一定的波动,可能有如下原因:
(1)电脑上其他程序占用了一定的资源,并且每时每刻占用的资源大小不一样;
(2)在数组比较小的时,存取时间可能受到指令的局部性的影响。

  • 3
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,实现一个cache模拟器可以分为以下几个步骤: 1. 定义cache结构体 我们需要定义一个结构体来表示cache的属性,比如cache大小、块大小、关联度等等。 ```c typedef struct { int cache_size; // cache大小 int block_size; // 块大小 int associativity; // 关联度 int num_blocks; // 块数 int num_sets; // 集数 int offset_bits; // 偏移量位数 int index_bits; // 索引位数 int tag_bits; // 标记位数 int cache_hits; // 命中次数 int cache_misses; // 未命中次数 int **cache; // 二维数组,表示cache } cache_t; ``` 2. 初始化cache 在初始化cache时,需要根据cache的属性计算出相应的位数、块数和集数,并且要为cache分配相应的内存空间。 ```c cache_t *init_cache(int cache_size, int block_size, int associativity) { cache_t *cache = (cache_t *)malloc(sizeof(cache_t)); cache->cache_size = cache_size; cache->block_size = block_size; cache->associativity = associativity; cache->num_blocks = cache_size / block_size; cache->num_sets = cache->num_blocks / associativity; cache->offset_bits = log2(block_size); cache->index_bits = log2(cache->num_sets); cache->tag_bits = 32 - cache->offset_bits - cache->index_bits; cache->cache_hits = 0; cache->cache_misses = 0; cache->cache = (int **)malloc(sizeof(int *) * cache->num_sets); for (int i = 0; i < cache->num_sets; i++) { cache->cache[i] = (int *)malloc(sizeof(int) * associativity); for (int j = 0; j < associativity; j++) { cache->cache[i][j] = -1; } } return cache; } ``` 3. 实现cache的读写操作 在cache的读写操作中,需要首先计算出相应的标记位和索引位,然后根据关联度和替换策略找到相应的块。 ```c void cache_read(cache_t *cache, unsigned int address) { unsigned int tag = address >> (cache->offset_bits + cache->index_bits); unsigned int index = (address >> cache->offset_bits) & ((1 << cache->index_bits) - 1); for (int i = 0; i < cache->associativity; i++) { if (cache->cache[index][i] == tag) { cache->cache_hits++; return; } } cache->cache_misses++; // 没有命中,进行替换 int replace_index = rand() % cache->associativity; cache->cache[index][replace_index] = tag; } void cache_write(cache_t *cache, unsigned int address) { cache_read(cache, address); } ``` 4. 测试cache模拟器 我们可以编写一个测试函数,读取一个数据集中的地址序列,并且统计cache的命中次数和未命中次数。 ```c void test_cache(cache_t *cache, char *filename) { FILE *fp = fopen(filename, "r"); if (fp == NULL) { printf("Failed to open file!\n"); return; } unsigned int address; char operation[10]; while (fscanf(fp, "%s %x", operation, &address) != EOF) { if (strcmp(operation, "R") == 0) { cache_read(cache, address); } else if (strcmp(operation, "W") == 0) { cache_write(cache, address); } } fclose(fp); printf("Hits: %d\n", cache->cache_hits); printf("Misses: %d\n", cache->cache_misses); printf("Hit rate: %.2f%%\n", (float)cache->cache_hits / (cache->cache_hits + cache->cache_misses) * 100); } ``` 完整代码如下:
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值