-1

我编写了以下代码,由多个进程并行执行:

// spawn 10 times with id=0..9 by a master process.
void slave_processing(int id)
{
  SHARED_TYPE last=id;
   for(;;) {
     /* each slave operates on a specific byte of the shared array. */
     if (shared[id]!=last) fprintf(stderr,"S%i expected %i\n",id,last);
     shared[id]+=id;
     last+=id;
     if (last>10*id) {last=0; shared[id]=0;}
   }
}

它们都使用相同的(IPC/linux)共享内存,但每个都访问数组的一个单独条目。它可以在我的双核机器上完美运行,无论是 when SHARED_TYPEisint还是char. 我使用积极优化 (-O3) 进行编译并检查组装的二进制文件以确认内存引用是针对shared[id]访问执行的,并且寄存器用于last.

然而,我很困惑。我曾预料到,在某个时候,来自一个核心的影响字节可能不会反映在来自另一个核心的缓存内容上。例如,一个核心可能在缓存中有 [xxyyzzuu] 并将 [xxyyZZuu] 写回内存,而同时另一个核心已将内存字升级为 [XXyyzzuu](假设每对 char 是我的 32 位字中的一个字节) .

linux是否在做一些神奇的设置,以便shmget无法缓存获得的内存?或者是否正在进行一些低级缓存同步以确保核心#1 可以读取核心#2 的最新修改以更新所选字节而不会产生讨厌的副作用?

是否有任何其他(多核)架构您知道上述代码会在哪里失败(输入 fprintf)?

4

1 回答 1

2

该代码假定编译器不会缓存(例如,将内存中的内容一次读入寄存器并继续使用寄存器而不是内存)它在循环中干预的任何值,但会为每次读取从共享段。为确保所有部分都能看到写入内存的数据,shared应使用内存屏障访问数组。

shared虽然依赖于编译器,但如果你是易失性的,它通常会进行内存获取。

由于您调用 fprintf(),编译器必须假定 fprintf() 可以访问或更新shared数组中的某些内容 - 毕竟,编译器不知道 fprintf() 做了什么,所以理论上它可以访问全局数组,编译器是需要重新获取该内存shared(除非它可以证明 fprintf 无法更新它)

shared如果不是 volatile ,您的代码可能会有不同的行为,而您例如这样做

SHARED_TYPE last=id;
int wrong = 0,i;
for(i = 0; i < 10000000;i++) {
  if (shared[id]!=last) wrong++;
  shared[id]+=id;
  last+=id;
  if (last>10*id) {last=0; shared[id]=0;}
}
return wrong;

编译器不需要执行内存获取来获取shared[id]每次迭代的最新值。再说一次,如果编译器确实缓存了一个值,它可能只是旋转循环,一次又一次地检查相同的正确事物,而不会从内存中获取更新的值,从而使wrong= 0 符合预期。无论您在这里做什么都是不安全的,并且取决于编译器将为您生成什么代码 - 它可能会为小的代码更改生成具有显着不同结果的代码。

与此相关的是可以从内存中原子读取哪些数据类型,这取决于处理器、数据类型以及访问是否与内存对齐。

这是对缓存一致性的补充。我们使用的常见桌面和服务器平台是缓存一致的,这意味着硬件负责更新内存的魔力,因此所有处理器/内核都可以看到更改,而不是例如在另一个内核需要时仅驻留在一个内核上的 L1 缓存中访问相同的内存位置。(某些内存区域,例如用于 DMA 的区域可能无法为您提供此类缓存一致性保证)

今天的桌面/服务器平台看起来很像 NUMA,但许多更大、更特殊的内置 NUMA 系统没有缓存一致性保证。

于 2012-11-30T15:25:54.897 回答