为可移植 C 库使用 GCC __sync 扩展

Posted

技术标签:

【中文标题】为可移植 C 库使用 GCC __sync 扩展【英文标题】:Using GCC __sync extensions for a portable C library 【发布时间】:2015-10-24 18:39:39 【问题描述】:

我正在 OS X 上开发一个 C 库(@​​987654324@ 恰好与 GCC 4.2.x 一起发布)。该库旨在最大程度地移植,而不是特定于OS X

我希望最终用户在从源代码构建时不会感到头疼。因此,虽然该项目被编码为std=c11 以获得最现代C 的一些好处,但这个版本的GCC 似乎不支持诸如atomics 之类的可选问题。

我假设 GNU-Linux 和各种 BSD 最终用户拥有 (a) GCC 的更高版本,或者 (b) 安装最新和最好的版本。

依赖GCC 的__sync 扩展来获得所需的CAS(等)语义是一个好的决定吗?

【问题讨论】:

在 OS X 上,您可能应该选择 clang 而不是 gcc。 gcc 4.2 真的很老了。 clang 和 gcc 的现代版本具有 __atomic 扩展,更接近 C11 提供的功能,并且从 gcc 4.9 左右开始,您确实拥有 C11 原子。 看trac.mpich.org/projects/openpa/wiki/FAQ或preshing.com/20130505/…。 谢谢@Jeff - 会看看。 它在第一段上写着“不特定于 OS X”,@JensGustedt。本机 OS X 是a non-issue。只想知道依赖 4.2.x __sync 扩展的广泛可用性是否合理。 【参考方案1】:

我认为您需要退后一步,首先定义所有用例。除了 __sync 与 C11 atomics 的优点,最好先定义您的需求(即 __sync/atomics 是不需要的解决方案)。

Linux 内核是使用锁定、原子等的最重、最复杂的用户之一,而 C11 原子对它来说还不够强大。见https://lwn.net/Articles/586838/

例如,您最好将东西包装成 pthread_mutex_lock / pthread_mutex_unlock 对。将一个结构声明为 C11 原子并保证对整个结构的原子访问,只有它的一部分。因此,如果您需要以下内容是原子的:

glob.x = 5;
glob.y = 7;
glob.z = 9;

您最好将其包装在 pthread_mutex_* 配对中。相比之下,在 Linux 内核中,这将是自旋锁或 RCU。事实上,您也可以使用 RCU。注意这样做:

CAS(glob.x,5)
CAS(glob.y,7)
CAS(glob.z,9)
如果您想要 all or nothing 更新,

与互斥锁配对相同。

我会将您的实现包装在一些薄层中。例如,最好的方法可能是在一个拱门上使用 __sync [比如 BSD],而在另一个拱门上使用 atomics。通过将其抽象为带有宏/内联的 .h 文件,您可以编写“通用代码”,而无需到处使用大量 #ifdef's

我写了一个环形队列结构/对象。它的更新程序可以使用 CAS [我为此编写了自己的内联汇编]、pthread_mutex_*、内核自旋锁等。实际选择由 my_ring_queue.h 中的一两个 #ifdef's 控制

抽象的另一个优点:您可以在更远的地方改变主意。假设您早期选择了 __sync 或 atomics。您在 30 个文件中的 200 个位置对此进行了编码。然后,您会意识到这是错误的选择。大量的编辑随之而来。所以,永远不要在你的任何 .c 文件中放置一个赤裸裸的 [say] __sync_val_compare_and_swap。将它作为 #define MY_CAS_VAL(...) __sync_val_compare_and_swap(...) 之类的东西放入 my_atomics.h 中一次,然后使用 MY_CAS_VAL

您还可以通过对子池分配/释放等某些事情使用线程本地存储来减少需要线程间锁定的位置的数量。

您可能还想混合使用 CAS 和锁定配对。使用低级 CAS 时,某些特定用途会更好,而使用互斥锁对的其他用途会更有效。同样,如果您可以先定义您的需求,这会有所帮助。

另外,请考虑最终的灾难场景:编译器不支持原子并且 __sync 对于您正在编译的架构不可用[或不起作用]。然后呢?

在这种情况下,请注意所有 __sync 操作都可以使用 pthread_mutex 配对来实现。那是你的灾难后备。

【讨论】:

仅仅因为 C11 atomic 不适用于 Linux 并不意味着您应该阻止使用它。 Linux 相对于几乎任何其他东西来说都是特殊的,当然相对于由提出这个问题的 SO 用户类型编写的用户空间代码而言。 在您使用的任何平台上,pthread mutex 的成本是否低于三个原子存储? @Jeff 让 C11 这样做是很糟糕的,因为它永远无法拥有足够的全局信息来不进行糟糕的代码运动优化。这就是链接详细的内容。这是在 lib 中正确完成的,因为您需要使用编译器无法预测的显式障碍指定限制。而且,在三重 CAS 示例中[考虑更改 struct 中的 50 项内容],它不一样,因为您希望对 struct 进行原子更新。对于单个 CAS,读者将看到不一致的状态(例如,它为 x 获取新值,但为 y/z 获取旧值,因为它在 cas(x) 和 cas(y) 之间捕捉数据)跨度> 我阅读了所有该链接以及其中的许多链接,我不同意您的偏执观点,尤其是关于 GCC 内在函数,它可能同样容易受到不良优化的影响。跨度> @alphazero 你一定会对这个感兴趣。新闻很火爆:***.com/questions/33083270/…

以上是关于为可移植 C 库使用 GCC __sync 扩展的主要内容,如果未能解决你的问题,请参考以下文章

qt5.3.2移植到arm上出undefined reference to '__sync_sub_and_fetch_4的错

Ubuntu 16.04下使用gcc输出汇编的.0文件为可执行文件时出现:`_start'被多次定义

[GCC] Linker

__extension__关键字是否可以在所有编译器中移植?

__COUNTER__ 宏是不是可移植?

可移植地识别非标准 C++?