Windows Via C/C++:用户模式下的线程同步——Cache行

来源:互联网 发布:数据库管理员薪资 编辑:程序博客网 时间:2024/05/16 12:27

为了提升运行在多处理器上的程序的性能,你应该了解CPU Cache的概念。当CPU从内存中读取某个字节时,它会按Cache中数据块大小(也就是Cache行大小)将一整块数据读到CPU的Cache中。比如Cache数据块大小是32,则内存地址按Cache行大小对齐形成0~31、32~63、……、32n~32(n+1)这样的块,当要读取的字节地址为41时,CPU将把包含该地址的整个内存块(32~63)读入Cache中。用公式表示时,假设Cache行大小为C,待读取的变量地址为Addr,则CPU读取内存块的地址是 (Addr/C)*C~(Addr/C+1)*C。将数据读入Cache后,CPU就可以在速度较高的Cache中操作该值,而不用每次都通过数据总线操作内存。

然而,在多处理器环境下,Cache可能会使内存更新出现一些问题,以其中的某一行为例:

  1. CPU1从内存中读取了一个字节B,导致与其相邻的Cache行大小的字节被读入CPU1的Cache行CL1中
  2. CPU2从内存中读取同样的字节B,这样与其相邻的Cache行大小的字节又被读入到CPU2的Cache行CL2中
  3. CPU1更改了B的值,此时CL1中B的值已被改变,但尚未写入到RAM中
  4. CPU2访问B,问题就出现在这里,CPU2从CL2中读到的B的值和CPU1的CL1中的B值没有同步更新

上面的情形是灾难性的。当然,CPU的设计者们已经从硬件角度解决了这个问题,当某个CPU核心Cache行中的值改变时,其它CPU核心将会得知这一改变并刷新自己的Cache。在上面的第4步中,CPU1会把CL1的内容写入内存,然后CPU2重新从内存中读取数据并写入CL2中。可以看出,Cache在多处理器环境中可能会使系统性能下降。 这一切意味着你应该将代码中的数据按照Cache行大小边界对齐,并把只读(或读操作频繁的)数据和读-写数据分开,把经常同时访问的数据组织在一起。

下面是一个设计比较糟糕的结构:

struct CUSTINFO {  DWORD dwCustomerId;// Mostly read-only  intnBalanceDue;// Read-write  wchar_tszName[100];// Mostly read-only  FILETIMEftLastOrderData;// Read-write};

可以调用GetLogicalProcessorInformation函数查询当前系统CPU的Cache行大小,具体用法可查阅MSDN。一旦获得了Cache行大小,就可以使用C/C++编译器的__declspec(align(#))对齐变量了,下面是改进后的CUSTINFO结构:

#define CACHE_ALIGN 64struct __declspec(align(CACHE_ALIGN)) CUSTINFO {  DWORD dwCustomerId;// Mostly read-only  wchar_t szName[100];// Mostly read-only  // Force the following memebers to be in a different cache line  _declspec(align(CACHE_ALIGN))  int nBalanceDue;// Read-write  FILETIME ftLastOrderData;// Read-write};

关于__declspec(align(#))指令的细节,请参阅http://msdn2.microsoft.com/en-us/library/83ythb65.aspx。