确定 Cpu 缓存关联性
Determine Cpu cache associativity
我正在尝试确定处理器的关联性。
我有英特尔酷睿 i5-2500:
L1 数据:32 Kb,8 路组关联
L1 指令:32 Kb,8 路集相联
L2:256 Kb,8 路组关联
L3:6 Mb,12 路组关联,在所有内核之间共享
我用处理器滴答测量数组元素的平均访问时间。数组被分割成碎片
在循环中,我增加了片段的数量。两个相邻片段之间的距离等于 L3 缓存大小。我访问所有片段的第一个元素,然后是第二个元素,依此类推。每个元素都包含下一个元素的索引。最后一个元素包含第一个元素的索引。
看起来像这样:enter image description here
当碎片数量大于缓存的关联性时,平均访问时间应该增加。
我得到了以下结果:
enter image description here
第一个跳转对应TLB的关联性,第二个对应L1和L2缓存的关联性,但我不明白为什么超过L3缓存的关联性后时间不增加
我也尝试了不同的大小和偏移量
我做错了什么吗?或者我的代码有什么错误?
你能给我解释一下吗?
这是代码:
#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#define SIZE 6291456 //6 Mb
#define OFFSET 6291456
#define ROUNDS 200
#define MIN_FRAGMENTS_COUNT 1
#define MAX_FRAGMENTS_COUNT 32
void FreeArray(int* array, int size) {
assert(size > 0 && "Size must be gerater than zero\n");
if (NULL == array) {
return;
}
for (int i = 0; i < size; ++i) {
array[i] = 0;
}
free(array);
}
int* CreateArray(int size) {
assert(size > 0 && "Size must be greater than zero\n");
return calloc(size, sizeof(int));
}
unsigned long long int GetTicksCount(void) {
unsigned int high = 0;
unsigned int low = 0;
__asm__ __volatile__("rdtsc" : "=a"(low), "=d"(high));
return (((unsigned long long int)high) << 32 | (unsigned long long int)low);
}
void SetIndexes(int* array, int fragment_size, int offset,
int fragments_count) {
assert(NULL != array && "Pointer to array must not be NULL\n");
assert(fragment_size > 0 && "Fragmnet size must be greater than zero\n");
assert(offset > 0 && "Offset must be greater than zero\n");
assert(fragments_count > 0 && "Fragments count must be greater than zero\n");
assert(fragment_size <= offset &&
"Fragment size must not be greater than offset\n");
int last_fragment = fragments_count - 1;
int last_element = fragment_size - 1;
for (int i = 0; i < last_element; ++i) {
for (int j = 0; j < last_fragment; ++j) {
array[j * offset + i] = (j + 1) * offset + i; //Go in the same element of next fragment
}
array[last_fragment * offset + i] = i + 1; // Go in the next element from last fragment
}
array[last_fragment * offset + last_element] = 0; // Go in first element from last element
}
unsigned long long int CalcAccessTime(int* array, int size) {
assert(NULL != array && "Pointer to array must not be NULL\n");
assert(size > 0 && "Size must be greater than zero\n");
unsigned long long int start = 0;
unsigned long long int end = 0;
unsigned long long int min_time = ULLONG_MAX;
int index = 0;
for (int i = 0; i < ROUNDS; ++i) {
start = GetTicksCount();
for (int j = 0; j < size; ++j) {
index = array[index];
}
end = GetTicksCount();
unsigned long long int cur_time = (end - start) / size;
if (cur_time < min_time) {
min_time = cur_time;
}
}
return min_time;
}
int main(int argc, char** argv) {
int integers_count = SIZE / sizeof(int);
int offset_int = OFFSET / sizeof(int);
for (int i = MIN_FRAGMENTS_COUNT; i <= MAX_FRAGMENTS_COUNT; ++i) {
int size = i * offset_int;
int* array = CreateArray(size);
if (NULL == array) {
return -1;
}
SetIndexes(array, integers_count / i, offset_int, i);
printf("Fragments: %d\n", i);
printf("Time: %llu\n", CalcAccessTime(array, integers_count));
FreeArray(array, size);
}
return 0;
}
与核心缓存不同,LLC 有一个额外的集合映射级别。
您通常不希望整个内存区域映射到同一个 LLC 切片中的相邻集合,因为这样一来,远离该切片的内核的访问时间会非常糟糕。相反,对于常见情况,您希望数据尽可能均匀地分布。为此,他们添加了一个哈希函数作为确定切片的映射的一部分。
例外情况是您需要所谓的 "sub-NUMA clustering" 或 "core-clustering" 的用例 - 在这些情况下,您可以通过显式配置覆盖该分布。
散列也应该在更高位上工作,以更好地分散内存区域,因此跳过 LLC 大小将不起作用。您仍然在 多个 切片中着陆在同一个集合上,这意味着您有效地获得了关联性乘以切片数。但是,根据您的结构对齐方式,您不一定会获得最佳分布,因此您可能会开始看到该关联级别之前的跳跃。
这是一篇关于切片散列的更多细节的论文:https://arxiv.org/pdf/1508.03767.pdf
我正在尝试确定处理器的关联性。 我有英特尔酷睿 i5-2500:
L1 数据:32 Kb,8 路组关联
L1 指令:32 Kb,8 路集相联
L2:256 Kb,8 路组关联
L3:6 Mb,12 路组关联,在所有内核之间共享
我用处理器滴答测量数组元素的平均访问时间。数组被分割成碎片
在循环中,我增加了片段的数量。两个相邻片段之间的距离等于 L3 缓存大小。我访问所有片段的第一个元素,然后是第二个元素,依此类推。每个元素都包含下一个元素的索引。最后一个元素包含第一个元素的索引。
看起来像这样:enter image description here
当碎片数量大于缓存的关联性时,平均访问时间应该增加。
我得到了以下结果: enter image description here
第一个跳转对应TLB的关联性,第二个对应L1和L2缓存的关联性,但我不明白为什么超过L3缓存的关联性后时间不增加
我也尝试了不同的大小和偏移量
我做错了什么吗?或者我的代码有什么错误?
你能给我解释一下吗? 这是代码:
#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#define SIZE 6291456 //6 Mb
#define OFFSET 6291456
#define ROUNDS 200
#define MIN_FRAGMENTS_COUNT 1
#define MAX_FRAGMENTS_COUNT 32
void FreeArray(int* array, int size) {
assert(size > 0 && "Size must be gerater than zero\n");
if (NULL == array) {
return;
}
for (int i = 0; i < size; ++i) {
array[i] = 0;
}
free(array);
}
int* CreateArray(int size) {
assert(size > 0 && "Size must be greater than zero\n");
return calloc(size, sizeof(int));
}
unsigned long long int GetTicksCount(void) {
unsigned int high = 0;
unsigned int low = 0;
__asm__ __volatile__("rdtsc" : "=a"(low), "=d"(high));
return (((unsigned long long int)high) << 32 | (unsigned long long int)low);
}
void SetIndexes(int* array, int fragment_size, int offset,
int fragments_count) {
assert(NULL != array && "Pointer to array must not be NULL\n");
assert(fragment_size > 0 && "Fragmnet size must be greater than zero\n");
assert(offset > 0 && "Offset must be greater than zero\n");
assert(fragments_count > 0 && "Fragments count must be greater than zero\n");
assert(fragment_size <= offset &&
"Fragment size must not be greater than offset\n");
int last_fragment = fragments_count - 1;
int last_element = fragment_size - 1;
for (int i = 0; i < last_element; ++i) {
for (int j = 0; j < last_fragment; ++j) {
array[j * offset + i] = (j + 1) * offset + i; //Go in the same element of next fragment
}
array[last_fragment * offset + i] = i + 1; // Go in the next element from last fragment
}
array[last_fragment * offset + last_element] = 0; // Go in first element from last element
}
unsigned long long int CalcAccessTime(int* array, int size) {
assert(NULL != array && "Pointer to array must not be NULL\n");
assert(size > 0 && "Size must be greater than zero\n");
unsigned long long int start = 0;
unsigned long long int end = 0;
unsigned long long int min_time = ULLONG_MAX;
int index = 0;
for (int i = 0; i < ROUNDS; ++i) {
start = GetTicksCount();
for (int j = 0; j < size; ++j) {
index = array[index];
}
end = GetTicksCount();
unsigned long long int cur_time = (end - start) / size;
if (cur_time < min_time) {
min_time = cur_time;
}
}
return min_time;
}
int main(int argc, char** argv) {
int integers_count = SIZE / sizeof(int);
int offset_int = OFFSET / sizeof(int);
for (int i = MIN_FRAGMENTS_COUNT; i <= MAX_FRAGMENTS_COUNT; ++i) {
int size = i * offset_int;
int* array = CreateArray(size);
if (NULL == array) {
return -1;
}
SetIndexes(array, integers_count / i, offset_int, i);
printf("Fragments: %d\n", i);
printf("Time: %llu\n", CalcAccessTime(array, integers_count));
FreeArray(array, size);
}
return 0;
}
与核心缓存不同,LLC 有一个额外的集合映射级别。
您通常不希望整个内存区域映射到同一个 LLC 切片中的相邻集合,因为这样一来,远离该切片的内核的访问时间会非常糟糕。相反,对于常见情况,您希望数据尽可能均匀地分布。为此,他们添加了一个哈希函数作为确定切片的映射的一部分。
例外情况是您需要所谓的 "sub-NUMA clustering" 或 "core-clustering" 的用例 - 在这些情况下,您可以通过显式配置覆盖该分布。
散列也应该在更高位上工作,以更好地分散内存区域,因此跳过 LLC 大小将不起作用。您仍然在 多个 切片中着陆在同一个集合上,这意味着您有效地获得了关联性乘以切片数。但是,根据您的结构对齐方式,您不一定会获得最佳分布,因此您可能会开始看到该关联级别之前的跳跃。
这是一篇关于切片散列的更多细节的论文:https://arxiv.org/pdf/1508.03767.pdf