使用缓存行对C语言全局共享状态的修改进行无锁检查

10

编辑:ST不允许新手发布超过两个链接。很抱歉缺少参考资料。

我正在尝试在一个C应用程序中减少锁定开销,其中检测全局状态的变化对性能非常重要。尽管我最近一直在阅读相关主题(例如来自H. Sutter等许多人的内容),但我仍然对我的实现不太有信心。我想使用类似于CAS操作和DCL的组合,对Cache-Line Aligned全局变量进行检查,从而避免虚假共享,以从在多个线程之间共享的数据更新线程本地数据。我缺乏信心主要是由于以下原因:

  1. 我无法解释关于 Type-Attributes 的GNU文档。
  2. 我似乎找不到任何文献和示例,可以轻松翻译为C,例如在ST上aligning-to-cache-line-and-knowing-the-cache-line-size 1 (尽管 1 在某种程度上回答了我的问题,但我对我的实现不太自信)
  3. 我在C方面的经验有限。

我的问题:

  1. Type-Attributes文档说明:

    此属性为指定类型的变量指定最小对齐方式(以字节为单位)。例如,声明:

    (请参见Type-Attributes文档进行声明)

    强制编译器尽可能确保每个类型为struct S more_aligned_int 的变量将被分配并至少对齐到8字节边界。在SPARC上,将所有类型为struct S 的变量对齐到8字节边界允许编译器在将一个类型为struct S的变量复制到另一个变量时使用ldd和std(双字加载和存储)指令,从而提高运行时效率。

    这意味着struct S more_aligned_int 的开头总是对齐到8字节边界吗?这并不意味着数据将填充以使用正好64字节,对吧?

  2. 假设1成立,即struct cache_line_aligned的每个实例(请参见代码示例1 )都在64字节边界上对齐,并且正好使用一个缓存行(假设缓存行长度为64字节

  3. 使用typedef 进行类型声明不会改变 __attribute__ ((aligned(64))) 的语义(请参见代码示例2

  4. 如果结构体声明了__attribute__ ... ,则无需使用aligned_malloc来实例化结构体。

// Example 1
struct cache_line_aligned {
  int version;
  char padding[60];
} __attribute__ ((aligned (64)));

// Example 2
typedef struct {
  int version;  
  // place '__attribute__ ((aligned (64)))' after 'int version'
  // or at the end of the declaration 
  char padding[60];
} cache_line_aligned2 __attribute__ ((aligned (64)));

最后,这是一个使用缓存行对齐方法的函数草图,可以有效地检查全局状态是否已被其他线程修改:

void lazy_update_if_changed(int &t_version, char *t_data) {
  // Assuming 'g_cache_line_aligned' is an instance of 
  // 'struct cache_line_aligned' or 'struct cache_line_aligned2' 
  // and variables prefixed with 't_' being thread local 
  if(g_cache_line_aligned.version == t_version) {
    // do nothing and return
  } else {
    // enter critical section (acquire lock e.g. with pthread_mutex_lock) 
    t_version = g_cache_line_aligned.version
    // read other data that requires locking where changes are notified 
    // by modifying 'g_cache_line_aligned.version', e.g. t_data
    // leave critical section
  }
} 

对于长篇帖子,非常抱歉。

谢谢!

1个回答

7
当您定义对齐类型时,比如对齐到8字节边界,编译器应该通过填充来使类型的大小成为对齐的倍数(这里是8字节的倍数)。
这么做的原因很简单。假设您想要定义一个那种对齐类型的数组。自然地,它的每个元素也应该是对齐的。这就是为什么可能需要填充的原因。
以下是一个小演示:
#include <stdio.h>

struct cache_line_aligned {
  int version;
//  char padding[60];
} __attribute__ ((aligned (64)));

int main(void)
{
  struct cache_line_aligned s;
  struct cache_line_aligned a[2];
  printf("sizeof(struct cache_line_aligned) = %d\n", (int)sizeof(struct cache_line_aligned));
  printf("sizeof(s) = %d\n", (int)sizeof(s));
  printf("sizeof(a[0]) = %d\n", (int)sizeof(a[0]));
  printf("sizeof(a) = %d\n", (int)sizeof(a));
  return 0;
}

输出 (ideone):

sizeof(struct cache_line_aligned) = 64
sizeof(s) = 64
sizeof(a[0]) = 64
sizeof(a) = 128

如果您非动态地创建struct cache_line_aligned实例(即不通过malloc()等方式),就像上面的代码一样,它将被对齐。
C标准(从1999年开始)适用于malloc()calloc()realloc()
The pointer returned if the allocation succeeds is suitably aligned so that
it may be assigned to a pointer to any type of object and then used to
access such an object or an array of such objects in the space allocated
(until the space is explicitly deallocated).

在这里,任何类型的对象并不包括像上面的结构体那样人为对齐/填充的类型,因为在C标准中没有类似于__attribute__ ((aligned (64)))的东西。这是GNU扩展。对于具有任意对齐方式的动态分配对象,您必须使用适当的内存分配函数或手动对齐(通过分配更多内存,然后"对齐"指针值)。


太好了!这解释得非常清楚。我没想到可以使用 sizeof 来检查对齐!我会记住这个方法的。那么动态分配对齐的结构体呢?aligned_malloc 能胜任吗? - instilled
很可能会。阅读其文档以确保。 - Alexey Frunze
当然!我会这样做。再次感谢你的精彩回答。 - instilled

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接