【发布时间】:2015-10-09 18:09:53
【问题描述】:
我有一个 CUDA 模板库,其中一个函数实际上 不是 模板,但 是 在 .cuh 标头中定义的。 (vector_add_kernel 在下面的kernel.cuh。)
如果多个.cu文件包含kernel.cuh并调用vector_add[_kernel],将导致链接时出现多个定义错误。在 C++ 中,可以使用 inline 限定符来避免此类错误。
但是,inline __global__ ... - 在我的系统上防止多重定义错误 - 导致警告inline 限定符已被忽略。
问:有没有更好的方法来避免多重定义错误,或者只针对这个函数抑制这个警告? inline __global__ 是否安全,或者其他主机编译器真的会忽略它吗?
我可以简单地将vector_add_kernel 移动到一个单独的.cu 文件中,但这将是唯一 非头文件。我也可以模板vector_add_kernel,但在我的库中这没什么意义。
一个(不太简单,抱歉)工作示例(在 Debian 上使用 CUDA 7.0、gcc 4.7.2 测试)如下。
为了澄清,main.cu 是一些用户的代码; lib.cu 是一些外部库不属于我; kernel.cuh 是我的模板库的一部分。所以,外部的lib 和用户的main 都在使用我的模板库kernel.cuh - 但是是分开的。
main.cu:
#include "lib.hpp"
#include "kernel.cuh"
#include <thrust/device_vector.h>
#include <thrust/host_vector.h>
#include <cstddef>
#include <cstdlib>
#include <iostream>
int main(void)
{
const size_t N = 1u << 7;
float* a = (float*) malloc(N * sizeof(float));
float* b = (float*) malloc(N * sizeof(float));
float* c = (float*) malloc(N * sizeof(float));
for (int i = 0; i < N; ++i) {
a[i] = b[i] = 2.0f * i;
}
lib_vector_add(a, b, c, N);
for (int i = 0; i < N; ++i) {
if (c[i] != 2.0f * i + 2.0f * i)
std::cout << "Error, lib, element " << i << std::endl;
}
thrust::device_vector<float> d_a(a, a + N);
thrust::device_vector<float> d_b(b, b + N);
thrust::device_vector<float> d_c(N);
vector_add(d_a, d_b, d_c);
thrust::host_vector<float> h_c = d_c;
for (int i = 0; i < N; ++i) {
if (h_c[i] != 2.0f * i + 2.0f * i)
std::cout << "Error, element " << i << std::endl;
}
}
lib.cu,
#include <kernel.cuh>
#include <thrust/host_vector.h>
#include <thrust/device_vector.h>
void lib_vector_add(float* a, float* b, float* c, size_t N)
{
thrust::host_vector<float> h_a(a, a + N);
thrust::host_vector<float> h_b(b, b + N);
thrust::device_vector<float> d_a = h_a;
thrust::device_vector<float> d_b = h_b;
thrust::device_vector<float> d_c(N);
vector_add(d_a, d_b, d_c);
thrust::host_vector<float> h_c = d_c;
for (int i = 0; i < N; ++i)
{
c[i] = h_c[i];
}
}
lib.hpp,
#pragma once
#include <cstddef>
void lib_vector_add(float*, float*, float*, size_t);
kernel.cuh - 这种形式会导致链接器错误。取消注释第一个 inline 以获得有效代码。
#pragma once
#include <thrust/device_vector.h>
#include <cstddef>
// inline keyword avoids multiple definition errors, but produces warnings.
// UNCOMMENT TO GET A WORKING EXECUTABLE.
// inline
__global__ void vector_add_kernel(
const float *const a,
const float *const b,
float *const c,
const size_t N)
{
int tid = threadIdx.x + blockIdx.x * blockDim.x;
while (tid < N)
{
c[tid] = a[tid] + b[tid];
tid += blockDim.x * gridDim.x;
}
}
// inline produces no warnings.
inline
void vector_add(
const thrust::device_vector<float>& d_a,
const thrust::device_vector<float>& d_b,
thrust::device_vector<float>& d_c)
{
const float *const a_ptr = thrust::raw_pointer_cast(d_a.data());
const float *const b_ptr = thrust::raw_pointer_cast(d_b.data());
float *const c_ptr = thrust::raw_pointer_cast(d_c.data());
const size_t N = d_a.size();
dim3 block(128);
dim3 grid((N + 127) / 128);
vector_add_kernel<<<grid, block>>>(a_ptr, b_ptr, c_ptr, N);
}
Makefile
OBJS = main.o lib.o
DEPS = kernel.cuh
CU_ARCH = -gencode arch=compute_20,code=sm_20
all: app
app: $(OBJS)
nvcc $(CU_ARCH) $(OBJS) -o app
%.o: %.cu $(DEPS)
nvcc $(CU_ARCH) -dc -I./ $< -o $@
clean:
-rm *.o
【问题讨论】:
-
更好的方法是从头文件中获取函数定义。这是一般性建议,并非 CUDA 独有。你已经有了
lib.cu,为什么不把它放在那里呢? -
因为实际上,只有
kernel.cuh是“我的”代码。lib.cu是一些外部库,它使用我的kernel.cuh,main.cu是一些未知用户的代码,他同时使用了我的kernel.cuh和外部lib。 -
也许在您的内核定义周围使用
#ifdef __CUDA_ARCH__会有所帮助?这样它只会在 nvcc 处理时被编译。 -
@void_ptr 这不是
__CUDA_ARCH__的有效用法 -
什么可能有效(除了下面答案中的体面解决方案)是将函数定义包装在
#ifdef FOO ... #endifinkernel.cuh中,然后记录它以便用户(编写@ 987654362@) 在做#include kernel.cuh之前在main.cu中做#define FOO。那么FOO将不会在lib.cu中定义,因此该函数不会被定义两次。