Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/56.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 什么时候在程序中第一次提取缓存线?_C_Arrays_Performance_Caching_Processor_X86 - Fatal编程技术网

C 什么时候在程序中第一次提取缓存线?

C 什么时候在程序中第一次提取缓存线?,c,arrays,performance,caching,processor,x86,C,Arrays,Performance,Caching,Processor,X86,假设我有一个简单的C程序,如下所示: void print_character(char); int main(int argc, char* argv[]){ char loads_of_text[1024]; int count = strlen(argv[1]); memcpy(loads_of_text, argv[1], count); for(int i = 0; i < count; ++i) print_character(loads_of_t

假设我有一个简单的C程序,如下所示:

void print_character(char);

int main(int argc, char* argv[]){

  char loads_of_text[1024];
  int count = strlen(argv[1]);
  memcpy(loads_of_text, argv[1], count);

  for(int i = 0; i < count; ++i)
    print_character(loads_of_text[i]);

  return 0;
}
void打印字符(char);
int main(int argc,char*argv[]){
char加载_文本的_[1024];
int count=strlen(argv[1]);
memcpy(加载_文本的_,argv[1],计数);
对于(int i=0;i
正如我所理解的,缓存的概念是,由于获取内存时的延迟,处理器在被请求提高性能时,将获取比必需的更多的数据。只有当我按顺序使用内存,直到整个缓存线都被读取,然后它将获取另一条缓存线时,这才有效


但是我很难准确地想象处理器何时提取和处理缓存线,在上面的代码示例中,这种情况究竟发生在哪里?什么时候处理缓存线?

我很难理解它的作用:

int count = strlen(argv);
你应该得到警告。顺便说一下,
argv[0]
是指向可执行文件的路径

首先确保理解代码是如何工作的,然后,也只有这样,请转到更高级的主题,例如理解缓存


给你直觉:

缓存将加载尽可能多的数据(例如一个数组的数据),希望您的程序稍后会请求它(因为从主内存加载某些数据是很昂贵的)


如果缓存已满,则会选择一个牺牲品(使用缓存使用的任何策略,如LRU),并由新请求的数据段替换。进一步阅读:有关链接,请参阅此答案的结尾

缓存线的大小为64B,并在64B边界上对齐。(一些CPU可能使用不同大小的缓存线,但64B是一种非常常见的选择)

缓存加载数据有两个原因:

  • 请求未命中:加载或存储指令访问的字节不在当前任何热缓存线中

    在不久的将来,对同一字节(或int或其他)的访问将在缓存中命中(时态局部性)。在不久的将来,对同一缓存线中附近字节的访问也将命中(空间位置)。以错误的顺序在多维数组上循环,或者在仅访问一个成员的结构数组上循环,这是非常糟糕的,因为必须加载整个缓存线,但只使用其中的一小部分

  • 预取:在两次顺序访问之后,硬件预取程序会注意到这种模式,并开始加载尚未访问的缓存线,因此希望在程序访问缓存线时不会出现缓存丢失。对于硬件预取器行为的一个具体示例,英特尔的优化手册描述了各种特定CPU中的硬件预取器(有关链接,请参阅tag wiki),并指出它们仅在内存系统尚未充满需求未命中时才运行

    还有软件预取:软件可以运行一条指令,告诉CPU它将很快访问某些内容。但即使内存还没有准备好,程序也会继续运行,因为这只是一个提示。程序不会在等待缓存丢失时陷入困境。现代的硬件预取非常好,而软件预取通常是浪费时间。它对于二进制搜索非常有用,您可以在查看新的中间位置之前预取1/4和3/4位置

    在未测试软件预取是否确实加快了实际代码的速度之前,不要添加软件预取。即使硬件预取做得不好,软件预取也可能没有帮助,或者可能会造成伤害。无序执行通常会隐藏缓存未命中延迟


通常缓存是“满”的,加载新行需要丢弃旧行。缓存通常通过对集合中的标记进行排序来实现,因此每次访问缓存线都会将其移动到最近使用的位置

以8路关联缓存为例:

64B内存块可以通过其映射到的集合中的8种“方式”中的任意一种进行缓存。部分地址位用作选择一组标记的“索引”。(有关将地址拆分为缓存线内的
标记|索引|偏移量的示例,请参见。OP对其工作原理感到困惑,但有一个有用的ASCII图。)

命中/未命中的确定不取决于顺序。快速缓存(如一级缓存)通常会并行检查所有8个标记,以找到与地址高位匹配的标记(如果有)

当我们需要为新行留出空间时,我们需要从8个当前标记中选择一个进行替换(并将数据放入相关的64B存储阵列)。如果有任何当前处于无效状态(不缓存任何内容),那么选择是显而易见的。在正常情况下,所有8个标记都已有效

但我们可以用标签存储额外的数据,足以存储订单。每次缓存命中时,集合中标记的顺序都会更新,以将命中的行置于MRU位置

当需要分配新行时,退出LRU标记,并将新行插入MRU位置

标准的LRU策略意味着,在一个稍微太大而无法放入缓存的阵列上循环意味着您将永远看不到任何缓存命中,因为当您返回到同一地址时,它已被逐出。一些CPU使用复杂的替换策略来避免这种情况:例如,Intel IvyBridge的大型共享三级缓存使用,因此新的分配会逐出其他最近分配的线路,保留具有未来价值的线路。这需要额外的逻辑,因此只能在大/慢缓存中执行,而不能在更快的L2和L1缓存中执行

(为什么即使在程序开始时,所有8个标记通常都有效:

当程序开始执行时,内核已经在运行程序的同一个CPU上运行了一堆代码