GLib和CUDA之间的`__noinline__`宏冲突

Posted

技术标签:

【中文标题】GLib和CUDA之间的`__noinline__`宏冲突【英文标题】:`__noinline__` macro conflict between GLib and CUDA 【发布时间】:2022-01-14 23:03:40 【问题描述】:

我正在开发一个在 C 中同时使用 GLib 和 CUDA 的应用程序。在为 .cu 文件同时导入 glib.h 和 cuda_runtime.h 时似乎存在冲突。

7 个月前 GLib 进行了更改以避免与 pixman 的宏发生冲突。他们在 gmacros.h 中的标记 noinline 前后添加了 __:https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2059

鉴于 gcc 声称,这应该有效:

您可以选择在名称前后使用__ 指定属性名称。这允许您在头文件中使用它们,而不必担心可能的同名宏。例如,您可以使用属性名称__noreturn__ 而不是 noreturn。

但是,CUDA 确实在其宏中使用了____noinline__ 就是其中之一。他们承认可能的冲突,并添加了一些编译器检查以确保它不会在常规 c 文件中发生冲突,但似乎在 .cu 文件中它仍然适用:

#if defined(__CUDACC__) || defined(__CUDA_ARCH__) || defined(__CUDA_LIBDEVICE__)
/* gcc allows users to define attributes with underscores,
   e.g., __attribute__((__noinline__)).
   Consider a non-CUDA source file (e.g. .cpp) that has the
   above attribute specification, and includes this header file. In that case,
   defining __noinline__ as below  would cause a gcc compilation error.
   Hence, only define __noinline__ when the code is being processed
   by a  CUDA compiler component.
*/
#define __noinline__ \
        __attribute__((noinline))

我对 CUDA 开发很陌生,这显然是他们和 gcc 都知道的一个可能的问题,所以我只是缺少编译器标志或其他东西吗?或者这是 GLib 需要解决的真正冲突?

环境: glib 2.70.2、cuda 10.2.89、gcc 9.4.0

编辑:我提出了一个 GLib 问题 here

这可能不是 GLib 的错,但鉴于迄今为止答案的不同意见,我将把它留给那里的开发人员来决定是否使用 NVidia 提出它。

我现在使用了 nemequ 的解决方法,它可以毫无怨言地编译。

【问题讨论】:

CUDA 工具链使用 gcc 编译其大部分代码,并且 NVIDIA 集成遵循 gcc 推荐的用户端宏命名的确切做法,您在问题中引用了该做法。自第一个测试版发布以来,它一直以这种方式工作。不幸的是,glib 开发人员已将冲突集成到他们的标头中,但您不应该期望可以从 CUDA 方面解决任何问题 GLib 开发人员没有将冲突集成到他们的头文件中。 GLib 正确使用了__noinline__ 属性名称,期望它由编译器提供。 CUDA 在为编译器保留的以下划线为前缀的命名空间中定义宏是错误的(例如,参见***.com/a/35243083/2931197)。 @Philip:您可能会争辩说 Nvidia 的宏定义是编译器的一部分。 GLib 库绝对不是编译器的一部分,我认为 gcc 对带有双下划线的用户属性的注释可能违反了关于保留标识符的 C++ 标准,或者至少只与 gcc 兼容而与其他编译器不兼容。 另一个问题是:如何在 Cuda 设备代码中使用 glib? 【参考方案1】:

GLib 显然就在这里。在使用 __noinline__ 之前,他们会检查 __GNUC__(编译器用来表示与 GNU C 的兼容性,也就是 GNU extensions to C 和 C++),正如 GNU 文档指出的那样:__attribute__((__noinline__))

GNU C 显然也在做正确的事。提供 GNU 扩展的编译器(包括 GCC、clang 和许多其他)是编译器,因此允许它们使用双下划线前缀标识符。事实上,这就是他们背后的全部想法。这是编译器提供扩展而不必担心与用户代码冲突的一种方式(不允许声明双下划线前缀标识符)。

乍一看,NVidia 似乎也在做正确的事,但事实并非如此。假设您将它们视为编译器(我认为这是正确的),则允许它们定义双下划线前缀的宏,例如__noinline__。然而,问题在于 NVidia 还定义了__GNUC__(非常故意,因为他们想宣传对 GNU 扩展的支持),然后以不兼容的方式继续定义 __noinline__,破坏了 GNU C 提供的 API。

底线:NVidia 错了

至于如何处理,这是一个不太有趣的问题,但有几个选项。您可以(并且应该)向 NVidia 提交问题以修复其编译器。根据我的经验,他们擅长快速响应,但不太可能在合理的时间内解决问题。

您也可以向 GLib 发送补丁以解决该问题,方法是:

#if defined(__CUDACC__)
__attribute__((noinline))
#elif defined(__GNUC__)
__attribute__((__noinline__))
#else
...
#endif

如果您可以控制包含 glib 的代码,另一种选择是执行类似的操作

#undef __noinline__
#include glib_or_file_which_includes_glib
#define __noinline__ __attribute__((noinline))

我的建议是所有三个都做,但尤其是第一个(向 NVidia 提交问题)并找到一种方法在您的代码中解决它,直到 NVidia 解决问题。

【讨论】:

【参考方案2】:

GCC 的 documentation 声明:

您可以选择在名称前后使用__ 指定属性名称。这允许您在头文件中使用它们,而不必担心可能的同名宏。例如,您可以使用属性名称__noreturn__ 而不是noreturn

现在,这只是假设您避免编译器和库使用的双下划线名称;他们可能使用这样的名称。因此,如果您使用 NVCC - NVIDIA 可以声明“我们使用 noinline 而您不能使用它”。

...事实上,基本上就是这样:宏的保护如下:

#if defined(__CUDACC__) || defined(__CUDA_ARCH__) || defined(__CUDA_LIBDEVICE__)
#define __noinline__  __attribute__((noinline))
#endif /* __CUDACC__  || __CUDA_ARCH__ || __CUDA_LIBDEVICE__ */
__CUDA_ARCH__ - 仅针对设备端代码定义,其中 NVCC 是编译器(此处忽略 clang CUDA 支持)。 __CUDA_LIBDEVICE__ - 不知道它在哪里使用,但你肯定不是在构建它,所以你不在乎。 __CUDACC__ 在 NVCC 编译代码时定义。

因此,在常规的主机端代码中,包含此标头不会与 Glib 的定义冲突。

底线:NVIDIA(基本上)在这里做的是正确的事情,这不应该是一个真正的问题。

【讨论】:

以上是关于GLib和CUDA之间的`__noinline__`宏冲突的主要内容,如果未能解决你的问题,请参考以下文章

clang 忽略属性 noinline

深度学习部署笔记(十五): CUDA_Run_Time_API_parallel_多流并行,以及多流之间互相同步等待的操作方式

CUDA测量2个_syncthread()点之间的时间[重复]

第三章 CUDA简介

全局函数和设备函数之间的区别

增加元素 CUDA 内核的算术强度的技术