C 处理器堆读取和预取

C 处理器堆读取和预取,c,memory,memory-management,heap-memory,C,Memory,Memory Management,Heap Memory,所以我试图找出堆读取如何可能通过预取降低处理器的性能,这只是一个理论问题,所以在示例中我使用了一些类似C的wonder语言 假设我们有一个120字节的堆,它有一些内存供程序使用。 [0…19,/*免费/,40-79,/免费到年底(119)*/] 我有一些结构,通过20字节的内存神奇地对齐 #include <stdlib.h> struct magic_struct { long long int foo[3]; short int bar; }; typedef M

所以我试图找出堆读取如何可能通过预取降低处理器的性能,这只是一个理论问题,所以在示例中我使用了一些类似C的wonder语言

假设我们有一个120字节的堆,它有一些内存供程序使用。 [0…19,/*免费/,40-79,/免费到年底(119)*/] 我有一些结构,通过20字节的内存神奇地对齐

#include <stdlib.h>

struct magic_struct {
   long long int foo[3];
   short int bar;
};

typedef MagicStruct struct magic_struct;

void read_magic_struct(MagicStruct* buzz) {
   // Some code to read struct
} 

int main(void) {
   MagicStruct *str1 = malloc(sizeof(MagicStruct));
   MagicStruct *str2 = malloc(sizeof(MagicStruct));

   read_magic_struct(str1);
   read_magic_struct(str2);
   
   free(str1);
   free(str2);
}
#包括
魔法结构{
龙永福[3];
短整型条;
};
typedef MagicStruct struct magic_struct;
无效读取魔法结构(魔法结构*buzz){
//读取struct的一些代码
} 
内部主(空){
MagicStruct*str1=malloc(sizeof(MagicStruct));
MagicStruct*str2=malloc(sizeof(MagicStruct));
读取魔法结构(str1);
读取魔法结构(str2);
免费(str1);
免费(str2);
}
假设我们的处理器获取40字节的缓存线, 这意味着,在我们当前的内存表示中,处理器在读取str1时无法预取str2,因此程序执行会变慢?如果内存缓冲区是空的,或者第一个空内存块是40字节,那么如何分配结构?如果structs的大小为50字节,处理器是否会命中缓存未命中?是否有某种机制决定何时何地在堆上分配内存

是否有某种机制决定何时何地在堆上分配内存

决定堆上内存分配位置的机制是一段称为“内存分配器”的代码,它是C运行时库的一部分,不太关心预取或类似的事情。大多数内存分配器尽最大努力使“相关”分配“紧密地联系在一起”,但这只是在尽最大努力的基础上进行的。所以你不能假设这两个分配有多远

如果内存缓冲区是空的,或者第一个空内存块是40字节,那么如何分配结构

以任何可能的方式:在阅读系统上的C运行时库使用的内存分配器的源代码之前(例如,大多数Linux系统上的glibc),您都无法知道。这完全是武断的,一般来说,你无法预测这些结构将如何被分配。而且在现实生活中,堆不必是连续的,也就是说,堆不必是单个大内存块,它通常是许多内存块,它们之间有间隙

因此,让我们假设我们的处理器获取40字节的缓存线,这意味着使用我们当前的内存表示,处理器在读取str1时不能预取str2,因此程序执行会变慢

假设您建议的堆布局,唯一有效的语句如下:包含
str1
的缓存线不包含
str2
。这就是你能说的,没有更多了。它不会告诉您任何有关预取的信息,因为预取必须在需要之前提前获取其他缓存线

我认为您误用了“预取”一词,因为处理器仅以完整缓存线的形式与内存交换数据。预取并不意味着在获取单个缓存线时,其他一些有用的数据也在该缓存线内。我们称之为缓存一致性,它是在程序中如何布局数据的属性。如果您需要如此精细的控制,您需要编写自己的内存分配器(即使它很简单)

预取意味着处理器有一个系统,该系统监视正在获取的缓存线,并预测需要另一个缓存线,并在使用它之前获取它。如果要将预回迁控制到这样的程度,可以通过显式预回迁机器指令触发预回迁,也可以通过处理器中实现的预回迁算法触发预回迁。现代处理器在检测连续的、甚至由重复间隔分隔的顺序访问方面非常出色


考虑这样的事情当然是好的,但你必须明白处理器是非常好的,如果你认为预取可能是你的问题,你必须有一些非常好的理由来解释为什么会这样,通常你需要实际的测量。谈论优化预取而没有测量结果表明处理器正在等待缓存线从内存进入,这绝对是愚蠢的,也是浪费时间。因此,在实际操作中,如果您想在真实处理器上使用真实预取(而不是虚构的东西)来探索这一点,您必须安装例如“英特尔VTune分析器”,或使用Valgrind的cachegrind(在linux上),在这些工具下运行程序,然后能够解释结果,查明问题,通过更改数据布局或使用显式预取计算机命令(或编译器内部指令)来解决这些问题,然后通过提高缓存命中率来验证解决方案。理想情况下,这应该是自动化的,这样您就不会出现性能倒退,也就是说,您可以在持续集成系统下以脚本的形式运行所有这些检测和结果解释,这样您就知道不会意外地破坏东西。但这需要大量的工作,如果你是从零开始(一个新项目),假设你在脚本、CI系统和C方面的熟练程度最低,那么你仍然可能需要数百个小时才能完全设置和调整以获得信任。指导原则是:

缺乏测量是你不在乎结果的初步证据

换句话说,如果你真的关心性能的某个方面,那么表现出这种关心的唯一方法就是测量它,并将这些测量作为正常工作流程的一部分,也就是说,一旦它们被设置好,你甚至不用担心它们-你签入一些新代码,如果你破坏了性能,性能测试将失败,在允许合并之前,您必须先修复它。这就是正常情况